TMemoryStream作为使用非常多的一个Stream类,日常使用时并没有感觉到慢,这是因为代码中使用频度不高的缘故,当使用频度一上去,TMemoryStream的性能简直不忍直视。好了,我们看看原始代码是怎么写的:
function TMemoryStream.Realloc(var NewCapacity: Longint): Pointer;
begin
if (NewCapacity > 0) and (NewCapacity <> FSize) then
NewCapacity := (NewCapacity + (MemoryDelta - 1)) and not (MemoryDelta - 1);
Result := Memory;
if NewCapacity <> FCapacity then
begin
if NewCapacity = 0 then
begin
{$IFDEF MSWINDOWS}
GlobalFreePtr(Memory);
{$ELSE}
FreeMem(Memory);
{$ENDIF}
Result := nil;
end else
begin
{$IFDEF MSWINDOWS}
if Capacity = 0 then
Result := GlobalAllocPtr(HeapAllocFlags, NewCapacity)
else
Result := GlobalReallocPtr(Memory, NewCapacity, HeapAllocFlags);
{$ELSE}
if Capacity = 0 then
GetMem(Result, NewCapacity)
else
ReallocMem(Result, NewCapacity);
{$ENDIF}
if Result = nil then raise EStreamError.CreateRes(@SMemoryStreamError);
end;
end;
end;
我们知道,TMemoryStream最终操纵的是内存,那么分配内存自然是对性能影响最大的部分,反映在代码上就是下面红色的两行代码:
if (NewCapacity > 0) and (NewCapacity <> FSize) then
NewCapacity := (NewCapacity + (MemoryDelta - 1)) and not (MemoryDelta - 1);
根据这两行代码,我们可以看出,每次最少分配8K内存,然后以8K的倍数进行增长。所以当向TMemoryStream写入N*8K数据时,理论上要分配N次内存,对于小于8K或者使用次数很少的MemoryStream来说完全不是问题。但是,一旦写入数据很多,就非常非常麻烦了。
知道原因后,解决方案就很简单了:
1。在写入前直接分配足够的内存来避免多次分配,使用TMemoryStream.Size = 你需要的内存数量(例如1024 *1024);来预先分配足够的内存来使用。
2。使用自己的内存分配策略来分配内存。其实也很简单,从TMemoryStream继承下来然后动态更改NewCapacity的值即可。例如:当所需内存少于8K时还是直接分配8K,但大于8K且小于64M时一次增长原有内存的2倍(或者4倍或者8倍),大于64M且小于1G时一次增长0.5倍,超过1G时每次增长128M。这个内存分配策略只是举例,可以根据自己的需要来进行调整。伪代码大概像这样:
if (NewCapacity > 0) and (NewCapacity <> FSize) then
begin
if FSize = 0 then
iCapacity := NewCapacity shl 1
else if FSize < C_64M then
iCapacity := FSize shl 1
else if FSize < C_1G then
iCapacity := FSize + FSize shr 1
else
iCapacity := FSize + C_128M;
if iCapacity < NewCapacity then
iCapacity := NewCapacity shl 1;
if iCapacity < FMemoryDelta then
iCapacity := FMemoryDelta;
if iCapacity > MaxInt then
iCapacity := MaxInt;
NewCapacity := iCapacity;
NewCapacity := (NewCapacity + (FMemoryDelta - 1)) and not (FMemoryDelta - 1);
end;
这样更改后,MemoryStream的性能可以有1~3个数量级的提升,这时才能真正展现MemoryStream的威力。嗯,内存的快不是你想象中的快,而是超级快!
其实,MemoryStream的原始代码毕竟写于20年前,那时的PC机内存大概在8~16M左右,因此原有的内存分配代码在当时是无可挑剔的,现在的情况完全不一样了,毕竟动不动16G、64G的内存标配让原有的内存分配代码成为了瓶颈,这也是历史原因和历史遗留问题。还好,我们可以快速修正。
最后,顺便说说TList的问题,同样,TList也是20年前的代码,在进行元素添加或者删除时同样存在内存分配和内存移动的问题。相信在简单的更改后你也能轻松把性能提升1~3个数量级!Good Luck!