* New version of IRM New version of the IRM, updated to Medley. * moved to docs/medley-irm as discussed
57 lines
45 KiB
Plaintext
57 lines
45 KiB
Plaintext
INTERLISP-D REFERENCE MANUAL
|
||
PERFORMANCE ISSUES
|
||
|
||
"22"21. PERFORMANCE ISSUES
|
||
2
|
||
|
||
This chapter describes a number of areas that often contribute to performance problems in Medley programs. Many performance problems can be improved by optimizing the use of storage, since allocating and reclaiming large amounts of storage is expensive. Another tactic that can sometimes yield performance improvements is to change the use of variable bindings on the stack to reduce variable lookup time. There are a number of tools that can be used to determine which parts of a computation cause performance bottlenecks.
|
||
Storage Allocation and Garbage Collection
|
||
1
|
||
|
||
As an Medley application program runs, it creates data structures (allocated out of free storage space), manipulates them, and then discards them. If there were no way to reclaim this space, over time the Medley memory would fill up, and the computation would come to a halt. Actually, long before this could happen the system would probably become intolerably slow, due to ªdata fragmentation,º which occurs when the data currently in use are spread over many virtual memory pages, so that most of the computer time must be spent swapping disk pages into physical memory. The problem of fragmentation will occur in any situation where the virtual memory is significantly larger than the real physical memory. To reduce swapping, you want to keep the "working set" (the set of pages containing actively referenced data) as small as possible.
|
||
You can write programs that don't generate much ªgarbageº data, or which recycle data, but such programs tend to be complex and hard to debug. Spending effort writing such programs defeats the whole point of using a system with automatic storage allocation. An important part of any Lisp implementation is the ªgarbage collectorº that finds discarded data and reclaims its space.
|
||
There are several well-known approaches to garbage collection. One method is the traditional mark-and-sweep, which identifies ªgarbageº data by marking all accessible data structures, and then sweeping through the data spaces to find all unmarked objects (i.e., not referenced by any other object). This method is guaranteed to reclaim all garbage, but it takes time proportional to the number of allocated objects, which may be very large. Also, the time that a mark-and-sweep garbage collection takes is independent of the amount of garbage collected; it is possible to sweep through the whole virtual memory, and only recover a small amount of garbage.
|
||
For interactive applications, it is not acceptable to have long interruptions in a computation for to garbage collect. Medley solves this problem by using a reference-counting garbage collector. With this scheme, there is a table containing counts of how many times each object is referenced. This table is updated as pointers are created and discarded, incurring a small overhead distributed over the computation as a whole. (Note: References from the stack are not counted, but are handled separately at "sweep" time; thus the vast majority of data manipulations do not cause updates to this table.) At opportune moments, the garbage collector scans this table, and reclaims all objects that are no longer accessible (have a reference count of zero). The pause while objects are reclaimed is only the time for scanning the reference count tables (small) plus time proportional to the amount of garbage that has to be collected (typically less than a second). ªOpportuneº times occur when a certain number of cells have been allocated or when the system has been waiting for you to type something for long enough. The frequency of garbage collection is controlled by the functions and variables described below. For the best system performance, it is desirable to adjust these parameters for frequent, short garbage collections, which will not interrupt interactive applications for very long, and which will have the added benefit of reducing data fragmentation, keeping the working set small.
|
||
One problem with the Medley garbage collector is that not all garbage is guaranteed to be collected. Circular data structures, which point to themselves directly or indirectly, are never reclaimed, since their reference counts are always at least one. With time, this unreclaimable garbage may increase the working set to unacceptable levels. Some users have worked with the same Medley virtual memory for a very long time, but it is a good idea to occasionally save all of your functions in files, reinitialize Medley, and rebuild your system. Many users end their working day by issuing a command to rebuild their system and then leaving the machine to perform this task in their absence. If the system seems to be spending too much time swapping (an indication of fragmented working set), this procedure is definitely recommended.
|
||
Another limitation of the reference-counting garbage collector is that the table in which reference counts are maintained is of fixed size. For typical Lisp objects that are pointed to from exactly one place (e.g., the individual conses in a list), no burden is placed on this table, since objects whose reference count is 1 are not explicitly represented in the table. However, large, "rich" data structures, with many interconnections, backward links, cross references, etc, can contribute many entries to the reference count table. For example, if you created a data structure that functioned as a doubly-linked list, such a structure would contribute an entry (reference count 2) for each element.
|
||
When the reference count table fills up, the garbage collector can no longer maintain consistent reference counts, so it stops doing so altogether. At this point, a window appears on the screen with the following message, and the debugger is entered:
|
||
Internal garbage collector(GARBAGE% COLLECTOR NIL garbage% collector NIL (4) NIL) tables have overflowed, due
|
||
to too many pointers with reference count greater than 1.
|
||
*** The garbage collector is now disabled. ***
|
||
Save your work and reload as soon as possible.
|
||
[This message is slightly misleading, in that it should say "count not equal to 1". In the current implementation, the garbage collection of a large pointer array whose elements are not otherwise pointed to can place a special burden on the table, as each element's reference count simultaneously drops to zero and is thus added to the reference count table for the short period before the element is itself reclaimed.]
|
||
If you exit the debugger window (e.g., with the RETURN command), your computation can proceed; however, the garbage collector is no longer operating. Thus, your virtual memory will become cluttered with objects no longer accessible, and if you continue for long enough in the same virtual memory image you will eventually fill up the virtual memory backing store and grind to a halt.
|
||
|
||
Garbage collection in Medley is controlled by the following functions and variables:
|
||
(RECLAIM(RECLAIM (Function) NIL NIL ("22") 2)) [Function]
|
||
Initiates a garbage collection. Returns 0.
|
||
(RECLAIMMIN(RECLAIMMIN (Function) NIL NIL ("22") 2) N) [Function]
|
||
Sets the frequency of garbage collection. Interlisp keeps track of the number of cells of any type that have been allocated; when it reaches a given number, a garbage collection occurs. If N is non-NIL, this number is set to N. Returns the current setting of the number.
|
||
RECLAIMWAIT(RECLAIMWAIT (Variable) NIL NIL ("22") 2) [Variable]
|
||
Medley will invoke a RECLAIM if the system is idle and waiting for your input for RECLAIMWAIT seconds (currently set for 4 seconds).
|
||
(GCGAG(GCGAG (Function) NIL NIL ("22") 2) MESSAGE) [Function]
|
||
Sets the behavior that occurs while a garbage collection is taking place. If MESSAGE is non-NIL, the cursor is complemented during a RECLAIM; if MESSAGE = NIL, nothing happens. The value of GCGAG is its previous setting.
|
||
(GCTRP(GCGAG (Function) NIL NIL ("22") 2)) [Function]
|
||
Returns the number of cells until the next garbage collection, according to the RECLAIMMIN number.
|
||
The amount of storage allocated to different data types, how much of that storage is in use, and the amount of data fragmentation can be determined using the following function:
|
||
(STORAGE(STORAGE (Function) NIL NIL ("22") 2) TYPES PAGETHRESHOLD) [Function]
|
||
STORAGE prints out a summary, for each data type, of the amount of space allocated to the data type, and how much of that space is currently in use. If TYPES is non-NIL, STORAGE only lists statistics for the specified types. TYPES can be a symbol or a list of types. If PAGETHRESHOLD is non-NIL, then STORAGE only lists statistics for types that have at least PAGETHRESHOLD pages allocated to them.
|
||
STORAGE prints out a table with the column headings Type, Assigned, Free Items, In use, and Total alloc. Type is the name of the data type. Assigned is how much of your virtual memory is set aside for items of this type. Currently, memory is allocated in quanta of two pages (1024 bytes). The numbers under Assigned show the number of pages and the total number of items that fit on those pages. Free Items shows how many items are available to be allocated (using the create construct, Chapter 8); these constitute the "free list" for that data type. In use shows how many items of this type are currently in use, i.e., have pointers to them and hence have not been garbage collected. If this number is higher than your program seems to warrant, you may want to look for storage leaks. The sum of Free Items and In use is always the same as the total Assigned items. Total alloc is the total number of items of this type that have ever been allocated (see BOXCOUNT, in the Performance Measuring section below).
|
||
Note: The information about the number of items of type LISTP is only approximate, because list cells are allocated in a special way that precludes easy computation of the number of items per page.
|
||
Note: When a data type is redeclared, the data type name is reassigned. Pages which were assigned to instances of the old data type are labeled **DEALLOC**.
|
||
At the end of the table printout, STORAGE prints a "Data Spaces Summary" listing the number of pages allocated to the major data areas in the virtual address space: the space for fixed-length items (including datatypes), the space for variable-length items, and the space for symbols. Variable-length data types such as arrays have fixed-length "headers," which is why they also appear in the printout of fixed-length data types. Thus, the line printed for the BITMAP data type says how many bitmaps have been allocated, but the "assigned pages" column counts only the headers, not the space used by the variable-length part of the bitmap. This summary also lists "Remaining Pages" in relation to the largest possible virtual memory, not the size of the virtual memory backing file in use. This file may fill up, causing a STORAGE FULL error, long before the "Remaining Pages" numbers reach zero.
|
||
STORAGE also prints out information about the sizes of the entries on the variable-length data free list. The block sizes are broken down by the value of the variable STORAGE.ARRAYSIZES, initially (4 16 64 256 1024 4096 16384 NIL), which yields a printout of the form:
|
||
variable-datum free list:
|
||
le 4 26 items; 104 cells.
|
||
le 16 72 items; 783 cells.
|
||
le 64 36 items; 964 cells.
|
||
le 256 28 items; 3155 cells.
|
||
le 1024 3 items; 1175 cells.
|
||
le 4096 5 items; 8303 cells.
|
||
le 16384 3 items; 17067 cells.
|
||
others 1 items; 17559 cells.
|
||
This information can be useful in determining if the variable-length data space is fragmented. If most of the free space is composed of small items, then the allocator may not be able to find room for large items, and will extend the variable datum space. If this is extended too much, this could cause an ARRAYS FULL error, even if there is a lot of space left in little chunks.
|
||
(STORAGE.LEFT(STORAGE.LEFT (Function) NIL NIL ("22") 3)) [Function]
|
||
Provides a programmatic way of determining how much storage is left in the major data areas in the virtual address space. Returns a list of the form (MDSFREE MDSFRAC 8MBFRAC ATOMFREE ATOMFRAC), where the elements are interpreted as follows:
|
||
MDSFREE The number of free pages left in the main data space (which includes both fixed-length and variable-length data types).
|
||
MDSFRAC The fraction of the total possible main data space that is free.
|
||
8MBFRAC The fraction of the total main data space that is free, relative to eight megabytes.
|
||
This number is useful when using Medley on some early computers where the hardware limits the address space to eight megabytes. The function 32MBADDRESSABLE returns non-NIL if the currently running Medley system can use the full 32 megabyte address space.
|
||
ATOMFREE The number of free pages left in the symbol space.
|
||
ATOMFRAC The fraction of the total symbol space that is free.
|
||
Note: Another important space resource is the amount of the virtual memory backing file in use (see VMEMSIZE, Chapter 12). The system will crash if the virtual memory file is full, even if the address space is not exhausted.
|
||
Variable Bindings
|
||
1
|
||
|
||
Different implementations of Lisp use different methods of accessing free variables. The binding of variables occurs when a function or a PROG is entered. For example, if the function FOO has the definition (LAMBDA (A B) BODY), the variables A and B are bound so that any reference to A or B from BODY or any function called from BODY will refer to the arguments to the function FOO and not to the value of A or B from a higher level function. All variable names (symbols) have a top level value cell which is used if the variable has not been bound in any function. In discussions of variable access, it is useful to distinguish between three types of variable access: local, special and global. Local variable access is the use of a variable that is bound within the function from which it is used. Special variable access is the use of a variable that is bound by another function. Global variable access is the use of a variable that has not been bound in any function. We will often refer to a variable all of whose accesses are local as a "local variable." Similarly, a variable all of whose accesses are global we call a "global variable."
|
||
In a ªdeepº bound system, a variable is bound by saving on the stack the variable's name together with a value cell which contains that variable's new value. When a variable is accessed, its value is found by searching the stack for the most recent binding (occurrence) and retrieving the value stored there. If the variable is not found on the stack, the variable's top level value cell is used.
|
||
In a ªshallowº bound system, a variable is bound by saving on the stack the variable name and the variable's old value and putting the new value in the variable's top level value cell. When a variable is accessed, its value is always found in its top level value cell.
|
||
The deep binding scheme has one disadvantage: the amount of cpu time required to fetch the value of a variable depends on the stack distance between its use and its binding. The compiler can determine local variable accesses and compiles them as fetches directly from the stack. Thus this computation cost only arises in the use of variable not bound in the local frame ("free" variables). The process of finding the value of a free variable is called free variable lookup.
|
||
In a shallow bound system, the amount of cpu time required to fetch the value of a variable is constant regardless of whether the variable is local, special or global. The disadvantages of this scheme are that the actual binding of a variable takes longer (thus slowing down function call), the cells that contain the current in use values are spread throughout the space of all symbol value cells (thus increasing the working set size of functions) and context switching between processes requires unwinding and rewinding the stack (thus effectively prohibiting the use of context switching for many applications).
|
||
Medley uses deep binding, because of the working set considerations and the speed of context switching. The free variable lookup routine is microcoded, thus greatly reducing the search time. In benchmarks, the largest percentage of free variable lookup time was 20 percent of the total ellapsed time; the normal time was between 5 and 10 percent.
|
||
Because of the deep binding, you can sometimes significantly improve performance by declaring global variables. If a variable is declared global, the compiler will compile an access to that variable as a retrieval of its top level value, completely bypassing a stack search. This should be done only for variables that are never bound in functions, such as global databases and flags.
|
||
Global variable declarations should be done using the GLOBALVARS file manager command (Chapter 17). Its form is (GLOBALVARS VAR1 ... VARN).
|
||
Another way of improving performance is to declare variables as local within a function. Normally, all variables bound within a function have their names put on the stack, and these names are scanned during free variable lookup. If a variable is declared to be local within a function, its name is not put on the stack, so it is not scanned during free variable lookup, which may increase the speed of lookups. The compiler can also make some other optimizations if a variable is known to be local to a function.
|
||
A variable may be declared as local within a function by including the form (DECLARE (LOCALVARS VAR1 ... VARN)) following the argument list in the definition of the function. Local variable declarations only effect the compilation of a function. Interpreted functions put all of their variable names on the stack, regardless of any declarations.
|
||
Performance Measuring
|
||
1
|
||
|
||
This section describes functions that gather and display statistics abÿÿ |