You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Delphi中TParallel.For性能不及普通for循环的问题排查

为什么你的TParallel.For比普通for循环更慢?

兄弟,问题根本出在你对并行执行的理解上,和TParallel.For的实现无关——你把几乎所有的工作都塞进了临界区,直接把并行执行变成了串行执行,还额外增加了线程创建、切换以及锁的开销,当然比普通循环慢得多!

你的代码问题出在哪?

看你的异步循环:

TParallel.&For(0, 10000000, procedure(k: Integer)
var xItem: TJsonObject;
begin
aLock.Enter;
try
// add new object to the array
xItem := jItems.AddObject;
finally
aLock.Leave;
end;
// populate some object property for test
xItem.I['I'] := k; // .I for integer
xItem.F['F'] := k; // .F for float
xItem.S['S'] := IntToStr(k); // .S for string
xItem.D['D'] := Now; // .D for date
end );

你在每次迭代里都先加锁,然后调用AddObject,这意味着所有线程都必须排队等待获取锁才能执行下一步——相当于所有线程都在串行干活,完全没用到并行的优势。而且线程切换、锁的获取释放都是有开销的,这就导致异步版本比普通循环还慢。

更关键的是:给xItem赋值的操作根本不需要锁!每个线程操作的是自己创建的独立TJsonObject,没有共享资源冲突,这部分工作完全可以并行执行。

正确的优化方案

我们要把不需要锁的工作放到临界区外,只在操作共享资源(也就是全局的TJsonArray)时加锁。这里有两种优化思路:

方案1:先创建填充对象,再加锁添加到全局数组

让每个线程先独立创建并填充好TJsonObject,最后只在把对象添加到全局数组时加锁。这样大部分耗时的工作都能并行执行:

var 
  aLock: TCriticalSection; 
  jItems: TJsonArray; 
  jItem: TJsonObject; 
  aStart: Cardinal; 
  aEnd: Cardinal; 
  i: integer;
begin
  jItems := TJsonArray.Create;
  // 优化后的ASYNC FOR LOOP
  aLock := TCriticalSection.Create;
  aStart := GetTickCount;
  TParallel.&For(0, 10000000, procedure(k: Integer)
  var 
    xItem: TJsonObject;
  begin
    // 无锁环境下创建并填充对象,这部分可并行
    xItem := TJsonObject.Create;
    try
      xItem.I['I'] := k;
      xItem.F['F'] := k;
      xItem.S['S'] := IntToStr(k);
      xItem.D['D'] := Now;

      // 仅在添加到共享数组时加锁
      aLock.Enter;
      try
        jItems.Add(xItem); // 将对象所有权转移给全局数组
        xItem := nil; // 避免后续Free释放已被数组接管的对象
      finally
        aLock.Leave;
      end;
    finally
      xItem.Free; // 如果对象没被数组接管,安全释放
    end;
  end );
  aEnd := GetTickCount;
  Writeln('优化后ASYNC ', aEnd-aStart);
  
  aLock.Free;
  jItems.Clear;

  // 原同步循环保留用于对比
  aStart := GetTickCount;
  for i := 0 to 10000000 do
  begin
    jItem := jItems.AddObject;
    jItem.I['I'] := i;
    jItem.F['F'] := i;
    jItem.S['S'] := IntToStr(i);
    jItem.D['D'] := Now;
  end;
  aEnd := GetTickCount;
  Writeln('SYNC ', aEnd-aStart);

  jItems.Free;
end;

方案2:用局部数组批量合并(性能更优)

如果循环次数极大,还可以让每个线程先填充自己的局部TJsonArray,最后一次性把局部数组的内容合并到全局数组——这样锁的竞争频率会更低,性能提升更明显:

var 
  aLock: TCriticalSection; 
  jItems: TJsonArray; 
  jItem: TJsonObject; 
  aStart: Cardinal; 
  aEnd: Cardinal; 
  i: integer;
begin
  jItems := TJsonArray.Create;
  // 局部数组合并版ASYNC FOR LOOP
  aLock := TCriticalSection.Create;
  aStart := GetTickCount;
  TParallel.&For(0, 10000000, 
    procedure(k: Integer)
    var 
      localArray: TJsonArray;
      xItem: TJsonObject;
      idx: Integer;
    begin
      // 每个线程创建自己的局部数组,完全无锁填充
      localArray := TJsonArray.Create;
      try
        xItem := TJsonObject.Create;
        xItem.I['I'] := k;
        xItem.F['F'] := k;
        xItem.S['S'] := IntToStr(k);
        xItem.D['D'] := Now;
        localArray.Add(xItem);

        // 批量合并到全局数组,仅合并时加锁
        aLock.Enter;
        try
          for idx := 0 to localArray.Count-1 do
            jItems.Add(localArray.Items[idx]);
          localArray.Clear; // 清空局部数组,避免重复释放对象
        finally
          aLock.Leave;
        end;
      finally
        localArray.Free;
      end;
    end );
  aEnd := GetTickCount;
  Writeln('局部数组合并版ASYNC ', aEnd-aStart);
  
  aLock.Free;
  jItems.Clear;

  // 原同步循环保留
  aStart := GetTickCount;
  for i := 0 to 10000000 do
  begin
    jItem := jItems.AddObject;
    jItem.I['I'] := i;
    jItem.F['F'] := i;
    jItem.S['S'] := IntToStr(i);
    jItem.D['D'] := Now;
  end;
  aEnd := GetTickCount;
  Writeln('SYNC ', aEnd-aStart);

  jItems.Free;
end;

额外的小优化

如果对时间精度要求不高,还可以在并行循环开始前先获取一次Now的值,然后传递给每个线程使用——减少多次调用系统时间函数的开销,进一步提升性能。

内容的提问来源于stack exchange,提问作者ar099968

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.13 09:23:01