5

嗨,我正在使用 Perl 变量中的大字符串数据(它的原始电子邮件正文,因此它可以包含附件)。Perl 的substr有一个有趣的问题。似乎它的泄漏或我做错了什么(如果是,是什么?)。考虑代码:

#!/usr/local/bin/perl
use strict;
my $str = 'a'x10_000_000;

system("ps up $$"); #22mb used (why?)
#USER   PID %CPU %MEM   VSZ   RSS  TT  STAT STARTED      TIME COMMAND
#alt  64398  0,0  0,2 33292 22700   7  S+J  22:41     0:00,03 /usr/local/bin/perl ./t.pl

substr($str, 0, 1)='';
system("ps up $$"); #No leak
#USER   PID %CPU %MEM   VSZ   RSS  TT  STAT STARTED      TIME COMMAND
#alt  64398  0,0  0,2 33292 22732   7  S+J  22:41     0:00,04 /usr/local/bin/perl ./t.pl

substr($str, 500);
system("ps up $$"); #Leaked 10Mb (why?!)
#USER   PID %CPU %MEM   VSZ   RSS  TT  STAT STARTED      TIME COMMAND
#alt  64398  0,0  0,3 43532 32520   7  S+J  22:41     0:00,05 /usr/local/bin/perl ./t.pl

my $a = substr($str, 500);
system("ps up $$"); #Leaked 10Mb + Copyed 10Mb
#USER   PID %CPU %MEM   VSZ   RSS  TT  STAT STARTED      TIME COMMAND
#alt  64398  0,0  0,5 64012 52096   7  S+J  22:41     0:00,09 /usr/local/bin/perl ./t.pl

undef $a; #Free scalar's memory
system("ps up $$"); #Free'd 10Mb
#USER   PID %CPU %MEM   VSZ   RSS  TT  STAT STARTED      TIME COMMAND
#alt  64398  0,0  0,4 53772 42308   7  S+J  22:41     0:00,09 /usr/local/bin/perl ./t.pl

# Total leaked 2 times for 10Mb each

substr($str, 500);命令。除了它的字符串的返回副本(没关系),它会泄漏相同数量的内存,所以如果你使用返回值它的两次内存,其中一个在整个时间脚本工作时丢失......另外,它似乎不是任何一种“内部缓冲区”,因为它会泄漏每个调用..

请注意,这种增加 10Mb 的情况不是“重用”内存,因为后续调用会获得越来越多的内存。

有什么建议可以解决或避免这种情况吗?

我的 Perl 版本 5.14.2;我在工作中遇到的相同行为(5.8.8)

4

2 回答 2

2

根据perlglossary,深埋在 Perl 内部,有一个叫做暂存器的东西:

特定文件或子例程的特定调用保留其一些临时值的区域,包括任何词法范围的变量。

这是由生成的代码perl -MO=Concise leak.pl

...
10    <;> nextstate(main 3 leak.pl:30) v:*,&,{,x*,x&,x$,$ ->11
15    <2> sassign vKS/2 ->16
13       <@> substr[t16] sK/2 ->14
-           <0> ex-pushmark s ->11
11          <0> padsv[$str:2,4] s ->12
12          <$> const(IV 500) s ->13
14       <0> padsv[$a:3,4] sRM*/LVINTRO ->15
16    <;> nextstate(main 4 leak.pl:35) v:*,&,{,x*,x&,x$,$ ->17
...

观察padsv[$str:2,4]声明。现在,如果我使用一些调试标志 ( perl -DmX leak.pl) 运行代码,“泄漏”的来源就会变得更加清晰:

USER   PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
stas 55970   1.5  0.3  2454528  21548 s001  S+    6:11PM   0:00.04 perl -DmX leak.pl
...
Pad 0x7f8a328062c8[0x7f8a3240d040] sv:      16 sv=0x7f8a32833298
0x7f8a3240a2a0: (02222) free
0x10d013000: (02223) malloc 9999500 bytes
...
Pad 0x7f8a328062c8[0x7f8a3240d040] sv:      15 sv=0x7f8a328332c8
0x7f8a3240a560: (02231) free
0x10d99d000: (02232) malloc 9999500 bytes
...
USER   PID  %CPU %MEM      VSZ    RSS   TT  STAT STARTED      TIME COMMAND
stas 55970   1.5  0.5  2474064  41084 s001  S+    6:11PM   0:00.06 perl -DmX leak.pl

所以,这只是 Perl 使用暂存器。

于 2012-11-28T20:20:50.873 回答
2

如果您ps在分配前检查一下,$str您会发现大约 2 megs 仅用于运行 perl。因此分配$str需要 20 兆,是创建字符串大小的两倍。如果我不得不猜测发生了什么,perl 必须制作一个 10 兆的字符串,然后将其复制到$str. 20 兆。它保留分配的内存以供以后使用。

substr($str, 0, 1)=''导致$str指向一个新的 C 字符串,您可以使用 Devel::Peek 看到这一点,但不会增加进程内存。它可能使用了从分配内存中释放出来的内存'a' x 10_000_000

my $a = substr($str, 500);有类似的问题。制作一个新的substr10 meg 字符串,然后将其复制到$a需要 20 megs 中。为什么需要更多系统内存才能做到这一点我不确定。perl 可能从它从操作系统获得的前 10 兆块分配内存,因此不再是单个 10 兆块,它不得不向操作系统询问更多。

undef $a肯定会清除与 关联的 C 字符串$a,您可以使用 Devel::Peek 看到,但 perl 不一定会将内存释放回操作系统。

无论如何,这是我最好的猜测。

长话短说,当一个进程将内存释放回操作系统时,这很复杂,而且操作系统的做法也不同。这是一个专门关于 perl 的讨论一个关于 Linux的讨论。

于 2012-11-28T20:02:45.470 回答