51

What are some good tips for keeping memory usage low in a Perl script? I am interested in learning how to keep my memory footprint as low as possible for systems depending on Perl programs. I know Perl isn't great when it comes to memory usage, but I'd like to know if there are any tips for improving it.

So, what can you do to keep a Perl script using less memory. I'm interested in any suggestions, whether they are actual tips for writing code, or tips for how to compile Perl differently.

Edit for Bounty: I have a perl program that serves as a server for a network application. Each client that connects to it gets it's own child process currently. I've used threads instead of forks as well, but I haven't been able to determine if using threads instead of forks is actually more memory efficient.

I'd like to try using threads instead of forks again. I believe in theory it should save on memory usage. I have a few questions in that regard:

  1. Do threads created in Perl prevent copying Perl module libraries into memory for each thread?
  2. Is threads (use threads) the most efficient way (or the only) way to create threads in Perl?
  3. In threads, I can specify a stack_size paramater, what specifically should I consider when specifying this value, and how does it impact memory usage?

With threads in Perl/Linux, what is the most reliable method to determine the actual memory usage on a per-thread basis?

GoldenNewby
  • 4,382
  • 8
  • 33
  • 44
  • 5
    This is a very broad question. Advice might be less random if you can provide some tasks you're trying to accomplish. – Richard Simões Mar 16 '12 at 09:22
  • 1
    I think you should break out your additional questions into new Stackoverflow questions. – brian d foy Mar 23 '12 at 19:27
  • Yeah I had figured that would get a better response, but I realized that after I setup the bounty. Now I'm not that sure how I'd go about doing so. Is it possible to cancel a bounty? I don't think it is :-/ – GoldenNewby Mar 23 '12 at 20:24
  • Note that "use threads" does not actually create what people normally call "threads" outside of Perl: "use threads" emulates normal processes, and is usually far slower and bigger than real threads or real processes, for example, "use threads" emulates the MMU in software and makes more or less full physical copies of every thread that creates a new thread. – Remember Monica Dec 29 '13 at 01:03
  • for perl better supported is fork. threads in perl are like an aliens. there are a lot of bugs, race conditions or dead locks. specially, if you use in thread worker fork, I/O operations and pipes. There is a big chance, your thread will hang up, and you as workaround must detach it. It is not true, nature of threads in perl is with thread emulation by fork. Generally on linux threads are very close to processes. It should save memory, but not in perl. When you will create thread in perl, all variables will be duplicated as a copy for thread. Then better use fork and select functions. – Znik Apr 10 '17 at 13:41

6 Answers6

84

What sort of problem are you running into, and what does "large" mean to you? I have friends you need to load 200 Gb files into memory, so their idea of good tips is a lot different than the budget shopper for minimal VM slices suffering with 250 Mb of RAM (really? My phone has more than that).

In general, Perl holds on to any memory you use, even if it's not using it. Realize that optimizing in one direction, e.g. memory, might negatively impact another, such as speed.

This is not a comprehensive list (and there's more in Programming Perl):

☹ Use Perl memory profiling tools to help you find problem areas. See Profiling heap memory usage on perl programs and How to find the amount of physical memory occupied by a hash in Perl?

☹ Use lexical variables with the smallest scope possible to allow Perl to re-use that memory when you don't need it.

☹ Avoid creating big temporary structures. For instance, reading a file with a foreach reads all the input at once. If you only need it line-by-line, use while.

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

☹ You might not even need to have the file in memory. Memory-map files instead of slurping them

☹ If you need to create big data structures, consider something like DBM::Deep or other storage engines to keep most of it out of RAM and on disk until you need it. Outside of Perl, there are various key-value stores, such as Redis, that may help.

☹ Don't let people use your program. Whenever I've done that, I've reduced the memory footprint by about 100%. It also cuts down on support requests.

☹ (Update: Perl can now handle this for you in most cases because it uses a Copy On Write (COW) mechanism) Pass large chunks of text and large aggregates by reference so you don't make a copy, thus storing the same information twice. If you have to copy it because you want to change something, you might be stuck. This goes both ways as subroutine arguments and subroutine return values:

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

☹ Track down memory leaks in modules. I had big problems with an application until I realized that a module wasn't releasing memory. I found a patch in the module's RT queue, applied it, and solved the problem.

☹ If you need to handle a big chunk of data once but don't want the persistent memory footprint, offload the work to a child process. The child process only has the memory footprint while it's working. When you get the answer, the child process shuts down and releases it memory. Similarly, work distribution systems, such as Minion, can spread work out among machines.

☹ Turn recursive solutions into iterative ones. Perl doesn't have tail recursion optimization, so every new call adds to the call stack. You can optimize the tail problem yourself with tricks with goto or a module, but that's a lot of work to hang onto a technique that you probably don't need.

☹ Use external programs, forks, job queues, or other separate actors so you don't have to carry around short-term memory burdens. If you have a have processing task that will use a big chunk of memory, let a different program (perhaps a fork of the current program) handle that and give you back the answer. When that other program is done, all of its memory returns to the operating system. This program doesn't even need to be on the same box.

☹ Did he use 6 Gb or only five? Well, to tell you the truth, in all this excitement I kind of lost track myself. But being as this is Perl, the most powerful language in the world, and would blow your memory clean off, you've got to ask yourself one question: Do I feel lucky? Well, do ya, punk?

There are many more, but it's too early in the morning to figure out what those are. I cover some in Mastering Perl and Effective Perl Programming.

brian d foy
  • 129,424
  • 31
  • 207
  • 592
  • Is it correct that when you create a hash Perl will never release the memory it used? Can you re-use the same hash for something else to save memory? – Øyvind Skaar Mar 16 '12 at 12:18
  • 6
    IIRC, memory is not release back out of the program, but the program itself will reuse that which has already been claimed. Therefore if your hash goes out of scope, and you then make a new hash it will likely use the same memory that the old one occupied (many caveats, but close enough). Also IIRC this is not a Perl-specific problem but basically the way the OSes distribute memory to programs. – Joel Berger Mar 16 '12 at 13:41
  • 2
    Also I think Reini Urban is probably the guy to ask for deeper looks on reduced-memory-Perling. Checkout his [technical blog](http://blogs.perl.org/users/rurban/) if you dare. – Joel Berger Mar 16 '12 at 13:45
  • A demonstration of memory reuse. `perl -E '$|++;while(1){ my $h = {}; say $h; sleep 1}'`. Again this is more an argument for lexical variables than against memory hogging. If your program ever claims lots of memory at once, it will keep it to the end. – Joel Berger Mar 16 '12 at 14:10
  • Thanks for the answer. I bought your book as a result! I'm not sure on your knowledge of threading in Perl, but can you shed some insight onto the new bounty? – GoldenNewby Mar 22 '12 at 23:15
  • Oh my God! This is brian d foy! Must ... click ... up – motobói Mar 23 '12 at 03:59
  • 3
    "Perl doesn't have tail recursion optimization" - I think that's what goto &foo; does. There's also the [Sub::Call::Tail](http://search.cpan.org/perldoc?Sub::Call::Tail) module. – Tim Bunce Mar 24 '12 at 16:08
  • You can always optimize it yourself, but Perl isn't going to do that for you. – brian d foy Mar 27 '12 at 18:03
  • + 1 for [searching for memory leaks](http://stackoverflow.com/a/1360137/78792). It's easier to leak in perl than people think. Since you're running a persistent server you may encounter leaks in modules that weren't found because authors only exercised them in short run scenarios. – benrifkah Mar 28 '12 at 16:02
  • +1 Nice blog on 'Memory-map files instead of slurping them'.One small question, is File::map be the part of startdard installation of Perl, if yes, can you tell which version of Perl is it? – Nikhil Jain Mar 29 '12 at 09:26
  • You can always find out which namespaces are in which Perl distributions by looking at Module::CoreList – brian d foy Mar 29 '12 at 12:19
  • I knew this list must be somewhere: It's in Chapter 21 of [Programming Perl](http://www.programmingperl.org), and that's one of the parts I worked on. Oh well. – brian d foy Mar 31 '12 at 03:36
  • 1
    +1 "Don't let people use your program. Whenever I've done that, I've reduced the memory footprint by about 100%." – ThisSuitIsBlackNot Jul 23 '13 at 20:44
4

My two dimes.

  1. Do threads created in Perl prevent copying Perl module libraries into memory for each thread?

    • It does not, it is just one process, what isn't repeated in the program stack, each thread must have its own.
  2. Is threads (use threads) the most efficient way (or the only) way to create threads in Perl?

    • IMO Any method eventually calls the pthread library APIs which actually does the work.
  3. In threads, I can specify a stack_size paramater, what specifically should I consider when specifying this value, and how does it impact memory usage?

    • Since threads runs in the same process space, the stack cannot be shared. The stack size tells pthreads how far away they should be from each other. Everytime a function is called the local variables are allocated on the stack. So stack size limits how deep you can recurse. you can allocate as little as possible to the extend that your application still works.

With threads in Perl/Linux, what is the most reliable method to determine the actual memory usage on a per-thread basis?

* 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.
pizza
  • 7,296
  • 1
  • 25
  • 22
  • 2
    From the perspective of a network daemon, the application logic doesn't really change at all. In fact, CPAN's "forks" is built as a drop-in replacement just for that reason. Additionally, at least in Linux, forking appears to actually use LESS memory than threads do. The libraries don't appear to be getting shared, and the memory usage when using threads is more than 2X. So all of that being said, from practical testing most of your answer doesn't appear to be in-line with my own results. That might be due to my own implementation though, and not general results. – GoldenNewby Mar 29 '12 at 20:17
  • for a network daemon that just listens and spawn off an independent task when there is an incoming request, fork has the advantage that it can be very light weight and just let the forked child to load whatever resources it needs and anything that breaks the child does not bring down the daemon. Threads is only worth considering if the incoming connection frequency is very high and a high number of current processes in the system is not desirable. – pizza Mar 29 '12 at 20:28
  • Well in my case it maintains a persistent connection for hours/days, so it rarely spawns a new fork. Even so though, I wouldn't have expected threads to use MORE memory than forks. I think it has something to do with the "Copy on Write" model of Linux, though I would have expected that to apply equally to threads. – GoldenNewby Mar 29 '12 at 20:31
  • I edited the post on 1 "does" -> "does not", which is what I really meant. – pizza Mar 29 '12 at 20:33
  • the pthread library is quite big and it acquires its own resources, that is overhead. In your case, fork is the way to go, it is clean and reliable, just not as scalable as threads. – pizza Mar 29 '12 at 20:35
  • 1
    Another bit of information to consider, if you spawn off a connection for a client with fork. It runs in its own address space. If you implement with pthread, it runs in the same address space for everyone. The thing to consider here is security, whehter you need a separate process to serve each connection. – pizza Mar 29 '12 at 20:43
  • The problem with this answer is that it confuses pthreads (or more correctly what is called threads outside the Perl world) with the so-called ithreads (the windows process emulation) in Perl. Ithreads use a lot more memory than fork, as they basically emulate fork in software, so this answer is completely wrong (for example, thread creation creates a physical copy of the calling thread, while with fork, the copy is usually only virtual, which is way faster). There is no way to use pthreads directly from Perl. The closest to pthreads (or "real threads") in Perl is Coro, which are green threads – Remember Monica Dec 29 '13 at 00:56
  • This answer is true if you write program in C. But completly false if it is written in Perl. with Perl, all variables are duplicated for thread, so all threads have its own copy that takes memory. Fork is better, because it simply duplicate process. After fork all two processes data, libraries, procedures, handles etc are completly shared. Memory block is splited, when one of process will change any data in common memory block. Then it is a big chance, fork will take much less memory. It is good idea for create network server, good logic example is in preforked apache daemon. – Znik Apr 11 '17 at 07:17
3

In addition to brian d foy's suggestions, I found the following also helped a LOT.

  1. Where possible, don't "use" external modules, you don't know how much memory they utilise. I found by replacing the LWP and HTTP::Request::Common modules with either Curl or Lynx slashed memory usage by half.
  2. Slashed it again by modifying our own modules and pulling in only the required subroutines using "require" rather than a full library of unnecessary subs.
  3. Brian mentions using lexical variables with the smallest possible scope. If you're forking, using "undef" also helps by immediately freeing up memory for Perl to re-use. So you declare a scalar, array, hash or even sub, and when you're finished with any of them, use :

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

    undef @divs; undef @array; undef $scalar; undef %hash; undef &sub;

  4. And don't use any unnecssary variables to make your code smaller. It's better to hard code whatever is possible to reduce namespace usage.

Then there's a lot of other tricks you can try depending on your application's functionality. Ours was run by cron, every minute. We found we could fork half the processes with a sleep(30) so half would run and complete within the first 30 seconds, freeing up cpu and memory, and the other half would run after a 30 second delay. Halved the resource usage again. All up, we managed to reduce RAM usage from over 2 GB down to 200MB, a 90% saving.

We managed to get a pretty good idea of memory usage with

top -M

as our script was executed on an relatively stable server with only one site. So watching "free ram" gave us a pretty good indication of memery usage.

Also "ps" grepping for your script and if forking, sorting by either memory or cpu usage was a good help.

ps -e -o pid,pcpu,pmem,stime,etime,command --sort=+cpu | grep scriptname | grep -v grep
Y.K.
  • 290
  • 2
  • 9
2

Both threads and forks will CoW (Copy on Write) memory pages. With threads you can defined shared variables but by default will copy your variables per thread. In both cases you can expect a higher memory usage.

I don't know exactly what kind of application you're dealing with but you may want to consider writing your App using Event Driven model instead of Parent/Child processes. I'd recommend that you take a look at AnyEvent it's quite simple and given the app becomes single threading ( or process ) you'll save some memory (and even faster in some cases). People even written webservers with AnyEvent with very good performance and you could almost not notice it is single threaded. Take a look for example to Twiggy

bluescreen
  • 1,375
  • 2
  • 8
  • 2
  • It really depends on the amount of time I want to spend writing a networking daemon, as to what method I deploy. I find a proper event driven model takes longer to write. I wasn't aware of any modules to help with making that, so thanks for the answer. – GoldenNewby Jan 07 '13 at 19:11
  • Perl threads most certainly do NOT do CoW - every perl "thread" that is created is an actual "physical" copy. – Remember Monica Dec 29 '13 at 00:54
2

If you're really desperate you could try to mount some memory as a filesystem (tmpfs/ramdisk) and read/write/delete files on it. I guess the tmpfs implementation is smart enough to release the memory when you delete a file.

You could also mmap (see File::Map, Sys::Mmap) a huge file on the tmpfs, an idea I got from Cache::FastMmap.

Never tried, but it should work :)

Øyvind Skaar
  • 2,278
  • 15
  • 15
  • 1
    Does this have any advantage of not using a RAM disk but writing files to disk? Is it maybe a little faster if you have slow physical disks? – brian d foy Mar 16 '12 at 20:22
  • It should be a lot faster! Even if there's a lot of dumb overhead when using operations optimized for disks on memory, ram is so much faster than disks.. – Øyvind Skaar Mar 20 '12 at 15:21
  • Btw, on Linux, ramfs might be a better choice: http://www.thegeekstuff.com/2008/11/overview-of-ramfs-and-tmpfs-on-linux/ http://en.wikipedia.org/wiki/Ramfs#Linux – Øyvind Skaar Mar 20 '12 at 15:22
  • 2
    The problem is that you are still using up RAM, which might be the thing you are trying to avoid. – brian d foy Mar 21 '12 at 16:15
  • Oh, yeah.. Guess I forgot what the topic was ;) The advantage is (hopefully) that if you remove a file from the ram disk the os will free the memory. Lets say you create 1GB of data in a long-running process. Instead of storing it in a hash you write it to a file and mmap it. When you don't need the data any more you rm the file and get the memory back. – Øyvind Skaar Mar 22 '12 at 10:06
-7

Try to use more caching. Logic for implementing caching routine is always the same so you can automate using CPAN module Memoize. Use Devel::Size to check the actual memory footprint.

ppant
  • 752
  • 9
  • 19
  • 5
    Caching is going to *increase* your memory footprint. – brian d foy Mar 16 '12 at 09:57
  • 1
    @briandfoy Will caching always increase memory footprints? or some specific cases?. I believe pre-computing operation like building up look-up table etc should reduce the cost and speed up the operation. – ppant Mar 16 '12 at 10:21
  • 5
    If you are saving things in memory, you are using memory. Speed isn't the issue. Usually your trading speed for memory. More of one is less of the other. – brian d foy Mar 16 '12 at 10:29
  • 2
    I also (mostly) disagree with the answer: however there might be a case where caching saves the day: by caching the results of a recursive sub. – Bram Schoenmakers Apr 05 '12 at 07:41
  • caching modules forces malloc. It takes system memory, until script will die/exit. – Znik Apr 11 '17 at 07:22