【问题标题】:25% efficiency of 4 cores processor with Delphi Threads [closed]使用 Delphi 线程的 4 核处理器效率为 25% [关闭]
【发布时间】:2020-06-14 13:40:08
【问题描述】:

谁能帮我解决这个问题?

我正在努力提高效率,这就是我尝试并行计算的原因。 经过几次测试,结果告诉我没有什么比在 1 个线程上计算更快的了。在这两种情况下(1 个线程和 4 个线程),它仅占处理器负载的 25%。 有人知道为什么会这样吗? 我能做些什么来实现 100% 的效率(甚至 90% 比 25% 更好)?

您可以在下面看到一个示例代码:


ToolsThread = class(TThread)
public
 procedure Execute(); override;
 procedure QuickSortT(var dict: TArray<AnsiString>; iLo, iHi: Integer);
 Procedure QSortT(var dict: TArray<AnsiString>);
 constructor Create();
var
 tab : TArray<AnsiString>;
 tmp1: Longint;
end;

procedure ToolsThread.QuickSortT(var dict: TArray<AnsiString>; iLo, iHi: Integer);
var
 Lo, Hi: Longint;
 Pivot: Pointer;
 T: Pointer;
begin
  Lo := iLo;
  Hi := iHi;
  Pivot := pointer(dict[(Lo + Hi) shr 1]); // shr 1 is slightly faster than div 2;
  repeat
    while dict[Lo] < AnsiString(Pivot) do Inc(Lo);
    while dict[Hi] > AnsiString(Pivot) do Dec(Hi);
    if Lo <= Hi then
    begin
      T := pointer(dict[Lo]);
      pointer(dict[Lo]) := pointer(dict[Hi]);
      pointer(dict[Hi]) := T;
      Inc(Lo) ;
      Dec(Hi) ;
    end;
  until Lo > Hi;
  if Hi > iLo then QuickSort(dict, iLo, Hi) ;
  if Lo < iHi then QuickSort(dict, Lo, iHi) ;
end;

Procedure ToolsThread.QSortT(var dict: TArray<AnsiString>);
begin
 QuickSort(dict, 0, Length(dict)-1);
end;

procedure ToolsThread.Execute();
var
 tmp1, tmp2 : Longint;
 dict: TArray<AnsiString>;
begin
 SetLength(dict, 10000000);
 for tmp1:= 0 to 10000000-1 do
  dict[tmp1] := IntToStr(Random(high(integer)));
 QSortT(dict);
end;

Procedure Main;
var
 Th1, Th2, Th3, Th4: ToolsThread;
begin

 Th1 := ToolsThread.Create();
 Th2 := ToolsThread.Create();
 Th3 := ToolsThread.Create();
 Th4 := ToolsThread.Create();

 debug('Start THR');
 Th1.Start;
 Th2.Start;
 Th3.Start;
 Th4.Start;
 th1.WaitFor;
 th2.WaitFor;
 th3.WaitFor;
 th4.WaitFor;
 debug('THR Done');
end;

根据建议更正。 仍然有 25% 的 CPU 负载(每个线程 5-8%)

已解决! 多处理中的一些 Delphi 内存管理存在一般问题。这不是 fastMM4 问题,目前只能作为解决方法解决。

【问题讨论】:

  • 你能发一个minimal reproducible example吗?
  • 那仍然不是minimal reproducible example。理想情况下,它应该是一个控制台应用程序,所有代码都在 .dpr 文件中,并且可以直接复制/粘贴和运行。还包括计时码。
  • 完成,现在应该可以工作了。只需复制/粘贴到新项目中
  • 您的 CPU 有多少个内核(不包括超线程)?
  • 4 核,无 HT。

标签: multithreading performance sorting delphi utilization


【解决方案1】:

首先,在任何地方使用string。不要乱用 AnsiStringstring 类型 - 除非你使用的是 Delphi 2009 之前的版本......而且你使用的是 AnsiString,那么 IntToStr() 将返回一个普通的 string,然后将其重新分配并在放入数组时转换为AnsiString...

从我在你的代码中可以看到,我的猜测是大部分时间都花在调用IntToStr(),然后将返回的string 转换为AnsiString,这将涉及堆管理器,这将是大部分是在这个过程中序列化的。当然,这是一个快速的猜测,我的“大脑分析器”(tm)曾经是错误的。

QuickSort 算法是 O(n*log(n)),它比来自多个线程的 IntToStr() 的 O(n) 调用要快。

尝试将only QSortT(dict) 放入Execute 方法中。它应该几乎是线性的。

另一个猜测是以下行不是最佳的。它们将在交换字符串时涉及引用计数(分配字符串实际上是对隐藏的_UStrAsg()low 函数的调用),由于引用计数器上的“锁定”,这会占用大量 CPU 资源。如果多个线程在“锁定”期间共享一些 CPU 缓存(这可能在您的情况下发生,因为 IntToStr 分配是并行完成的),那么这个“锁定”会产生一些缓存争用,这可能需要一些明显的时间,甚至在较新的 CPU 上。

以下可能更快(待验证):

   var T: pointer;
   ...
    if Lo <= Hi then
    begin
      T := pointer(dict[Lo]);
      pointer(dict[Lo]) := pointer(dict[Hi]);
      pointer(dict[Hi]) := T;
      Inc(Lo) ;
      Dec(Hi) ;
    end;

您也可以将pivot 替换为pointer,但这有点棘手:

var pivot: pointer;
...
  repeat
    Pivot := pointer(dict[(Lo + Hi) shr 1]); // within the 2nd repeat..until
    while dict[Lo] < string(Pivot) do Inc(Lo) ;
    while dict[Hi] > string(Pivot) do Dec(Hi) ; 

想法总是在这种情况下使用真实的分析。至少,在您要测量的代码中使用精密计时器/手表,并查看 CPU 进入的位置。

编辑:我已经使用我的建议编写了一些代码 - 请参阅 https://gist.github.com/synopse/02eb142d35cb44126aed9fd3200a76d1

输出在 2 核 CPU 上:

Fill done in 3.25s
Sort done in 24.11s
  • 在填充/创建期间使用了一个核心 - 符合预期
  • 在排序/执行期间使用了 100% 的核心 - 符合预期

【讨论】:

  • 指针没有帮助。 ` for tmp1:= 0 to 10000000-1 do dict[tmp1] := IntToStr(Random(high(integer)));` 占用 100% 的 CPU,这没关系,但 QSortT(dict); 在 4 个线程中占用 25% (每个 5-8%)
  • 还是一样。
  • 您确实 NOT 将 IntStr() 放在了线程之外,这是我的第一个建议。生成字典,例如在 ToolsThread.Create 中进行内存分配顺序,没有堆争用,并且可能减少 CPU 缓存冲突。
  • 这绝对是内存管理器争用 - 我想你已经正确识别了。
【解决方案2】:

您的Pivot := dict[(Lo + Hi) div 2] 行将AnsiString 的一部分分配给String 类型的变量。你一遍又一遍地做。我真的不知道幕后发生了什么,但是在将所有类型更改为 StringAnsiString 之后,代码将运行 100% 的 CPU 负载,而不是仅使用一个内核。

您必须更加注意编译器警告?

【讨论】:

  • 这不是问题,我纠正了这个问题,问题仍然存在。仍然是 25% 的 CPU 使用率。
  • 我没有使用分析器,但即使使用调试器停止应用程序也显示了如何通过分配Pivot 变量不断地分配和释放内存。将 所有变量 更改为相同类型为我带来了 100% 的 CPU 使用率?‍♂️
  • 我们并不真正知道提问者在运行什么代码,因为只发布了部分代码,而且我们不知道 Delphi 版本。这就是为什么我们需要坚持完整的复制。
  • 这又是我们需要查看minimal reproducible example 的原因。我希望这节课现在能深入人心。
  • 最好删除问题,因为它取决于问题中不存在的信息
猜你喜欢
  • 1970-01-01
  • 2020-01-27
  • 2018-09-18
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2022-08-19
相关资源
最近更新 更多