Perl 为什么这个函数会占用大量内存?

Perl 为什么这个函数会占用大量内存?,perl,memory,binary,unpack,Perl,Memory,Binary,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 @u

我试图将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;

}

Perl中的单个整数值将存储在
SVt_IV
SVt_UV
标量中,标量的大小将是四个机器大小的字,在32位机器上是16字节。因此,一个由1.4亿字节组成的数组将消耗22亿字节,假设它是密集地打包在一起的。再加上
AvARRAY
中用来引用它们的
SV*
指针,我们现在有28亿字节。现在加倍,因为您在返回数组时复制了它,我们现在有56亿字节

这当然是在32位机器上——在64位机器上,我们又翻了一番,所以有112亿字节。这假定内存中有完全密集的填充—实际上,这将以阶段和块的形式分配,因此RAM碎片将进一步增加这一点。我可以想象这个的总大小大约是350亿字节。这听起来并不是离奇的不合理

要获得一种非常简单的方法来大量减少内存使用(更不用说所需的CPU时间),而不是将数组本身作为列表返回,请返回对它的引用。然后返回一个引用,而不是1.4亿SV的庞大列表;这也避免了第二次复制

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;
}

标量包含很多信息

$ 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
为了使它们尽可能小,标量由两个内存块[1]、一个固定大小的磁头和一个可以“升级”以包含更多信息的主体组成

$ 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
可以包含字符串的最小标量类型(如
split
返回的字符串)是
SVt\u PV
。(它通常被称为
PV
,但是
PV
也可以引用指向字符串缓冲区的字段的名称,因此我将使用常量的名称。)

第一块是头部

  • ANY
    是指向主体的指针
  • REFCNT
    是一个引用计数,它允许Perl知道何时可以释放标量
  • 标志
    包含有关标量实际包含内容的信息。(例如,
    SVf_POK
    表示标量包含字符串。)
  • TYPE
    包含标量类型的信息(它可以包含什么类型的信息。)
  • 对于
    SVt\u 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
这只是针对标量本身。它不考虑三个内存块的内存分配系统的开销


这些标量在一个数组中。数组实际上只是一个标量

所以阵列无意中听到了

$ perl -MDevel::Size=total_size -E'say total_size([])'
56   # 32-bit Perl
64   # 64-bit Perl
这是一个空数组。你们有1.4亿个标量,所以它需要一个可以包含1.4亿个指针的缓冲区。(在这种特殊情况下,数组至少不会被过度分配。)在32位系统上,每个指针是4字节,在64位系统上是8字节

这使得总数达到:

  • 32位:56+(4+28)*140000000=4480000056
  • 64位:64+(8+56)*140000000=8960000064
这并没有考虑内存分配开销,但它仍然与您给出的数字有很大不同。为什么?好的,
split
返回的标量实际上与数组中的标量不同。所以现在,你实际上有280000000个标量在内存中


内存的其余部分可能由SUB中当前未执行的词法变量保存。词法变量通常不会在作用域退出时释放,因为下次调用子函数时需要内存。这意味着
bin2list
退出后将继续使用140MB内存


脚注

  • 未定义的标量可以在没有实体的情况下运行,直到为它们指定了值。只包含整数的标量可以通过将整数存储在与
    SVt_PV
    存储字符串缓冲区指针相同的字段中而不为主体分配内存块


  • 这些图像来自于。它们受版权保护。

    Perl scalar需要。为什么需要解包向量?我说的是35GB,几个字节什么都不是。。我需要它有几个原因,我不能改变它。我的主要问题是“@unpacket”数组的大小!您可以在函数的第1行复制。在2号复印。返回副本。它的高级代码效率很低,因此内存使用是成本。请准确描述您不能更改的内容,或许我们可以找到解决方法。我需要二进制文件以“0”和“1”为数组。我无法确定返回的值。我说不出$vector的大小是~600MB(没关系),当运行这行“my@unpack=split/,(unpack“B*”,$vector);”时,内存使用增加到35GB。我需要减少这个。包含有关scalarss.trued\@解包格式的信息。内存确实更少,但不多,只有2GB。有没有办法计算二进制向量中1的个数?这可能是一个解决办法