我有一个完全由Delphi代码编写的应用程序。它是一个64位Windows控制台应用程序,具有工作负载管理器和固定数量的工作线程。这是通过创建线程完成的,每个线程都是一个工作线程。线程不会死亡,它从其自己的队列中拉取工作,而这个队列是工作负载管理器填充的。
看起来这很好用。
然而,我发现在16核系统上,处理时间约为90分钟(它有200万个以上的工作量;每个工作项都需要进行数据库操作)。当我增加到32核时,性能反而下降了!没有数据库争用。基本上,数据库正在等待要做的事情。
每个线程都有自己的DB连接。每个线程的查询仅使用该线程的连接。
我更新了Delphi MM以使用ScaleMM2;这使得改进很大;但我仍然不知道为什么增加内核数会降低性能。
当应用程序有256个线程,在32个内核上,CPU总使用率为80%。 当应用程序有256个线程,在16个内核上,CPU总使用率为100%(这就是为什么我想添加内核的原因) - 结果变慢了 :-(
我已经尽可能多地应用了我可以理解的建议到代码库中。
例如 - 函数不返回字符串,对参数使用Const,使用小的关键部分来保护“共享”数据(实际上使用Multi-read Exclusive Write)。我目前没有分配处理器亲和力;我正在阅读有关使用它的矛盾建议。因此,我目前没有使用(可以轻松添加,但今天还没有)。
问题 - 倾向于我认为问题在于线程争用...
如何确认线程争用是问题?是否有专门用于此类争用识别的工具? 如何确定正在使用“堆栈”,以进一步减少争用?
欢迎提供见解、指导和提示。
如果我知道哪些是相关的,我可以提供相应的代码区域...
看起来这很好用。
然而,我发现在16核系统上,处理时间约为90分钟(它有200万个以上的工作量;每个工作项都需要进行数据库操作)。当我增加到32核时,性能反而下降了!没有数据库争用。基本上,数据库正在等待要做的事情。
每个线程都有自己的DB连接。每个线程的查询仅使用该线程的连接。
我更新了Delphi MM以使用ScaleMM2;这使得改进很大;但我仍然不知道为什么增加内核数会降低性能。
当应用程序有256个线程,在32个内核上,CPU总使用率为80%。 当应用程序有256个线程,在16个内核上,CPU总使用率为100%(这就是为什么我想添加内核的原因) - 结果变慢了 :-(
我已经尽可能多地应用了我可以理解的建议到代码库中。
例如 - 函数不返回字符串,对参数使用Const,使用小的关键部分来保护“共享”数据(实际上使用Multi-read Exclusive Write)。我目前没有分配处理器亲和力;我正在阅读有关使用它的矛盾建议。因此,我目前没有使用(可以轻松添加,但今天还没有)。
问题 - 倾向于我认为问题在于线程争用...
如何确认线程争用是问题?是否有专门用于此类争用识别的工具? 如何确定正在使用“堆栈”,以进一步减少争用?
欢迎提供见解、指导和提示。
如果我知道哪些是相关的,我可以提供相应的代码区域...
Procedure TXETaskWorkloadExecuterThread.Enqueue(Const Workload: TXETaskWorkload);
Begin
// protect your own queue
FWorkloadQueue.Enter;
FWorkloads.Add(Workload);
FWorkloadQueue.Leave;
End;
Procedure TXETaskManager.Enqueue(Const Workload: TXETaskWorkload);
Begin
If FWorkloadCount >= FMaxQueueSize Then Begin
WaitForEmptyQueue;
FWorkloadCount := 0;
End;
FExecuters[FNextThread].Enqueue(Workload);
// round-robin the queue
Inc(FNextThread);
Inc(FWorkloadCount);
If FNextThread >= FWorkerThreads Then Begin
FNextThread := 0;
End;
End;
Function TXETaskWorkloadExecuterThread.Dequeue(Var Workload: TXETaskWorkload): Boolean;
Begin
Workload := Nil;
Result := False;
FWorkloadQueue.Enter;
Try
If FNextWorkload < FWorkloads.Count Then Begin
Workload := FWorkloads[FNextWorkload];
Inc(FNextWorkload);
If Workload Is TXETaskWorkLoadSynchronize Then Begin
FreeAndNil(Workload);
Exit;
End;
Result := True;
End Else Begin
FWorkloads.Clear;
FNextWorkload := 0;
FHaveWorkloadInQueue.ResetEvent;
FEmptyAndFinishedQueue.SetEvent;
End;
Finally
FWorkloadQueue.Leave;
End;
End;
编辑 ---
感谢所有的评论。澄清如下。
这个系统/虚拟机上没有其他东西。所涉及的可执行文件是唯一使用CPU的东西。单线程性能意味着线性。我只是把它变成了分治。如果我有500万辆车要停放,我有30个司机和30个不同的停车场。我可以告诉每个司机等待其他人完成停车,但这比让30个司机同时停车要慢。
单线程性能分析表明没有任何原因导致这种情况。在这个论坛上,我看到有关Delphi和多核性能“陷阱”的提及(主要与字符串处理和LOCK相关)。
数据库基本上是在说它很无聊,正在等待事情发生。我已经用英特尔的vTune副本进行了检查。一般来说,它会显示...锁定。但是,我找不到锁定发生的地方。在我看来,我的程序很简单,目前需要锁定的区域很小。我看不到可能由其他事情引起的锁定,比如字符串创建锁定,或者线程1通过访问该数据(尽管受关键部分保护)对主进程造成问题。
继续研究。再次感谢反馈/想法。