当前的任务,遍历大量词典,使我头疼。我无法在此处查明CPU使用率高的确切来源,因此希望这里的一些C#专家可以给我一些提示和技巧。
设置是10个预分配的Guid-byte []字典,每个字典包含一百万个条目。整个过程都在迭代,每个字典都有自己的线程。简单地遍历所有变量,并将byte []引用传递给迭代委托,产生随机结果所需的时间不到2ms,但是实际上访问包含条目中的任何字节都会导致该数字增加到300 + ms。
注意:迭代委托是在任何迭代之前构造的,然后我仅传递引用。
如果我对接收到的字节引用不做任何事情,那一切都非常快:
var iterationDelegate = new Action<byte[]>((bytes) =>
{
var x = 5 + 10;
});
但是一旦我尝试访问第一个字节(实际上包含指向其他地方该行的元数据的指针)
var iterationDelegate = new Action<byte[]>((bytes) =>
{
var b = (int)bytes[0];
});
总时间猛增,甚至更奇怪,第一组迭代花费30ms,第二组40+,第三组100+,第四组花费500ms + ...然后我停止测试性能,让调用线程休眠持续几秒钟,一旦我再次开始迭代,它会在30ms内随便开始,然后像以前一样上升,直到我再次给它“呼吸时间”。
当我在VS CPU调用树中查看它时,[外部代码]占用了93%的CPU,我无法查看或至少看不到它是什么。
有什么我可以做些帮助的吗? GC的时间很艰难吗?
编辑1:我要运行的实际代码是:
var iterationDelegate = new Action<byte[]>((data) =>
{
//compare two bytes, ensure the row belongs to desired table
if (data[0] != table.TableIndex)
return;
//get header length
var headerLength = (int)data[1];
//process the header info and retrieve the desired column data position:
var columnInfoPos = (key * 6) + 2;
var pointers = new int[3] {
//data position
BitConverter.ToInt32(new byte[4] {
data[columnInfoPos],
data[columnInfoPos + 1],
data[columnInfoPos + 2],
data[columnInfoPos + 3] }),
//data length
BitConverter.ToUInt16(new byte[2] {
data[columnInfoPos + 4],
data[columnInfoPos + 5] }),
//column info position
columnInfoPos };
});
但是这段代码甚至更慢,迭代时间分别为〜150,〜300,〜600、700 +
这是在各个线程中为每个商店保持活动的工作程序类:
class PartitionWorker
{
private ManualResetEvent waitHandle = new ManualResetEvent(true);
private object key = new object();
private bool stop = false;
private List<Action> queue = new List<Action>();
public void AddTask(Action task)
{
lock (key)
queue.Add(task);
waitHandle.Set();
}
public void Run()
{
while (!stop)
{
lock (key)
if (queue.Count > 0)
{
var task = queue[0];
task();
queue.Remove(task);
continue;
}
waitHandle.Reset();
waitHandle.WaitOne();
}
}
public void Stop()
{
stop = true;
}
}
最后是一个启动迭代的代码,该代码从Task中为每个传入的TCP请求运行。
for (var memoryPartition = 0; memoryPartition < partitions; memoryPartition++)
{
var memIndex = memoryPartition;
mem[memIndex].AddJob(() =>
{
try
{
//... to keep it shor i have excluded readlock and try/finally
foreach (var obj in mem[memIndex].innerCache.Values)
{
iterationDelegate(obj.bytes);
}
//release readlock in finally..
}
catch
{
}
finally
{
latch.Signal();
}
});
}
try
{
latch.Wait(50);
sw.Stop();
Console.WriteLine("Found " + result.Count + " in " + sw.Elapsed.TotalMilliseconds + "ms");
}
catch
{
Console.WriteLine(">50");
}
Edit2: 字典是使用
预先分配的private Dictionary<Guid, byte[]> innerCache = new Dictionary<Guid, byte[]>(part_max_entries);
,对于条目,它们平均为70个字节。该过程占用了大约2Gb的内存,其中有10000000个条目分配在10个词典中。
条目的结构如下:
T | HL | {POS | POS | POS | POS | LEN | LEN} | {数据字节}
其中|表示单独的字节
POS和LEN对条目中的每个数据值重复:
然后{data bytes}是数据有效载荷
答案 0 :(得分:-1)
对于那些可能想知道的人来说,最大的性能提升是实际使用热纺而不是休眠/延迟/ WaitHandles。即使有大量并行请求,CPU的损失也可以忽略不计。对于非常密集的操作,有一个回退实现,即如果旋转花费的时间超过3毫秒,则回退到线程等待。代码现在以恒定的24ms / 10mil条目运行。另外,从代码中删除所有GC集合并回收尽可能多的变量也是有益的。
这是我使用的微调代码:
drop user admin@localhost;
flush privileges;
create user admin@localhost identified by 'admins_password'
注意:这只能与在其自己的线程中运行的代码一起使用!如果在单线程应用程序中使用它,则将在此处阻止所有代码。
编辑: 同样值得注意的是,由于某种原因,以某种方式重写for循环(使其计数为0)会对性能产生重大影响。我不知道原因的确切机制,但我认为与零进行比较会更快。
我还修改了词典,现在是Dictionary(Guid,Int)。我添加了一个byte [] []数组,而字典int指向此数组中的索引。与枚举字典元素并对其进行迭代相比,遍历此数组要快得多。不过,我需要实施一些机制来确保一致性。