需要多线程内存管理器

WeG*_*ars 14 delphi delphi-xe

我将不得不创建一个多线程项目很快我看到实验(delphitools.info/2011/10/13/memory-manager-investigations)显示默认的Delphi内存管理器存在多线程问题.

在此输入图像描述

所以,我找到了这个SynScaleMM.有人可以给它或类似的内存管理器提供一些反馈吗?

谢谢

Arn*_*hez 47

我们的SynScaleMM仍处于试验阶段.

编辑:看看更稳定的ScaleMM2 和全新的SAPMM.但是我的下面的评论仍然值得关注:你做的分配越少,你的规模越大!

但它在多线程服务器环境中按预期工作.对于某些关键测试,缩放比FastMM4要好得多.

但内存管理器可能不是多线程应用程序中的更大瓶颈.如果你没有强调它,FastMM4可以很好地工作.

如果你想在Delphi中编写FAST多线程应用程序,这里有一些(不是教条,只是来自实验和低级Delphi RTL的知识)建议:

  • 始终使用const字符串或动态数组参数,MyFunc(const aString: String)以避免每次调用分配一个临时字符串;
  • 避免使用字符串连接(s := s+'Blabla'+IntToStr(i)),但依赖TStringBuilder于最新版本的Delphi中提供的缓冲写入;
  • TStringBuilder也不是完美的:例如,它会创造大量的临时字符串进行追加一些数值数据,并且将使用非常缓慢SysUtils.IntToStr(),当你添加一些功能integer价值-我不得不重写了很多低级别的功能,以避免大多数字符串我们TTextWriterSynCommons.pas中定义的类中的分配 ;
  • 不要滥用关键部分,让它们尽可能小,但如果你需要一些并发访问,可以依赖一些原子修饰符 - 参见例如InterlockedIncrement / InterlockedExchangeAdd;
  • InterlockedExchange(来自SysUtils.pas)是更新缓冲区或共享对象的好方法.您在线程中创建某些内容的更新版本,然后TObject在一个低级CPU操作中交换共享指向数据(例如实例)的指针.它将通过非常好的多线程扩展通知其他线程的更改.您必须处理数据完整性,但它在实践中非常有效.
  • 不要在线程之间共享数据,而是创建自己的私有副本或依赖一些只读缓冲区(RCU模式更适合缩放);
  • 不要对字符串字符使用索引访问,而是依赖于某些优化函数PosEx(),例如;
  • 不要混合使用AnsiString/UnicodeString各种变量/函数,并通过Alt-F2检查生成的asm代码以跟踪任何隐藏的不需要的转换(例如call UStrFromPCharLen);
  • 而是使用a中的var参数procedure而不是function返回一个字符串(返回一个函数string将添加一个UStrAsg/LStrAsg具有LOCK 的调用,该LOCK将刷新所有CPU内核);
  • 如果可以,对于数据或文本解析,可以使用指针和一些静态堆栈分配的缓冲区而不是临时字符串或动态数组;
  • 不要在TMemoryStream每次需要时创建一个,而是依赖类中的私有实例,已经在足够的内存中进行调整,在这个实例中,您将使用数据Position来检索数据而不更改数据Size(这将是内存由MM分配的块);
  • 限制你创建的类实例的数量:尝试重用相同的实例,如果可以的话,record/object在已经分配的内存缓冲区上使用一些指针,映射数据而不将其复制到临时内存中;
  • 始终使用测试驱动的开发,用专用的多线程测试,试图到达最坏情况下的限制(增加线程数,数据内容,增加一些不连贯的数据,暂停随意,试图胁迫网络或磁盘访问,基准测试实际数据的时间安排......);
  • 永远不要相信自己的直觉,但要在实际数据和流程上使用准确的时间.

我试图在开源框架中遵循这些规则,如果你看看我们的代码,你会发现很多真实的示例代码.

  • +1这个好建议列表中的大多数可以概括为"如果可能的话,不要使用堆" (6认同)
  • @David ......正如你在答案中所说的那样!我只是想通过精确的解决方法技巧和想法使其更清晰. (5认同)
  • 有趣的建议,但其中一些似乎是以维护性为代价优先考虑速度(例如,返回字符串的函数是一种比使用var参数的程序更"自然"的编写代码的方式).所以我还要加上"不要过早优化"的建议.如果你真的需要速度,只能做一些这些改变. (4认同)
  • @Jonathan你是完全正确的:这是我最后两个建议(第一个基准和简介)的原因.但是,如果你希望你的多线程应用程序与FastMM4和当前的参考计实现(即ASM LOCK)很好地扩展,你必须在所有情况下,循环摆脱(临时)串分配. (2认同)
  • @Darian ShortString是一个AnsiString,在使用VCL的任何方法之前将被转换为普通的`String`.所以你在这里会有更多的内存分配.自Delphi 2009以来,你将失去Unicode功能.在某些情况下,ShortString可以很方便(用于处理数值数据或代码级标识符),但是你必须只使用ShortString方法来避免所有那些隐藏的转换为`string`.所以恕我直言,这不是一般建议 - 这可能会减慢你的应用程序. (2认同)

Dav*_*nan 12

如果您的应用程序可以容纳GPL许可代码,那么我建议您使用Hoard.你必须自己编写包装,但这很容易.在我的测试中,我发现没有任何与此代码匹配的内容.如果您的代码无法容纳GPL,那么您可以获得很高的费用获得Hoard的商业许可.

即使您不能在代码的外部版本中使用Hoard,您也可以将其性能与FastMM的性能进行比较,以确定您的应用程序是否存在堆分配可伸缩性问题.

我还发现msvcrt.dll版本中的内存分配器随Windows Vista一起发布,后来在线程争用下可以很好地扩展,肯定比FastMM好得多.我通过以下Delphi MM使用这些例程.

unit msvcrtMM;

interface

implementation

type
  size_t = Cardinal;

const
  msvcrtDLL = 'msvcrt.dll';

function malloc(Size: size_t): Pointer; cdecl; external msvcrtDLL;
function realloc(P: Pointer; Size: size_t): Pointer; cdecl; external msvcrtDLL;
procedure free(P: Pointer); cdecl; external msvcrtDLL;

function GetMem(Size: Integer): Pointer;
begin
  Result := malloc(size);
end;

function FreeMem(P: Pointer): Integer;
begin
  free(P);
  Result := 0;
end;

function ReallocMem(P: Pointer; Size: Integer): Pointer;
begin
  Result := realloc(P, Size);
end;

function AllocMem(Size: Cardinal): Pointer;
begin
  Result := GetMem(Size);
  if Assigned(Result) then begin
    FillChar(Result^, Size, 0);
  end;
end;

function RegisterUnregisterExpectedMemoryLeak(P: Pointer): Boolean;
begin
  Result := False;
end;

const
  MemoryManager: TMemoryManagerEx = (
    GetMem: GetMem;
    FreeMem: FreeMem;
    ReallocMem: ReallocMem;
    AllocMem: AllocMem;
    RegisterExpectedMemoryLeak: RegisterUnregisterExpectedMemoryLeak;
    UnregisterExpectedMemoryLeak: RegisterUnregisterExpectedMemoryLeak
  );

initialization
  SetMemoryManager(MemoryManager);

end.
Run Code Online (Sandbox Code Playgroud)

值得指出的是,在FastMM中的线程争用成为性能障碍之前,您的应用必须非常努力地锤击堆分配器.通常根据我的经验,这会在您的应用程序执行大量字符串处理时发生.

对于在堆分配上遇到线程争用的任何人,我的主要建议是重新编写代码以避免命中堆.你不仅避免了争用,而且还避免了堆分配的费用 - 一个经典的双人!