2012-03-16 66 views
45

为了在Perl脚本中保持内存使用率低,有哪些好的提示?我有兴趣学习如何保持我的内存占用尽可能低的系统取决于Perl程序。我知道Perl在内存使用方面并不是很棒,但我想知道是否有任何改进它的提示。保持Perl内存使用率低的技巧

那么,你可以做些什么来保持Perl脚本使用较少的内存。我对任何建议感兴趣,无论它们是编写代码的实际技巧,还是有关如何以不同方式编译Perl的技巧。

编辑为赏金: 我有一个perl程序,作为网络应用程序的服务器。每个连接到它的客户端当前都有自己的子进程。我也使用线程而不是叉子,但是我一直无法确定使用线程而不是叉子实际上是否更有效率。

我想尝试使用线程而不是叉子。我相信理论上它应该节省内存使用量。我在这方面有几个问题:

  1. 不要在Perl中创建的线程防止 复制Perl模块,库到内存中的每个线程?
  2. threads(使用线程)最有效的方式(或唯一的) 的方式来创建Perl中的线程?
  3. 在线程中,我可以指定一个stack_size参数,在指定此值时应该考虑什么,以及它如何影响 内存使用情况?

使用Perl/Linux中的线程,在每个线程的基础上确定实际内存使用情况的最可靠方法是什么?

+5

这是一个非常宽泛的问题。如果您可以提供您正在尝试完成的一些任务,建议可能不会随机。 – 2012-03-16 09:22:23

+1

我想你应该把你的其他问题分解成新的Stackoverflow问题。 – 2012-03-23 19:27:42

+0

是的,我认为这会得到更好的回应,但是我在设置奖励后意识到。现在我不确定我会如何去做。是否有可能取消赏金?我不认为它是: -/ – GoldenNewby 2012-03-23 20:24:56

回答

76

你遇到了什么样的问题,“大”对你来说意味着什么?我有朋友需要将200 Gb文件加载到内存中,所以他们对于优秀技巧的想法与预算购物者有很大不同,因为它对于250 MB内存造成的最小VM片(实际上是我的手机不止)。

一般来说,即使没有使用它,Perl也会保留您使用的任何内存。在一个方向上实现优化记忆,可能会对另一个产生负面影响,如速度。

这不是一个全面的列表(多在Programming Perl有):

☹使用Perl内存分析工具,帮助您找到问题区域。请参阅Profiling heap memory usage on perl programsHow to find the amount of physical memory occupied by a hash in Perl?

☹使用可能的最小范围的词法变量,以便Perl在不需要时重新使用该内存。

☹避免创建大的临时结构。例如,读取一个带有foreach的文件会一次读取所有输入。如果您只需要逐行,请使用while

foreach (<FILE>) { ... } # list context, all at once 
while(<FILE>) { ... } # scalar context, line by line 

☹你可能甚至不需要在内存中有文件。Memory-map files instead of slurping them

☹如果您需要创建大数据结构,请考虑类似DBM::Deep或其他存储引擎,以便将大部分数据从RAM和磁盘中取出,直到您需要为止。

☹不要让人们使用您的程序。每当我这样做,我已经减少了大约100%的内存占用。它也减少了支持请求。

☹通过引用传递大量文本和大量聚合,因此您不需要复制,从而存储相同的信息两次。如果因为要改变某些内容而必须复制它,则可能会卡住。作为子程序参数和子程序返回值,这两种方式都可以:

call_some_sub(\$big_text, \@long_array); 
sub call_some_sub { 
     my($text_ref, $array_ref) = @_; 
     ... 
     return \%hash; 
     } 

☹追踪模块中的内存泄漏。直到我意识到a module wasn't releasing memory,我在应用程序中遇到了很大的问题。我在模块的RT队列中找到一个补丁,应用它,并解决了这个问题。

☹如果您需要处理一大块数据但不想占用内存占用量,请将工作转移到子进程。子进程在运行时只有内存占用。当你得到答案时,子进程关闭并释放内存。同样,工作分配系统(例如Gearman)可以在机器之间传播工作。

re将递归解决方案转换为迭代解决方案。 Perl没有尾递归优化,所以每个新的调用都会添加到调用堆栈中。您可以使用goto或模块的技巧自行优化尾部问题,但这需要很多工作来坚持您可能不需要的技术。

☹他使用6 Gb还是只使用了5个?那么,告诉你实情,在这种兴奋中,我有点失去了自己。但是,因为这是Perl,世界上最强大的语言,并且会打消你的记忆,所以你必须问自己一个问题:我感到幸运吗?那么,你好,朋克?

还有很多,但现在清晨才知道这些是什么。我在Mastering PerlEffective Perl Programming中覆盖了一些。

+0

这是正确的,当你创建一个散列Perl永远不会释放它使用的内存?你可以重复使用相同的哈希值来节省内存吗? – 2012-03-16 12:18:00

+6

IIRC,内存不会退出程序,但程序本身将重用已被声明的内容。因此,如果你的哈希超出了范围,然后你做了一个新的哈希,它可能会使用与旧的哈希相同的内存(许多警告,但足够接近)。另外IIRC这不是一个Perl特有的问题,但基本上是操作系统将内存分配给程序的方式。 – 2012-03-16 13:41:32

+2

另外,我认为Reini Urban可能是要求对减少内存的Perling进行更深层次的观察的人。如果你敢的话,请查看他的[技术博客](http://blogs.perl.org/users/rurban/)。 – 2012-03-16 13:45:57

-7

尝试使用更多缓存。实现缓存例程的逻辑总是相同的,所以你可以自动使用CPAN模块Memoize。使用Devel::Size检查实际的内存占用情况。

+5

缓存会增加内存占用量。 – 2012-03-16 09:57:00

+1

@briandfoy缓存是否会增加内存占用?或者一些特定的情况?我相信像构建查找表等预计算操作应该降低成本并加速操作。 – ppant 2012-03-16 10:21:39

+5

如果您将内容保存在内存中,则正在使用内存。速度不是问题。通常你的记忆交易速度。其中更多的不是其他。 – 2012-03-16 10:29:30

2

如果你真的很绝望,你可以尝试挂载一些内存作为文件系统(tmpfs/ramdisk)并在其上读取/写入/删除文件。我猜tmpfs实现足够聪明,可以在删除文件时释放内存。

您也可以mmap(请参阅File::Map,Sys::Mmap)tmpfs上的一个大文件,我从Cache::FastMmap得到的一个想法。

从来没有尝试过,但它应该工作:)

+1

这是否有没有使用RAM磁盘,但写入文件到磁盘的任何优势?如果物理磁盘速度较慢,速度可能会更快一些吗? – 2012-03-16 20:22:07

+0

它应该快很多!即使在使用针对内存上的磁盘优化的操作时存在大量笨拙的开销,ram也比磁盘快得多。 – 2012-03-20 15:21:12

+0

顺便说一下,在Linux上,ramfs可能是更好的选择:http://www.thegeekstuff.com/2008/11/overview-of-ramfs-and-tmpfs-on-linux/ http://en.wikipedia.org/wiki/Ramfs#Linux – 2012-03-20 15:22:57

4

我的两个硬币。

  1. Perl中创建的线程是否阻止将Perl模块库复制到每个线程的内存中?

    • 它没有,它只是一个进程,在程序堆栈中没有重复什么,每个线程都必须有自己的 。
  2. 线程(使用线程)是在Perl中创建线程的最有效方法(或唯一)吗?

    • IMO任何方法最终会调用实际完成该工作的pthread库API。
  3. 在线程,我可以指定一个STACK_SIZE paramater,具体是什么我应该考虑当 指定此值,以及它如何影响内存使用情况?

    • 由于线程运行在相同的进程空间中,堆栈无法共享。堆栈大小 告诉pthreads应该离彼此有多远。每次调用一个函数 时,局部变量将被分配到堆栈上。所以堆栈大小限制了你可以递归的深度。 您可以尽可能少地分配您的应用程序仍能工作的范围。

随着在Perl/Linux的主题,什么是最可靠的方法,以确定在每个线程的基础上实际使用的内存 ?

* Stack storage is fixed after your thread is spawned, heap and static storage is shared and 
    they can be used by any thread so this notion of memory usage per-thread doesn't really 
    apply. It is per process. 


Comparing fork and thread: 

* fork duplicate the process and inherites the file handles 

    advantages: simpler application logic, more fault tolerant. 
       the spawn process can become faulty and leaking resource 
       but it will not bring down the parent. good solution if 
       you do not fork a lot and the forked process eventually 
       exits and cleaned up by the system. 

    disadvantages: more overhead per fork, system limitation on the number 
       of processes you can fork. You program cannot share variables. 

* threads runs in the same process with addtional program stacks. 

    advantages: lower memory footprint, thread spawn if faster and ligther 
       than fork. You can share variables. 

    disadvantages: more complex application logic, serialization of resources etc. 
       need to have very reliable code and need to pay attention to 
       resource leaks which can bring down the entire application. 

IMO, depends on what you do, fork can use way less memory over the life time of the 
application run if whatever you spawn just do the work independently and exit, instead of 
risking memory leaks in threads. 
+2

从网络守护进程的角度来看,应用程序逻辑根本没有改变。事实上,CPAN的“叉子”就是因为这个原因而被建造成一个直接替代品。此外,至少在Linux中,分支看起来实际上使用LESS内存,而不是线程。这些库似乎没有被共享,并且使用线程时的内存使用率超过2倍。因此,所有这一切都是说,从实际测试中,你的答案大部分似乎不符合我自己的结果。这可能是由于我自己的实施,但不是一般的结果。 – GoldenNewby 2012-03-29 20:17:09

+0

对于一个网络守护进程来说,当有一个传入的请求时,它只是监听并产生一个独立的任务,fork的优点是它可以非常轻便,只需让分叉的孩子加载它需要的任何资源以及打破孩子的任何资源不会降低守护进程。如果传入的连接频率非常高并且系统中的大量当前进程不可取,则线程仅值得考虑。 – pizza 2012-03-29 20:28:26

+0

那么在我的情况下,它保持连续数小时/天的连接,所以它很少会产生新的分叉。即使如此,我也不希望线程使用更多内存而不是分支。我认为这与Linux的“拷贝写入”模式有关,尽管我会预期它将同样适用于线程。 – GoldenNewby 2012-03-29 20:31:40

1

线程和分支都将CoW(写入时复制)内存页面。使用线程可以定义共享变量,但默认情况下会为每个线程复制变量。在这两种情况下,您都可以预期更高的内存使用量

我不确切知道你在处理什么样的应用程序,但是你可能要考虑使用事件驱动模型而不是父/子进程来编写你的应用程序。我建议你看看AnyEvent这很简单,并且考虑到应用程序变成单线程(或进程),你将节省一些内存(在某些情况下甚至更快)。人们甚至用AnyEvent编写了web服务器,性能非常好,你几乎不会注意到它是单线程的。看看例如Twiggy

+0

这实际上取决于我想花费写入网络守护程序的时间,以及我部署的方法。我发现一个适当的事件驱动模型需要更长的时间来写。我没有意识到有任何模块可以帮助解决这个问题,所以谢谢你的答案。 – GoldenNewby 2013-01-07 19:11:02

+0

Perl线程绝对不会执行CoW - 创建的每个perl“线程”都是实际的“物理”副本。 – 2013-12-29 00:54:59

1

除了布莱恩福伊的建议,我发现以下也帮了很多。

  1. 如果可能,请不要“使用”外部模块,您不知道它们使用了多少内存。我发现通过用Curl或Lynx替换LWP和HTTP :: Request :: Common模块来减少一半的内存使用量。
  2. 通过修改我们自己的模块并仅使用“require”而不是完整的不需要的子库来提取所需的子例程,从而再次削减了它。
  3. Brian提到使用词法变量尽可能小的范围。如果你分叉,使用“undef”也可以帮助立即释放内存以供Perl重用。所以你声明一个标量,数组,哈希,甚至是子,当你完成任何一个时,使用:

    my(@divs)= localtime(time); $ VAR {minute} = $ divs [1];

    undef @divs; undef @array; undef $标量; undef%hash; undef & sub;

  4. 并且不要使用任何不必要的变量来缩小代码。为了减少命名空间的使用,最好尽可能硬编码。

然后有很多其他的技巧,你可以尝试取决于你的应用程序的功能。我们每分钟都由cron运行。我们发现我们可以用一个睡眠(30)分支一半的进程,所以一半会在前30秒内运行并完成,释放cpu和内存,另一半在延迟30秒后运行。再次减半资源使用。总而言之,我们设法将RAM使用量从2 GB降低到200 MB,节省90%。

我们设法为我们的脚本是一个相对稳定的服务器上只有一个站点执行来获得内存使用的一个不错的主意与

top -M 

。所以看“免费的公羊”给了我们一个相当不错的memery用法。

对于你的脚本也是“ps”grepping,如果分叉,按内存或cpu使用排序是一个很好的帮助。

ps -e -o pid,pcpu,pmem,stime,etime,command --sort=+cpu | grep scriptname | grep -v grep