All Manuals > LispWorks® User Guide and Reference Manual > 11 Memory Management

11.2 Guidance for control of the memory management system

11.2.1 General guidance

The memory management is designed with the intention that the programmer will have to do very little or nothing about it. In general, we believe that the design is quite successful, and in most cases you do not have to do anything. The main exception to this is dealing with long-lived data in long-lived processes in 32-bit LispWorks.

Before doing anything about memory management, you should be familiar with the function room, and use it frequently. There is no point at all in trying to tune the memory management without knowing the sizes of your application, as output by room.

The data and code in the LispWorks image can be categorized according to how long they live, as follows:

  1. Short-lived data
  2. Long-lived data
  3. Permanent data

Note that the distinction is not in the data itself, but in the existence of pointers to it.

In general, you rarely need to worry about short-lived data, and have to worry about permanent data only if you have a large amount of it. In short-lived applications you do not need to worry about long-lived data either, so there is a good chance that you do not have to worry about memory management at all.

In long-lived applications, you certainly need to consider long-lived data in 32-bit LispWorks, and maybe in 64-bit LispWorks.

11.2.2 Short-lived data

Normally you should not do anything about handling of short-lived data, because the default settings are good enough for almost all situations. Sometimes you may hit a situation where the settings are not good ("pathological case"). However, it would normally require a deep understanding of the memory management system to deal with such a situation, and we will in general consider this as a bug and try to fix it. Therefore if you find such situation you should report it to Lisp Support, following the guidelines at www.lispworks.com/support/bug-report.html .

Problems with short-lived data normally just reduce the performance of some part of your application. Normally the best solution is to optimize the code to do less work, including allocating less.

To do that, first find the bottlenecks in your application by using profile (and start-profiling and stop-profiling). time and extended-time can then be used to determine how long specific operations take, how much they allocate and, for long operations, how long they spend in garbage collection. Use this information to decide what to try to optimize.

11.2.3 Long-lived data

Long-lived data is data that lives long enough to be promoted to the highest generation to which promotion occurs automatically (the "blocking generation"), but later becomes garbage. The blocking generation is 2 in 32-bit LispWorks and (by default) 3 in 64-bit LispWorks.

You can check which generation individual objects are in (by generation-number), but normally you want to know the total amount of data in various generations. The function room is used for that. In general, it is useful to call:

(room)

and sometimes also:

(room t)

periodically (every 5 minutes) and log the output. In servers, such logs are essential. From this output you can see how the sizes of the various generations change over time.

If the output shows that the blocking generation grows too much, even though permanent data is not added, you will need to do something about it. In 64-bit LispWorks there is a good chance that you do not have to do anything. In 32-bit LispWorks long-lived processes (for example servers) probably need to do something.

The main thing you will do is calling (gc-generation t). This garbage collects the blocking generation. You should check the state of the memory after calling it by calling room again. If the amount of allocated data (as opposed to total size) did not reduce, you may have a memory leak that causes accumulation of permanent data that does not die.

If gc-generation does free data (that is, the allocation reduces significantly), you probably need to add calls to it to your application.

Compatibility note: In 32-bit LispWorks version 5.1 and earlier, the documented way to collect generation 2 is to call (mark-and-sweep 2). (gc-generation t) does what (mark-and-sweep 2) does, plus some additional operations that improve the performance of allocation. It also has the advantage that it is the same call that is used in 64-bit LispWorks. We recommend always using gc-generation.

To decide when to call gc-generation, you need to consider the following:

  1. You need to prevent excessive growth of the process.
  2. You want to avoid calling gc-generation when the application needs to respond quickly.
  3. The call will be more effective if it is done between chunks of work than in the middle of a chunk of work.

We now discuss these considerations in detail:

  1. You can follow the overall size of the process by looking at the output of (room nil), or programmatically by using the result of room-values. The definition of "excessive growth" depends on the machine that you are running on and what the server actually does. Normally you want to avoid the need for paging, so you should try to keep the size of the image below the size of real memory that it can use. For 32-bit LispWorks on modern machines that have a lot of memory, the limit will be the amount of address space the machine has. In addition, garbage collecting a larger image takes more time. In a typical 32-bit application, 100-200 MB would be the target, though it can be larger. In a 64-bit application the limit is the size of the real memory.
  2. (gc-generation t) can take a significant amount of time. 32-bit LispWorks on a modern machine can collect 100-200 MB in less than a second if it does not page. If it pages, or has a slower CPU, it takes more time. The 64-bit GC is generally faster and better, as long as it does not page, but since you normally deal with much more data in 64-bit images, there may be significant delays in 64-bit LispWorks. If such delays are a problem for your application, you should try to call gc-generation at times when it is less of a problem. Use time to find out how long gc-generation takes in various situations.
  3. If you can identify places where there are no active chunks of work, you can try to place calls to gc-generation in these places. For servers, this is likely to be much less important than the two considerations above, but for an application that computes results using large amounts of data, this may be a significant consideration.

In 32-bit LispWorks, by default, generation 2 (which is the "blocking generation") is not collected automatically, because such collection may take a significant amount of time, so most programmers need to control when it actually happens. You can change this by using collect-generation-2, but usually you need better control, and do a collection of generation 2 when it is appropriate. Therefore if your application generates long-lived data, you need to add calls to gc-generation.

Even if you find that your application does not generate long-lived data (that is, generation 2 does not grow), it is probably a good idea to keep checking, in case some circumstances do cause it to generate long-lived data.

In 64-bit LispWorks by default generation 3 (the "blocking generation") is collected automatically, so there is a good chance that you do not have to do anything. However, you may want to call gc-generation explicitly when you know it is a good time to do it. You may also want to block automatic calls if they they take too long: use set-blocking-gen-num to do that. If generation 3 becomes very big (Gigabytes), you may also consider using marking-gc instead of gc-generation.

Once you set up gc-generation calls, you may still see the image growing even though the allocation does not grow that much. That is normally the result of fragmentation. In 32-bit LispWorks you can use check-fragmentation to check for fragmentation, and try-move-in-generation to prevent it if needed. See 11.3.11 Controlling Fragmentation for a discussion.

In 64-bit LispWorks you have a problem with fragmentation only if you use marking-gc. marking-gc has keyword arguments that can be used to reduce fragmentation, and there is a good chance that using these will be enough to avoid serious fragmentation. gc-generation can be used occasionally to eliminate all fragmentation. Check for fragmentation by using gen-num-segments-fragmentation-state.

11.2.4 Permanent data

Permanently-living data will typically be the actual code of the application, and maybe also data that never goes away.

Because the data never goes away, it is best to put it outside normal garbage collection, which means promoting it to the highest generation. This is done by clean-down, which is called automatically (by default) when saving an image (whether by save-image or deliver). In most cases that is the right time to do it, so normally you do not need to call clean-down explicitly. In some situations you may want to call it yourself, and sometimes you want to avoid the call when saving an image with a lot of non-permanent data. To control the automatic call, see save-image and deliver.

There are several things that need to be considered when using clean-down:

  1. If the permanent data is only a small amount compared to the long-lived data, it is not obvious that clean-down is needed, specially if you use a saved (or delivered) image where the code and maybe some data was already promoted.
  2. clean-down promotes all the data that is live (that is, pointed to from some other live object) in the image when it is called. If the image contains data that is live, but later becomes garbage, it will be promoted and hence not collected until another call to clean-down, which will make the image unnecessarily larger. Since this data is not being accessed, the effect on performance is small, but if there is a lot of it the effect may be significant.
  3. clean-down needs extra memory to operate, especially in 64-bit LispWorks. For very large 64-bit LispWorks images clean-down may fail due to running out of swap memory.
  4. clean-down takes a significant amount of time. If it does not cause paging, it should take seconds, but if it needs to page it may take much longer. You therefore should avoid calling it when you need the application to respond reasonably quickly.

LispWorks® User Guide and Reference Manual - 01 Dec 2021 19:30:20