Perl内部使用专用的哈希PL_strtab作为哈希键的共享存储,但是在apache / mod_perl之类的fork环境中,这会造成很大的问题。最佳做法是在父进程中预加载模块,但没有人说它最终会为PL_strtab分配内存,而这些内存页在子进程中往往被隐式修改。修改的原因似乎有两个:
原因1:当子进程PL_strtab增长时,可能会发生重新分配(hsplit())。 原因2:每次创建新引用时,REFCNT。
下面的示例显示了16MB的写时复制泄漏,试图使用哈希。尝试使用-DNODEFAULT_SHAREKEYS重新编译perl失败(https://rt.perl.org/SelfService/Display.html?id=133384)。我能够通过XS模块访问PL_strtab。
理想情况下,我正在寻找一种降级父级中创建的所有哈希值的方法,以将哈希键保留在哈希(HE对象)而不是PL_strtab中,即关闭SHAREKEYS标志。这应该允许将PL_strtab缩小到最小可能的大小。理想情况下,它的父级应该有0个键。
请让我知道您认为通过XS在理论上是可行的。
#!/usr/bin/env perl
use strict;
use warnings;
use Linux::Smaps;
$SIG{CHLD} = sub { waitpid(-1, 1) };
# comment this block
{
my %h;
# pre-growth PL_strtab hash, kind of: keys %$PL_strtab = 2_000_000;
foreach my $x (1 .. 2_000_000) {
$h{$x} = undef;
}
}
my $pid = fork // die "Cannot fork: $!";
unless ($pid) {
# child
my $s = Linux::Smaps->new($$)->all;
my $before = $s->shared_clean + $s->shared_dirty;
{
my %h;
foreach my $x (1 .. 2_000_000) {
$h{$x} = undef;
}
}
my $s2 = Linux::Smaps->new($$)->all;
my $after = $s2->shared_clean + $s2->shared_dirty;
warn 'COPY-ON-WRITE: ' . ($before - $after) . ' KB';
exit 0;
}
sleep 1000;
print "DONE\n";
请注意,父级样本%h被销毁,子级样本无法访问。这样做的唯一目的是为PL_strtab预分配更多的内存,并使写时复制问题更明显。
PL_strtab是共享数据结构(不是%h)的问题。它仅由Perl控制,无法控制或使用IPC :: Shareable或任何其他对我而言众所周知的CPAN模块。
现实生活中的例子: