Eva*_*oll 9 perl memory-management perl-module shared-libraries xs
免责声明我不确定我是否使用了正确的条款.它可能不是负责下面提到的膨胀的选择:它可能DynaLoader是未加载的符号.
是否可以使用模块,例如POSIX.pm,卸载它并减少(缩小或修剪)optree
我试过的事情,
这是一个简单的测试创建文件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);
Run Code Online (Sandbox Code Playgroud)
Shell命令
perl ./test.pl & watch -n1 'ps -C perl -o "cmd rss";'
Run Code Online (Sandbox Code Playgroud)
您可能会或可能不会看到RSS大小增加(POSIX可能在watch生成之前加载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
Run Code Online (Sandbox Code Playgroud)
VS
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
Run Code Online (Sandbox Code Playgroud)
我已经确认了一些事情,nuking命名空间不会丢弃打开的文件句柄POSIX.so和Fnctl.so- 我确定了这一点lsof.这本身就有点令人担忧.我认为在被调用者的包上分配句柄是有意义的.XSLoader也模糊了你可以释放该文件句柄 - 一个可用的功能DynaLoader.
此外,似乎在我libc/ dlfcn.h我
dlclose()函数dlclose()减少handle引用的动态加载的共享对象的引用计数.如果引用计数降为零,则卸载该对象.在句柄引用的对象上调用dlopen()时自动加载的所有共享对象以相同的方式递归关闭.
从dlclose()成功返回并不能保证从调用者的地址空间中删除与句柄关联的符号.除了由显式dlopen()调用产生的引用之外,由于其他共享对象中的依赖性,共享对象可能已被隐式加载(并且引用计数).仅当已释放所有引用时,才能从地址空间中删除共享对象.
所以我猜这可能是怀疑,DynaLoader::dl_unload_file正在呼唤dlclose它似乎确实有效.
foreach my $dlref ( @DynaLoader::dl_librefs ) {
print DynaLoader::dl_unload_file($dlref);
}
Run Code Online (Sandbox Code Playgroud)
在我修复了所有加载的文件之后DynaLoader,XSLoader通过执行上述操作,RSS仍然没有丢失.
一般来说,没有.坚韧不拔的细节是几乎没有人缩小自己的记忆,因为几乎每个人都使用C库malloc(和朋友)调用来直接或间接地分配内存.并且没有(标准)方法告诉C库释放内存(将其发送回操作系统).Perl在这里没有什么不同 - 一旦malloced和freed,Perl依赖的C库保留了内存以供将来使用,这样如果你需要重用内存,就不需要昂贵的内核调用(具体而言brk),它可以简单地重复使用.实际上,这就是你的卸载方案正在做的事情 - 当你回来并在服务器进程的其余部分重用下一个2MB时,你将重新使用内存,而不是调用brk,你会更快.
如果您接管内存分配所有权并自行调用brk,则可以这样做,但这很少值得.并且让perl使用该分配器将需要对perl和重新编译进行一些代码更改.可能不是你想做的.
其他选择是要么硬着头皮,在分离任何服务器之前加载POSIX(这应该将所有这些都留在共享的写时复制内存中,因此只占用5k服务器的2MB内存),或者fork,load孩子的POSIX,做脏工作,退出孩子,继续在父母.这对我来说似乎相对较慢.
是的你可以。
但有龙,而且实际上没有。
SV 和 OP 被分配在竞技场中。OP 保存指向其数据、SV 的指针。这些 OP 和 SV 可以通过 undef 释放,其中 malloc 的部分会立即释放,并且当其中的所有 OP 都被释放时,arena(约 70 个 OP)也会被释放。
然后你就拥有了可以通过遍历命名空间轻松释放的全局变量。但要注意不要销毁来自其他地方的引用仍然存在的数据,并且它的 DESTROY 处理程序无法处理该问题。那里有很多不安全的 DESTROY 代码,因为没有人这样做。
有时要删除的全局变量是从其他地方引用的,因此它不会被释放,只是引用计数下降。
然后你就有了外部 XS 代码,你必须调用dl_unload_file().
在您的情况下,use POSIX会在 main:: 命名空间中创建大量导入,所有导入函数的 GV 别名。它们也需要被删除。
use POSIX ();将跳过导入,因此需要的内存要少得多,而且很可能可以完全删除。
看看什么不是真正的 undef'd see
#!/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);
Run Code Online (Sandbox Code Playgroud)
结果,
=>
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
Run Code Online (Sandbox Code Playgroud)
这表明
SV 头部和身体区域永远不会被释放,它们只是被重复使用。所以只能收缩optree,而不能收缩数据。
如果 undef'd,符号后面的 SV 只是设置为 TEMP,因此它的内存永远不会释放,并且符号本身(GV)只能通过 undef 清除。OP 通过 undef'ing CV 来删除,但系统 malloc 很少释放它,只有在释放整页并且使用 glibc 调用 时才释放它malloc_trim(0),并且 perl 的内存太分散了。毕竟它仍然是一个没有太多压缩的链表。
从卸载 XS 到释放,RSS 略有下降,但仍然高于初始导入后的水平。
我的观察者是watch -n1 'ps -o "comm,rss,vsize" |grep perl;'因为这也适用于 BSD/darwin。
我Internals::gc()为 cperl 编写了一个程序来实际遍历所有竞技场并释放空的竞技场,但它非常不稳定且不推荐,因为虚拟机只能在全局销毁期间“正确”处理这些空闲的 SV,而不是在运行时。请参阅https://github.com/perl11/cperl/issues/336