Perl 为什么这个函数会占用大量内存?
我试图将1.4亿位的二进制向量解压到列表中。 我正在检查这个函数的内存使用情况,但它看起来很奇怪。内存使用率上升到35GB(GB而不是MB)。如何减少内存使用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
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
是一个引用计数,它允许Perl知道何时可以释放标量REFCNT
包含有关标量实际包含内容的信息。(例如,标志
表示标量包含字符串。)SVf_POK
包含标量类型的信息(它可以包含什么类型的信息。)TYPE
- 对于
,最后一个字段指向字符串缓冲区SVt\u PV
SVt_PV
的主体具有以下字段:
不用于讨论中的标量,因为它们不是对象STASH
不用于所讨论的标量。Magic允许在访问变量时调用代码MAGIC
是缓冲区中字符串的长度CUR
是字符串缓冲区的长度。Perl过度分配以加速连接LEN
$ 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的个数?这可能是一个解决办法