9

免责声明我不确定我是否使用了正确的术语。它可能不是导致下面提到的膨胀的 optree:它可能是加载的符号DynaLoader没有被释放。

是否可以使用一个模块,例如POSIX.pm,卸载它并减少(缩小或修剪)optree,而无需任何一个

  1. 执行perl
  2. 分叉

我尝试过的事情,

  1. Class::Unload->unload('POSIX');
  2. Symbol::delete_package('POSIX');
  3. no POSIX;

这是一个简单的测试,创建一个文件 test.pl

$|++;
use Symbol;
use Class::Unload;
use POSIX;

print "GOT POSIX";
sleep(3);

no POSIX;
Class::Unload->unload('POSIX');
Symbol::delete_package('POSIX');
print "unloaded";

sleep(3);

外壳命令

perl ./test.pl & watch -n1 'ps -C perl -o "cmd rss";'

您可能会也可能不会看到 RSS 大小的增加(POSIX 可能会在watchspawns之前加载ps)。但是,我想看到它缩回去。

追踪POSIX.pm我看到它使用XSLoader的到底是什么DynaLoader

进行一些快速比较检查后,/proc/$$/smaps我确定使用 POSIX.pm 会导致堆分配表示空间差异。使用 POSIX.pm 时,堆上的第一个分配要大得多:

56122fe4c000-561230040000 rw-p 00000000 00:00 0                          [heap]
Size:               2000 kB
Rss:                1956 kB
Pss:                1956 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:      1956 kB
Referenced:         1956 kB
Anonymous:          1956 kB
AnonHugePages:         0 kB
ShmemPmdMapped:        0 kB
Shared_Hugetlb:        0 kB
Private_Hugetlb:       0 kB
Swap:                  0 kB
SwapPss:               0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Locked:                0 kB
VmFlags: rd wr mr mw me ac sd

对比

560c9f6ba000-560c9f6fc000 rw-p 00000000 00:00 0                          [heap]
Size:                264 kB
Rss:                 220 kB
Pss:                 220 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:       220 kB
Referenced:          220 kB
Anonymous:           220 kB
AnonHugePages:         0 kB
ShmemPmdMapped:        0 kB
Shared_Hugetlb:        0 kB
Private_Hugetlb:       0 kB
Swap:                  0 kB
SwapPss:               0 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Locked:                0 kB
VmFlags: rd wr mr mw me ac sd

我已经确认了一些事情,对命名空间进行核对不会将打开的文件句柄删除到POSIX.so并且Fnctl.so- 我用lsof. 这本身就有些令人担忧。我认为在被调用者的包上分配句柄是有意义的。XSLoader还掩盖了您可以释放该文件句柄 - DynaLoader.

此外,似乎在libc/dlfcn.h我有

dlclose()

函数 dlclose() 减少句柄引用的动态加载的共享对象的引用计数。如果引用计数降至零,则对象被卸载。在句柄引用的对象上调用 dlopen() 时自动加载的所有共享对象都以相同的方式递归关闭。

从 dlclose() 成功返回并不能保证与句柄关联的符号已从调用者的地址空间中删除。除了由显式 dlopen() 调用产生的引用之外,由于其他共享对象中的依赖关系,共享对象可能已被隐式加载(并且引用计数)。只有当所有引用都被释放后,才能从地址空间中删除共享对象。

所以我猜这可能是可疑的,DynaLoader::dl_unload_file正在调用dlclose并且它似乎确实有效。

foreach my $dlref ( @DynaLoader::dl_librefs ) {
  print DynaLoader::dl_unload_file($dlref);
}

在我对所有加载的文件进行核对DynaLoaderXSLoader通过执行上述操作后,RSS 仍然没有下降。

4

2 回答 2

5

一般来说,没有。最重要的细节是几乎没有人缩小自己的内存,因为几乎每个人都malloc直接或间接地使用 C 库(和朋友)调用来分配内存。并且没有(标准)方法可以告诉 C 库释放内存(将其发送回操作系统)。Perl 在这里没有什么不同 - 一旦malloced 和freed,Perl 所依赖的 C 库保留内存以供将来使用,因此如果您需要重用内存,则不需要昂贵的内核调用(特别是,brk),它可以简单地重复使用。事实上,这就是你的卸载场景正在做的事情——当你回来并在服务器进程的其余部分重用下一个 2MB 时,你将重用内存,而不是调用brk,你会快得多.

如果您接管内存分配所有权并调用brk自己,这是可能的,但这很少值得。并且让 perl 使用该分配器将需要对 perl 和重新编译进行一些代码更改。可能不是你想做的。

其他选择要么咬紧牙关,在分叉任何服务器之前加载 POSIX(这应该将所有这些都留在共享的写时复制内存中,因此对于 5k 服务器只占用 2MB 内存),或者分叉,加载POSIX 在孩子中,做脏活,退出孩子,然后在父母中继续。这对我来说似乎相对较慢。

于 2017-10-16T03:15:16.417 回答
4

是的你可以。

但是有龙,实际上没有。

SV 和 OP 分配在竞技场中。OP 持有指向其数据 SV 的指针。这些 OP 和 SV 可以通过 undef 释放,其中 malloc 的部分会立即被释放,并且当其中的所有 OP 都被释放时,arenas(约 70 个 OP)也会被释放。

然后你有全局变量,也可以通过命名空间轻松释放。但要注意不要破坏来自其他地方的引用仍然存在的数据,它的 DESTROY 处理程序无法处理。那里有很多不安全的 DESTROY 代码,因为没有人这样做。

有时要删除的全局变量是从其他地方引用的,所以它不会被释放,只是引用计数下降。

然后你有外部 XS 代码,你必须调用dl_unload_file().

在您的情况下use POSIX,会在 main:: 命名空间中创建大量导入,所有导入函数的 GV 别名。它们也需要被删除。 use POSIX ();将跳过导入,因此需要更少的内存,并且可以完全删除它。

看看什么不是真正的 undef'd 看到

#!/usr/bin/perl
$|++;
my $s = shift // 3;
sub rss { `ps -o "comm,rss,vsize" | grep perl` }
print "BEGIN ",scalar keys %main::," ",rss;
require Symbol;
#require Class::Unload;
require POSIX;

print "GOT POSIX ",scalar keys %main::," ",rss;
sleep($s);

POSIX->import;
print "IMPORT POSIX ",scalar keys %main::," ",rss;
sleep($s);

POSIX->unimport;
#Class::Unload->unload('POSIX');
Symbol::delete_package('POSIX');

for (keys %main::) {
  #print "$_\n";
  undef ${$_} unless /^(STD|!|0|1|2|\]|_)/;
  undef &{$_} unless /rss/;
  undef @{$_};
  # clear the GV
  undef *{$_} unless /^(STD...?|rss|main::|DynaLoader::|_|!)$/;
  # delete the GV
  delete $main::{$_} unless /^(STD...?|rss|main::|DynaLoader::|_|!)$/;
}
#Symbol::delete_package('main::'); # needs a patched Symbol
print "unloaded ",scalar keys %main::," ",rss;

sleep($s);

DynaLoader::dl_unload_file($_) for @DynaLoader::dl_librefs;
undef *DynaLoader::;
print "unload XS ",scalar keys %main::," ",rss;
#print "  $_\n" for keys %main::;
print "POSIX::$_\n" for keys %POSIX::;
print "freed ",scalar keys %main::," ",rss;
sleep($s);

结果,

=>
  BEGIN 45 /usr/src/perl/bl   3192  2451188
  GOT POSIX 70 /usr/src/perl/bl   6112  2468844
  IMPORT POSIX 645 /usr/src/perl/bl   6928  2468844
  unloaded 8 /usr/src/perl/bl   7120  2468844
  unload XS 8 /usr/src/perl/bl   7040  2468596
  freed 8 /usr/src/perl/bl   7048  2468596

这表明

  1. 符号不可靠地删除只读、受保护的符号和
  2. 全局符号(在 main:: 中)不是由 undef 释放的,而是通过删除 stash 条目来释放的。
  3. 不要导入 POSIX 和此类旧的大量导入模块,而是使用全名。清除这些很难。
  4. 您不能仅释放 SV OP,内存大部分会增加,而不是缩小。

SV 头部和身体区域永远不会被释放,它们只是被重复使用。所以你只能收缩 optree,而不是数据。

如果未定义符号,则符号后面的 SV 仅设置为 TEMP,因此它的内存永远不会被释放,符号本身(GV)仅用 undef 清除。OP 通过取消定义 CV 被删除,但系统 malloc 很少释放它,只有当一个完整的页面被释放并使用 glibc 调用时malloc_trim(0),并且 perl 的内存被过度使用。毕竟它仍然是一个没有太多压缩的链表。

rss 从 unload XS 到 freed 下降了一点,但仍高于初始导入后。

我的观察者是watch -n1 'ps -o "comm,rss,vsize" |grep perl;'因为这也适用于 BSD/darwin。

Internals::gc()为 cperl 编写了一个实际遍历所有竞技场并释放空的竞技场,但它非常不稳定且不推荐,因为 VM 只能在全局销毁期间“正确”处理那些免费的 SV,而不是在运行时。见https://github.com/perl11/cperl/issues/336

于 2017-10-21T17:54:14.643 回答