【问题标题】:Perl: When is unneeded memory of a scalar freed without going out of scope?Perl:什么时候释放不需要的标量内存而不超出范围?
【发布时间】:2016-09-15 14:10:37
【问题描述】:

我有一个应用程序,它可以将大量文本数据读入一个标量,有时甚至是 GB 的大小。我在该标量上使用substr 将大部分数据读入另一个标量并用空字符串替换提取的数据,因为第一个标量不再需要它。我最近发现的是 Perl 没有释放第一个标量的内存,而它认识到它的逻辑长度已经改变。所以我需要做的是再次将数据从第一个标量提取到第三个标量,undef 第一个标量并将提取的数据放回原处。只有这样,第一个标量占用的内存才能真正释放出来。将 undef 分配给该标量或小于已分配内存块的其他值不会改变已分配内存的任何内容。

以下是我现在所做的:

     $$extFileBufferRef = substr($$contentRef, $offset, $length, '');
     $length            = length($$contentRef);
  my $content           = substr($$contentRef, 0, $length);
     $$contentRef       = undef( $$contentRef) || $content;

$$contentRef 可能是例如第一行大小为 5 GB,我提取 4.9 GB 的数据并替换提取的数据。第二行现在将报告例如100 MB 的数据作为字符串的长度,但例如Devel::Size::total_size 仍会输出为该标量分配的 5 GB 数据。并且将undef 或诸如此类分配给$$contentRef 似乎并没有改变这一点,我需要在该标量上调用undef 作为函数。

我原以为$$contentRef 后面的内存在应用substr 之后已经至少部分释放了。好像不是这样的……

那么,只有在变量超出范围时才释放内存吗?如果是这样,为什么分配 undef 与调用 undef 作为同一标量上的函数不同?

【问题讨论】:

  • 您是否在其他地方需要该内存?
  • 是的,由于不同的原因,我有多个数据副本,此外,整个过程可能会并行执行。所以在整个过程中浪费了一些 GB 的内存是我需要关心的。是的,可能是糟糕的设计,但目前就是这样......

标签: perl memory memory-management


【解决方案1】:

你的分析是正确的。

$ perl -MDevel::Peek -e'
   my $x; $x .= "x" for 1..100;
   Dump($x);
   substr($x, 50, length($x), "");
   Dump($x);
'
SV = PV(0x24208e0) at 0x243d550
  ...
  CUR = 100       # length($x) == 100
  LEN = 120       # 120 bytes are allocated for the string buffer.

SV = PV(0x24208e0) at 0x243d550
  ...
  CUR = 50        # length($x) == 50
  LEN = 120       # 120 bytes are allocated for the string buffer.

Perl 不仅过度分配字符串,它甚至不会释放超出范围的变量,而是在下次进入范围时重用它们。

$ perl -MDevel::Peek -e'
   sub f {
      my ($set) = @_;
      my $x;
      if ($set) { $x = "abc"; $x .= "def"; }
      Dump($x);
   }

   f(1);
   f(0);
'
SV = PV(0x3be74b0) at 0x3c04228   # PV: Scalar may contain a string
  REFCNT = 1
  FLAGS = (POK,pPOK)              # POK: Scalar contains a string
  PV = 0x3c0c6a0 "abcdef"\0       # The string buffer
  CUR = 6
  LEN = 10                        # Allocated size of the string buffer

SV = PV(0x3be74b0) at 0x3c04228   # Could be a different scalar at the same address,
  REFCNT = 1                      #   but it's truly the same scalar
  FLAGS = ()                      # No "OK" flags: undef
  PV = 0x3c0c6a0 "abcdef"\0       # The same string buffer
  CUR = 6
  LEN = 10                        # Allocated size of the string buffer

逻辑是,如果您曾经需要内存,那么您很有可能再次需要它。

出于同样的原因,将undef 分配给标量不会释放其字符串缓冲区。但是 Perl 让您有机会根据需要释放缓冲区,因此将标量传递给 undef 确实会强制释放标量的内部缓冲区。

$ perl -MDevel::Peek -e'
   my $x = "abc"; $x .= "def";  Dump($x);
   $x = undef;                  Dump($x);
   undef $x;                    Dump($x);
'
SV = PV(0x37d1fb0) at 0x37eec98   # PV: Scalar may contain a string
  REFCNT = 1
  FLAGS = (POK,pPOK)              # POK: Scalar contains a string
  PV = 0x37e8290 "abcdef"\0       # The string buffer
  CUR = 6
  LEN = 10                        # Allocated size of the string buffer

SV = PV(0x37d1fb0) at 0x37eec98   # PV: Scalar may contain a string
  REFCNT = 1
  FLAGS = ()                      # No "OK" flags: undef
  PV = 0x37e8290 "abcdef"\0       # The string buffer is still allcoated
  CUR = 6
  LEN = 10                        # Allocated size of the string buffer

SV = PV(0x37d1fb0) at 0x37eec98   # PV: Scalar may contain a string
  REFCNT = 1
  FLAGS = ()                      # No "OK" flags: undef
  PV = 0                          # The string buffer has been freed.

【讨论】:

  • 谢谢,不知道超出范围的行为。这种缓存方法是每个操作系统进程还是 Perl 解释器?因为我正在使用 mod_perl 并认识到请求后进程中会保留大量内存。想到某个地方的内存泄漏,但可能是那个“聪明的”缓存。一个进程在内存中可以有许多解释器,如果所有这些解释器都缓存了一些 GB 的数据,我就有问题了。
  • 抱歉,我不理解您的回答:如果我是正确的,每个进程的许多线程都会选择 mod_perl 的池中内存中可用的任意解释器。所以缓存/分配的内存需要分配给解释器,并且只有在某个线程使用这种缓存/分配的内存执行某个解释器时才使用?其他口译员没有受益,而是分配自己。离开解释器的线程不会释放内存。 10 个解释器,例如分配 10 * 2 GB 的数据。仅在从进程中删除解释器时清除。对吗?
  • mod_perl 解释器在使用后不会被释放,它们保留在内存中,默认情况下被不同的线程多次使用。否则 mod_perl 本身没有意义,因为如果在请求后直接释放所有解释器,您将不会获得任何性能。代码需要一遍又一遍地编译......即使是文档也有不同的说法:perl.apache.org/docs/2.0/user/config/…
  • 文档中是否明确说明了这一点?对我来说没有意义,并且 mod_perl src 具有像 modperl_interp_[un]select 这样的功能,它似乎没有考虑任何线程,只考虑请求、连接或服务器本身。如果只与一个线程一起使用,则根本不需要将解释器放回池中。
  • 此外,Apache 有备用线程和类似的线程,当决定由空闲线程池中的任意线程处理请求时,它无法知道请求是否需要 mod_perl。在线程和 mod_perl 解释器的线程生命周期内固定 1:1 关系上,不需要的解释器只需要被克隆,因为选择了一个线程而过去没有分配解释器,而解释器可以在池中免费使用已经过去了,但现在还没有。
猜你喜欢
  • 1970-01-01
  • 2018-10-05
  • 1970-01-01
  • 2012-04-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-09-16
  • 1970-01-01
相关资源
最近更新 更多