PL_strtab / SHAREKEYS和写时复制泄漏

时间:2018-07-20 21:19:26

标签: apache perl fork mod-perl copy-on-write

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模块。

现实生活中的例子:

  • 在apache / mod_perl,Starman或任何其他前叉环境中,每个人都尝试在父进程中预装尽可能多的模块。对吧?
  • 如果任何预加载的模块创建具有大量键的哈希(甚至是临时的),则Perl会为内部PL_strtab哈希静默分配越来越多的内存。
  • 在任何尝试使用哈希的情况下,
  • PL_strtab都会在子级中静静地被触摸。 问题更加严重,因为我们预加载的模块中有很大一部分是CPAN模块->无法知道其中哪些模块过度使用了哈希值,从而导致父进程的内存占用量增加。

0 个答案:

没有答案