我正在编写一个完全托管的Mercurial库(将在即将推出的完全托管的Mercurial Server for Windows中使用),而我遇到的最严重的性能问题之一就是奇怪的是,分裂了部分数组。
这个想法如下:有一个字节数组,大小从几百字节到一兆字节不等,我需要做的就是将它拆分成部分,在我的特定情况下,{{1字符。
现在dotTrace向我展示的是\n
Split
{代码 正确,我开始的"optimized" version版本)占用了11秒2,300个电话(dotTrace本身引入了明显的性能影响,但一切都达到了规模)。
以下是数字:
unsafe
版本:11 297
ms 2 312
来电
20 001
ms 2 312
来电所以这里是:最快的(最好是可移植的,意味着支持x86和x64)在C#中拆分数组的方法。
答案 0 :(得分:4)
我认为问题是,你在循环中做了很多复杂的操作。此代码删除除循环内的单个添加和比较之外的所有操作。其他复杂的东西只有在检测到分割或阵列结束时才会发生。
此外,很难说你运行测试的数据类型,所以这只是猜测。
public static unsafe Segment[] Split2(byte[] _src, byte value)
{
var _ln = _src.Length;
if (_ln == 0) return new Segment[] { };
fixed (byte* src = _src)
{
var segments = new LinkedList<Segment>(); // Segment[c];
byte* last = src;
byte* end = src + _ln - 1;
byte lastValue = *end;
*end = value; // value-termination
var cur = src;
while (true)
{
if (*cur == value)
{
int begin = (int) (last - src);
int length = (int) (cur - last + 1);
segments.AddLast(new Segment(_src, begin, length));
last = cur + 1;
if (cur == end)
{
if (lastValue != value)
{
*end = lastValue;
}
break;
}
}
cur++;
}
return segments.ToArray();
}
}
修改:修复了代码,因此返回正确的结果。
答案 1 :(得分:3)
对于Split,在32位机器上处理ulong非常慢,所以肯定会减少到uint。如果你真的想要ulong,请实现两个版本,一个用于32位,一个用于64位。
您还应该测量一次处理字节是否更快。
需要分析内存分配的成本。如果它足够大,请尝试在多个呼叫中重用内存。
其他:
ToString:使用“(”+“Offset.ToString()+”,“+ Length.ToString()+”)“;
更快GetHashCode:尝试修复(byte * b =&amp; buffer [offset])
如果多次使用,此版本应该非常快。 关键点:内部阵列扩展到正确的大小后,没有新的内存分配,最小的数据复制。
class ArraySplitter
{
private byte[] m_data;
private int m_count;
private int[] m_stops;
private void AddRange(int start, int stop)
{
// Skip empty range
if (start > stop)
{
return;
}
// Grow array if needed
if ((m_stops == null) || (m_stops.Length < (m_count + 2)))
{
int[] old = m_stops;
m_stops = new int[m_count * 3 / 2 + 4];
if (old != null)
{
old.CopyTo(m_stops, 0);
}
}
m_stops[m_count++] = start;
m_stops[m_count++] = stop;
}
public int Split(byte[] data, byte sep)
{
m_data = data;
m_count = 0; // reuse m_stops
int last = 0;
for (int i = 0; i < data.Length; i ++)
{
if (data[i] == sep)
{
AddRange(last, i - 1);
last = i + 1;
}
}
AddRange(last, data.Length - 1);
return m_count / 2;
}
public ArraySegment<byte> this[int index]
{
get
{
index *= 2;
int start = m_stops[index];
return new ArraySegment<byte>(m_data, start, m_stops[index + 1] - start + 1);
}
}
}
测试程序:
static void Main(string[] args)
{
int count = 1000 * 1000;
byte[] data = new byte[count];
for (int i = 0; i < count; i++)
{
data[i] = (byte) i;
}
Stopwatch watch = new Stopwatch();
for (int r = 0; r < 10; r++)
{
watch.Reset();
watch.Start();
int len = 0;
foreach (var seg in data.MySplit(13))
{
len += seg.Count;
}
watch.Stop();
Console.WriteLine("MySplit : {0} {1,8:N3} ms", len, watch.Elapsed.TotalMilliseconds);
watch.Reset();
watch.Start();
ArraySplitter splitter = new ArraySplitter();
int parts = splitter.Split(data, 13);
len = 0;
for (int i = 0; i < parts; i++)
{
len += splitter[i].Count;
}
watch.Stop();
Console.WriteLine("ArraySplitter: {0} {1,8:N3} ms", len, watch.Elapsed.TotalMilliseconds);
}
}
结果:
MySplit : 996093 9.514 ms
ArraySplitter: 996093 4.754 ms
MySplit : 996093 7.760 ms
ArraySplitter: 996093 2.710 ms
MySplit : 996093 8.391 ms
ArraySplitter: 996093 3.510 ms
MySplit : 996093 9.677 ms
ArraySplitter: 996093 3.468 ms
MySplit : 996093 9.685 ms
ArraySplitter: 996093 3.370 ms
MySplit : 996093 9.700 ms
ArraySplitter: 996093 3.425 ms
MySplit : 996093 9.669 ms
ArraySplitter: 996093 3.519 ms
MySplit : 996093 9.844 ms
ArraySplitter: 996093 3.416 ms
MySplit : 996093 9.721 ms
ArraySplitter: 996093 3.685 ms
MySplit : 996093 9.703 ms
ArraySplitter: 996093 3.470 ms
答案 2 :(得分:2)
安东,
我不知道你是否仍然有兴趣优化这个,因为这个线程相当陈旧,但是我看到你的代码在你的在线存储库里几乎是一样的,所以我想我会试一试。在评估您的HgLab应用程序时,我在bitbucket.org上查看了您的HgSharp代码。我使用本机构造重写了该函数,这极大地简化了它。我的测试结果比原来的例程好一半。我通过加载几兆字节的源文件测试它,并使用原始例程将时间与同一操作的性能进行比较。
除了重写基本逻辑之外,我决定使用框架内置的本地ArraySegment<>
而不是自定义实现。唯一显着的区别是ArraySegment<>
公开Count
属性而不是Length
属性。下面的代码不需要unsafe关键字,因为我没有使用指针,但如果更改它,似乎确实会有轻微的性能提升。
public static ArraySegment<byte>[] SplitEx(this byte[] source, byte value) {
var _previousIndex = -1;
var _segments = new List<ArraySegment<byte>>();
var _length = source.Length;
if (_length > 0) {
int _index;
for (_index = 0; _index < _length; _index++) {
var _value = source[_index];
if (_value == value) {
_segments.Add(new ArraySegment<byte>(source, _previousIndex + 1, _index - _previousIndex));
_previousIndex = _index;
}
}
if (--_index != _previousIndex) {
_segments.Add(new ArraySegment<byte>(source, _previousIndex + 1, _index - _previousIndex));
}
}
return _segments.ToArray();
}