我试图将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;
}
答案 0 :(得分:6)
Scalars包含大量信息。
$ 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] 组成,一个固定大小的头部,一个可以升级的主体&#34 ;包含更多信息。
可以包含字符串的最小类型的标量(例如split
返回的标量)是SVt_PV
。 (它通常称为PV
,但PV
也可以引用指向字符串缓冲区的字段的名称,因此我将使用常量的名称。 )
第一个区块是头部。
ANY
是指向身体的指针。REFCNT
是一个引用计数,它允许Perl知道标量何时可以被释放。FLAGS
包含有关标量实际包含内容的信息。 (例如SVf_POK
表示标量包含字符串。)TYPE
包含标量类型的信息(可以包含哪种信息。)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
这仅仅是标量本身。它没有考虑三个内存块的内存分配系统的开销。
这些标量位于数组中。数组实际上只是一个标量。
因此无法听到数组。
$ 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个。
这使总数达到:
这不会影响内存分配开销,但它与您提供的数字仍有很大差异。为什么?好吧,split
返回的标量实际上与数组内的标量不同。所以有一会儿,你实际上有280,000,000个标量!
内存的其余部分可能由当前正在执行的子中的词汇变量保留。词典变量通常不会在范围退出时释放,因为它预期子将在下次调用时需要内存。这意味着bin2list
退出后会继续消耗140MB的内存。
脚注
SVt_PV
相同的字段中而不为主体分配内存块而离开,而是将指针存储到字符串缓冲区。图片来自illguts。它们受版权保护。
答案 1 :(得分:3)
Perl中的单个整数值将存储在SVt_IV
或SVt_UV
标量中,其大小将是四个机器大小的单词 - 因此在32位机器上,16个字节。因此,其中1.4亿个阵列将消耗22亿字节,假设它密集在一起。再加上用于引用它们的SV *
中的AvARRAY
指针,我们现在的数量为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;
}