gpt4 book ai didi

string - 连接的 Delphi 字符串是否保存在保留对字符串的引用的隐藏临时变量中?

转载 作者:行者123 更新时间:2023-12-03 14:42:13 25 4
gpt4 key购买 nike

我正在尝试了解 Delphi 服务器应用程序中的内存问题:最初我怀疑是彻底泄漏,但现在相信我们看到内存挂起的时间比它应该的更长,因为编译器在动态连接字符串时使用了隐藏的临时 + ,导致痛苦的自由空间内存碎片。

背景:

这是一套 Windows 上的 32 位服务器应用程序,Delphi 版本很旧,我认为它是 7,但肯定是 Unicode 之前的,并且使用 Nexus 3 内存管理器,我在其中编写了一个 DLL 来 Hook 所有分配/free 调用(和千兆字节的内存跟踪)。

我有应用程序源代码,但没有编译器;我不是此应用程序的开发人员(甚至不是 Delphi 开发人员),但创建了大量自定义工具来监视、跟踪和分析内存。我一直在 IDA Pro 反汇编程序中挑选 .EXE。

一些示例代码:

我试图将其减少到最低限度的情况;此代码不打算编译:

procedure TaskThread.RunWorkLoop
begin
while not Terminated do
begin

tsk := WaitForWorkToDo(); // this could sit for minutes at a time

SetThreadName('Working on ' + tsk.Name);

tsk.Run(); // THIS COULD TAKE A LONG TIME

SetThreadName('Idle');
end
end;
SetThreadName()接受一个 const 字符串参数并卡在它上面,以便系统的其他部分知道这个线程在做什么。

我对代码的反汇编显示编译器分配了一个隐藏的局部临时变量来接收“工作中”和任务名称部分的连接,这就是传递给 SetThreadName 的内容。 , 它还保留了字符串的句柄。

当任务正在运行时——这可能是 20 分钟——我相信字符串有两个句柄。一个是在 SetThreadName内举行,另一个是在隐藏的临时。

这一切都很好。

然后,当任务结束并且线程名称设置为 'Idle' , SetThreadName()释放原始字符串并分配文字 Idle .

但是:我相信隐藏的本地临时文件仍然保留该字符串的句柄,引用计数 = 1,因此它将占用空间,直到过程返回或下一个循环来覆盖该隐藏的本地临时文件,释放旧值。

并且在此期间,程序无法访问它,无法显式释放它,并且没有任何用途但仍在消耗内存。

对于大多数程序来说,这并不重要,因为它们开始和结束彼此相对较近,因此所有内容都是一次性发布的,但在循环服务器应用程序中,它们可能会停留更长时间。这导致了我们的内存碎片。

情况变得更糟

在实际应用中,更多的是沿着以下路线:
SetThreadName(tsk.Name + '-' + FormatDateTime('mm/dd/yy hh:nn:ss', Now));

在这种情况下,有两个隐藏的临时变量:一个用于 FormatDateTime 的结果。 ,另一个用于整体连接结果,实际上运行为:
tmp1: String;
tmp2: String;
...
tmp1 := FormatDateTime('...');
tmp2 := tsk.Name + '-' + tmp1;
SetThreadName(tmp2);

我确定我看到的是 FormatDateTime 的字符串结果任务完成后很长时间在内存中徘徊,我已经看到它 字面意思 是单个 ~30 字节的分配,位于 1 兆字节内存部分的中间,周围是可用空间; Nexus3MM 使用 VirtualAlloc分配更大的操作系统级块。

单个 30 字节的字符串最终将被释放,无论是在下一个循环中还是在过程退出时,所以我确定这不是泄漏,但我宁愿将单个 30 字节的分配放在一个单独的分配中间当我们完成它时,兆字节部分实际上会消失,因此整个部分可以发布到操作系统。

但是如果它存在的时间足够长,内存管理器就会从中分配其他东西,内存中的这个漏洞就会变得更加持久。

我们有非常详细的忙/空闲内存映射,并且确信这种碎片正在杀死我们(这当然不是唯一的原因)。

我的问题:

1)我理解正确吗?

2)如果是这样,这是通过使用显式临时文件来消除隐藏临时文件的唯一解决方法,我们在其中执行以下操作:
tmp1: String;
tmp2: String;
...
tmp1 := FormatDateTime('...');
tmp2 := tsk.Name + '-' + tmp1;
SetThreadName(tmp2);
tmp1 := ''; // release the date/time string
tmp2 := ''; // release the overall thread name string

我非常有信心我必须用 FormatDateTime 做到这一点。中间结果(我已经具体看过),但不确定整体连接。

这只是感觉不对。

编辑:几周后才更新。我们重写了中央循环以使用显式临时变量,它实际上在某些关键服务器进程的内存碎片方面产生了显着(尽管不是主要)差异。我们还有其他事情要研究,但我很清楚这是一条值得走的路。

最佳答案

根据我的经验,它确实像那样工作。我不确定这是通过契约(Contract)还是通过实现。我想随着最近添加的内联变量声明,现在可能会略有不同。但是在 unicode 之前的 Delphi 中,我相信它的工作原理与您描述的完全相同。

所有使用托管类型变量(隐式或显式)或包含变量的记录的例程都将生成隐式 try/finally例程中的块,使用 finally部分清除引用。你的代码真正做的是:

procedure TaskThread.RunWorkLoop
var
sImplicit : string;
begin
sImplicit := '';
try
while not Terminated do
begin
tsk := WaitForWorkToDo(); // this could sit for minutes at a time

sImplicit := 'Working on ' + tsk.Name;

SetThreadName(sImplicit);

tsk.Run(); // THIS COULD TAKE A LONG TIME

SetThreadName('Idle');
end;
finally
sImplicit := '';
end;
end;

在您的情况下,由于您从未退出使用隐式变量的例程,因此它确实保留在内存中。

至于解决方案,我相信您提出的建议会奏效。但是您也可以简单地将代码移动到另一个方法(或本地过程)。
procedure TaskThread.RunWorkLoop
procedure JustKeepWorking;
begin
tsk := WaitForWorkToDo(); // this could sit for minutes at a time
SetThreadName('Working on ' + tsk.Name);
tsk.Run(); // THIS COULD TAKE A LONG TIME
SetThreadName('Idle');
end;
begin
while not Terminated do
begin
JustKeepWorking;
end
end;

另外,您可能想查看 this question以获得更多见解。

关于string - 连接的 Delphi 字符串是否保存在保留对字符串的引用的隐藏临时变量中?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57683466/

25 4 0
Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
广告合作:1813099741@qq.com 6ren.com