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