Mobile platforms typically inform applications when memory availability becomes low. On Android this is done by the onTrimMemory
or onLowMemory
methods and on iOS by the didReceiveMemoryWarning
method. It is probably a good idea to respond to these methods, but it is not essential.
In your implementation of these methods, you should release any system resources that can be released without loss and also try to reduce the memory used by Lisp data. Since the GC sometimes temporarily requires more memory during an operation, it may be a bad idea to do a GC once you get the warning. The function reduce-memory is provided to reduce memory usage without requiring more memory temporarily. Note that gc-generation can do a much better job than reduce-memory in general, but it may require more memory temporarily.
Calling reduce-memory with argument nil
(the default) just releases any reserved memory that LispWorks has kept. It is fast and probably always a good idea. However, with argument nil
, reduce-memory does not perform any GC operation, which in principle could release more memory. Because a GC takes time, it is not obvious whether it worth the trouble.
Calling reduce-memory with 0 or 1 causes a GC of generation 0 or 1, which is probably fast enough (unless promotion of generation 1 is blocked and generation 1 grows), but will not typically release much memory.
Calling reduce-memory with 2 (or, equivalently, T), or even :aggressive
, can release much more memory, but takes more time, depending on the size of generation 2. Unless it is likely to release a large amount, it is probably not worth it. Thus, unless you know that generation 2 contains a lot of dead objects, you should only call reduce-memory with nil
, or maybe 0 or 1.
If you call reduce-memory with a non-nil argument, you should first clear any caches that you have kept, so their contents can be GCed.
To be able to reduce memory usage, reduce-memory needs reserved memory to perform a copying GC. Since reduce-memory never obtains more memory from the operating system, its effectiveness depends on the amount of reserved memory that it has when it is called. Moreover, any call to reduce-memory frees all of the reserved memory (once the GC has occurred if the argument is non-nil), so calling reduce-memory with non-nil shortly after a previous call with NIL is not going to be effective.
To see how much effect reduce-memory had on the memory, you can look at the output of room (last line with any argument you give it), or the result of room-values. To see how much time it takes, use the time macro or get-internal-real-time
.
GC of generations 0 and 1 should normally be fast enough that you do not need to worry about them. GC of generation 2, however, typically takes enough time to be noticeable, and if generation 2 is large (> 100 MB) can take more than a second. Thus you normally want to avoid GC of generation 2.
In a "nicely behaved" application, which we believe is true for most applications, generation 2 never needs to be collected. This is based on the assumption that a nicely behaved application starts with some initialization that allocates long-lived objects, but then enters a "work" phase, where it allocates only short lived objects, which die before they reach generation 2.
Even if there is some "generation leak", that is objects being promoted from generation 1 to 2 that die not long afterwards, the leak may be slow enough that it is not a problem. For example, if your application "leaks" on average 1 kB each second, it would take close to 3 hours of operation to leak 10 MB, which is still too small to worry about (the default minimum size of generation 2 before a GC is 64 MB). So you can usually ignore this kind of leakage and hope that any occasional delay of a second or two after running the application for many hours is not too annoying for the user (though if it only a "generation leak" , you can do better by blocking promotion). If you have a leakage of 100 kB per second, the delay would happen every few minutes, which may be too annoying.
To find if your applications leaks to generation 2, you should periodically log the size of either the whole application or of generation 2. The output of room is the most useful thing to log, but you can also use room-values or count-gen-num-allocation. If the application does leak to generation 2, you should determine if it is a real memory leak, which means that the application accumulates live objects, or just a generation leak, which means that objects live long enough to reach generation 2 and then die. To determine that, call (gc-generation T)
(or, equivalently, (clean-down)
), continue using the application for a while and then call it again. If the leak is just a "generation leak", then the size of generation 2 after (gc-generation t)
should stay (more or less) the same. If it grows, then you have a real memory leak.
If your application is "nicely behaved", generation 2, and hence the whole application, will initially grow, typically by few 10's of megabytes, and then will stay more or less fixed. The size of the whole application will always fluctuate, because generation 0 and 1 fluctuate, but generation 2 should be stable or grow slowly. If this is the case, you probably do not need to do anything further to control memory usage.
If generation 2 does grow, LispWorks will occasionally do a GC of generation 2, which takes a noticeable time (maybe a few seconds if generation 2 is few 100's of megabytes). If the leak is a real memory leak, it will also cause the application to grow indefinitely.
If the leak is a real memory leak, then the GC cannot do anything about it. One possibility is to make the application run for a limited time, for example by monitoring the size and quitting when it reaches some threshold. If quitting and restarting is possible without much loss, that may be a good solution. Most of applications probably want to avoid that though, in which case you will need to figure out what keeps objects alive and fix it. The functions sweep-all-objects, sweep-gen-num-objects and mobile-gc-sweep-objects can be used to check what kind of objects have accumulated. However, whatever keeps the objects is something in your application, and you will have to find it.
If the leak is only a "generation leak", then there are several ways to deal with it:
nil
.Once you have made this call, the automatic GC will never promote to generation 2 (explicit invocation of the GC ignores this setting). This is useful in situations where the "leaking" objects are live long enough to be promoted to generation 2, or the memory they use is small, so generation 1 does not grow too much. If there are many objects that live longer, then generation 1 will grow, and hence the GC of generation 1 will become slow. You should check if generation 1 grows, but it is probably OK if it remains at 20-30 megabytes allocated after a GC. You can try timing a GC of generation 1 by (time (gc-generation 1))
.
Note that you can switch promotion on and off as needed, so if you can identify phases in your application when allocation is not long-lived and phases when some is long-lived, then you can switch promotion on and off as appropriate.
Once you have called set-generation-2-gc-options with :minimal-size-for-gc t
, LispWorks will not automatically GC generation 2. It is then your responsibility to GC generation 2 at the appropriate time by calling (gc-generation 2)
.
As above, one of the options is to never GC generation 2, and just quit when the application reaches some size. Otherwise, you will need to identify appropriate points in time to perform the GC.
In an interactive application, you can have a "cleanup" option somewhere that invokes the GC, so the user can invoke it. You probably also want some indicator when the application has grown and needs a "cleanup".
For an interactive application, it may be a useful to do a GC when the application becomes backgrounded, but it is not obviously so. The method that is called by the operating system to indicate that the application has been backgrounded must return in a short time, so you probably need to invoke the GC from another thread. Also the operating system may not give much CPU to the application while it is in the background, and may even terminate background applications that take CPU. For example, the "App Programming Guide for iOS" says: "Apps that spend too much time executing in the background can be throttled back by the system or terminated." A GC of a 100-200 megabyte application should not take enough time to cause termination, but it depends both on the underlying system (hardware and OS) and the current state of it, so it is not that predictable. You certainly need to store anything that needs to be stored before doing a GC while in the background.
As long as memory is not constrained, the time it takes to GC generation 2 correlates to the amount alive after the GC rather than before the GC (because it uses copying, so does not touch dead objects). Therefore, if you have points in time in the execution when you know your application uses less memory then these are good points for doing a GC. That would be the case if your application builds a large data structure for a task (allocation of > 10 megabytes), and all this data becomes free when the task finishes. In this situation, it may be useful to perform a GC in the end of the task.
By calling set-generation-2-gc-options you can tune the frequency of GC of generation 2. You can either aim for infrequent GCs, which may be long but hopefully rare enough not to be too annoying, or aim for frequent GCs which are fast enough that they do not bother the user.
When the amount that is alive after a GC is almost always much less than the amount alive before, which is quite common, you can tell that to the GC by set-expected-allocation-in-generation-2-after-gc. This can significantly improve how well the GC copes when it fails to get as much memory as it asks for from the operating system. See set-expected-allocation-in-generation-2-after-gc for details.
The function make-current-allocation-permanent causes all the currently allocated objects to be made permanent, which means that the GC will not scan or free them in future (but it will still follow pointers from them). That is useful in the typical situation where the application starts with some initialization that creates long-lived objects. Using make-current-allocation-permanent at the end of the initialization makes all these objects permanent, and therefore reduces the time for GC of generation 2. If new objects in generation 2 after initialization are only the result of "generation leak" then the effect on time can be quite large.
RetroSearch is an open source project built by @garambo | Open a GitHub Issue
Search and Browse the WWW like it's 1997 | Search results from DuckDuckGo
HTML:
3.2
| Encoding:
UTF-8
| Version:
0.7.4