为什么这个函数使用了大量内存?

use*_*639 2 memory binary perl unpack

我正在尝试将1.4亿比特的二进制向量解包到列表中.我正在检查这个函数的内存使用情况,但看起来很奇怪.内存使用量增加到35GB(GB而不是MB).我怎样才能减少内存使用量?

sub bin2list {
    # This sub translates a binary vector to a list of "1","0" 
    my $vector = shift;
    my @unpacked = split //, (unpack "B*", $vector );
    return @unpacked;

}
Run Code Online (Sandbox Code Playgroud)

ike*_*ami 6

标量包含大量信息.

$ perl -MDevel::Peek -e'Dump("0")'
SV = PV(0x42a8330) at 0x42c57b8
  REFCNT = 1
  FLAGS = (PADTMP,POK,READONLY,pPOK)
  PV = 0x42ce670 "0"\0
  CUR = 1
  LEN = 16
Run Code Online (Sandbox Code Playgroud)

为了使它们尽可能小,标量由两个内存块[1],一个固定大小的头部和一个可以"升级"以包含更多信息的主体组成.

可以包含字符串的最小类型的标量(例如返回的标量split)是a SVt_PV.(它通常被称为PV,但PV也可以指向指向字符串缓冲区的字段的名称,因此我将使用常量的名称.)

SVt_PV

第一个块是头部.

  • ANY 是指向身体的指针.
  • REFCNT 是一个引用计数,允许Perl知道标量何时可以被释放.
  • FLAGS包含有关标量实际包含内容的信息.(例如SVf_POK,标量包含一个字符串.)
  • TYPE包含标量类型的信息(它可以包含哪种信息.)
  • 对于a SVt_PV,最后一个字段指向字符串缓冲区.

第二个块是身体.正文SVt_PV包含以下字段:

  • STASH 因为它们不是对象,所以不会在有问题的标量中使用.
  • MAGIC不适用于有问题的标量.Magic允许在访问变量时调用代码.
  • CUR 是缓冲区中字符串的长度.
  • LEN是字符串缓冲区的长度.Perl过度分配以加速连接.

右边的块是字符串缓冲区.您可能已经注意到,Perl过度分配.这加速了连接.

忽略底部的块.它是特殊字符串(例如散列键)的字符串缓冲区格式的替代品.

这加起来多少钱?

$ perl -MDevel::Size=total_size -E'say total_size("0")'
28   # 32-bit Perl
56   # 64-bit Perl
Run Code Online (Sandbox Code Playgroud)

这只是标量本身.它不会占用三个存储器块的存储器分配系统的开销.


这些标量是一个数组.数组实际上只是一个标量.

SVt_AV

因此无意中听到阵列.

$ perl -MDevel::Size=total_size -E'say total_size([])'
56   # 32-bit Perl
64   # 64-bit Perl
Run Code Online (Sandbox Code Playgroud)

这是一个空数组.你有1.4亿个标量,所以它需要一个可以包含1.4亿个指针的缓冲区.(在这种特殊情况下,至少不会过度分配数组.)每个指针在32位系统上是4个字节,在64位上是8个.

这使得总数达到:

  • 32位:56 +(4 + 28)*140,000,000 = 4,480,000,056
  • 64位:64 +(8 + 56)*140,000,000 = 8,960,000,064

这不会影响内存分配开销,但它与您提供的数字仍然非常不同.为什么?好吧,返回的标量split实际上与数组中的标量不同.所以有一会儿,你实际上有280,000,000个标量!


内存的其余部分可能由当前未执行的subs中的词法变量保存.词法变量通常不会在范围退出时释放,因为预期sub将在下次调用时需要内存.这意味着bin2list退出后继续消耗140MB的内存.


脚注

  1. 未定义的标量可以在没有正文的情况下离开,直到为它们分配值.仅包含整数的标量可以通过将整数存储在与SVt_PV存储指向字符串缓冲区的指针相同的字段中而不为主体分配内存块而离开.

图像来自恶魔.它们受版权保护.