This section will describe each Inuse report option in
detail.
Clicking on the Hist button or selecting Heap History
from the Reports menu brings up the Heap History window. If your program
is running when you open this reports, you will be able to monitor the
amount of memory allocated by the program as it executes. If the program
has ended its run, you can see how much memory was allocated across the
history of the run.
Figure 3. The Heap History window
The black area indicates the total size of the heap. The red, yellow,
and blue areas constitute the make-up of the heap. The red area represents
the amount of memory allocated by the program. The yellow area represents
the amount of "overhead" associated with the memory (but which
cannot be used by the application). The blue area represents the amount of
leaked memory.
To change the sampling rate, that is, how much time passes before the
graph is updated, press the Sampl button or select Sampling
from the Options menu.
The "block frequency" report will show you what sizes of
blocks are typically being allocated by your program. If you are allocating
many small blocks, you may want to switch to a different memory allocation
scheme which groups many small allocations into several larger ones. A
"block frequency" graph might look like the one in
Figure 4.
Figure 4. A "Block Frequency" graph
The Block Frequency graph groups together blocks that are similarly
sized. Each bin (column) in the graph represents the number of blocks
in a particular size range. Clicking on a bin will show you the number
and size range of blocks contained in that bin.
The right-most bin includes all blocks above a certain size. If this
bin is very high, click to see the size range covered by that bin. If the
range is fairly wide, you can rescale the graph to include more bins by
pressing the Bins button or selecting Bins from the Options
menu. Entering a number higher than the number currently shown
should narrow the size range for each bin. Likewise, entering a
number that is lower than the number currently shown should
increase the size range included in each bin. If all block sizes
are currently represented on the graph, increasing the bin number will
have no effect.
You can alternate between a linear or logarithmic scale by pressing
the Yscale buttons. Pressing the Xscale button will toggle
the x-axis between linear and logarithmic scales. Pressing the
Yscale button will toggle the y-axis between linear and
logarithmic scales. Selecting Horiz.log/lin or Vert.log/lin
from the Options menu will also toggle the x- or y-axis.
The Heap Layout report shows the status of blocks in the program's
heap. Blocks are either free, allocated, overhead, or leaked.
Figure 5. The "heap layout" report
Click on a block to see its address, size and status. This information
is shown in the lower right corner of the display screen.
If the block is allocated or leaked, a Memory Information window will
also appear. The memory information window contains the block's id,
address, stack size, and stack trace.
Figure 6. Memory Information window
You can scan through different areas of the heap layout by pressing the
Fast left (Fleft), Fast Right (Fright) and left and
right buttons on the Heap Layout toolbar. You can also zoom in (+)
and out (-) of the layout by pressing the zoom buttons. (These
options are also available in the Controls menu).
The Time Layout report shows how memory blocks are allocated across the
run of the program. As each block is allocated, it is added to the end of
the display. As blocks are freed, they are marked green. (See Figure 7.)
Figure 7. The "Time Layout" screen
From this display, you can watch how memory is allocated over the run of
your program. As you see the patterns, in which memory blocks are allocated
and freed over time, you can better optimize your program's use of memory.
Memory leaks are also shown on this display.
You can scan through the run by pressing the Fast Left (Fleft),
Fast Right (Fright) and left and right buttons on the Time Layout
toolbar. You can also zoom in (+) and out (-) of the layout by pressing
the zoom buttons. (These options are also available in the
Controls menu.)
The "usage summary" report will show you how much memory you
are using and how often calls have been made to each category of memory
allocation functions malloc, realloc, and free .
Note that the malloc category includes all functions which
allocate dynamic memory, for example calloc, memalign , and
XtMalloc . Similarly, the realloc category
includes all functions which relocate or resize dynamic memory blocks.
The free category includes all functions which free dynamic
memory. You can calculate the number of blocks currently allocated by
subtracting the number of frees from the number of
malloc 's. A typical "usage summary" graph might
look like the following one in Figure 8.
Figure 8. A "usage summary" graph
The number given by "Alloc" is the total number of bytes
allocated dynamically by your program. The number given by "Heap"
is the total number of bytes currently allocated by the system to your
program's heap.
The number given with "Number of calls" and "Memory
in bytes" is simply the extreme value on the x-axis for each graph.
The limit of each graph will change as Inuse updates the display
with more memory allocation function calls.
Query is a powerful tool that makes it easier for you to understand
how memory is being used by your program. With Query, you can find out
exactly how much memory is being allocated to blocks of a particular
size or location; how much memory is being allocated from a particular
path; find out the stack traces of blocks of a particular size or
location; and much, much more.
By creating queries, you will be creating a "model" of your
program's memory use. You will be able to "look" at it from
different angles and approaches, learning more and more with each
successive report. As you come to understand how and where your program
uses memory, you will be able to better optimize you program's memory
use.
For example, if the Block Frequency report shows that your program is
using many small allocations (creating a lot of memory overhead), you may
to know exactly how much memory is being allocated to these small blocks.
You will also want to know which part of the code is responsible for
creating them.
To find out how your program is distributing memory across block sizes,
you can run a query that shows how much memory is being allocated to each
size block.
If you find that your program is distributing memory across block sizes,
you can then run a query that gives you the block ids and stack traces
responsible for creating these small blocks.
Armed with this information, you will be able to make simple adjustments
to your code that will result in a more effective use of memory.
You can run queries on any combination of block id, block size, and
stack trace. These queries can be flexible or as restrictive as you choose,
as you set the parameters (see "Editing a Query").
Running queries does not affect your program or its operation. By
running different queries and trying different approaches, you will soon
see how valuable and informative these reports can be.
Figure 9. The Query Manager screen
Pressing Query opens the Query Manager screen. From here you
can:
- Press New to start a new query.
- Press Load to open a saved query.
- Press Delete to delete the current query.
- Press Save to save the current query for later use.
- Press Eval to evaluate the current query.
When you open a query, its name will appear in the Query Manager window.
If you pressed New, the query will be named NoName .
Pressing the Edit button on the Query Manager screen will let
you edit the currently selected query.
Figure 10. The Query Editor
The Query Editor window lets you:
- Change the name of a query.
- Set the lower and upper ranges for the block ids you want to
isolate.
- Set the lower and upper ranges for the block sizes you want to
isolate.
- Enter an expression to isolate certain stack traces.
- Choose whether to receive a complete report (including block id,
block size, and stack trace data) or just a summary of block size
and/or stack trace information.
The default values for a query are 0 for the lower and
upper block id and block size ranges and no expressions in the stack
trace area. This will produce the widest query, listing all memory blocks
allocated by your program.
You can filter a query to isolate particular block ids, block sizes,
and/or stack traces. Just enter the values you want to filter for and
"check" the Filter by box.
The Sum by option provides a useful summary of block size and
stack trace data. Use it in combination with the Filter by option
or on its own to get a breakdown of blocks by size and/or stack trace.
For example, let's say you want to learn more about blocks that are
between 60 and 70 bytes in size. In the Block Size Filter by box
and press OK. Running this query will return block ids and stack
traces for all blocks allocated by your program that are sized between
60 and 70 bytes.
Checking the Block Size Sum by box for this query will return a
list showing how much memory (in bytes) is being allocated to each block
size in that range.
You can enter filters for any or all three areas in a single query.
This can help you find out the exact stack trace(s) responsible for the
largest block allocations by your program, for example, or conversely,
to find our the size and location of memory allocated from a particular
stack trace path.
Once you have edited your query, pressing OK returns you to
the Query Manager.
To run the query, press the Eval button or select Evaluate
Query from the Commands menu. Query results will appear in a Memory
Information window.
Figure 11. Query Results
By default, Inuse will animate every dynamic memory action that
is made by your program. This has the advantage that the data displayed
reflects the true situation inside your program's memory, but the
disadvantage of hiding low level detail.
If you are only interested in a portion of your application, you can
make some simple, unobtrusive changes to the original source code so
that the animation shows only those data blocks which are active within
a particular memory range.
When you compile programs with the insure command,
the pre-processor symbol __INSURE__ is automatically
defined. This allows you to conditionally insert calls to the function
which controls the animation process:
void _Inuse_display_values(int status);
Suppose, for example, that you are only interested in events occurring
during the execution of a hypothetical function
malloc_a_lot . To restrict data collection to this
function, you need to add the following lines to the beginning of your
program (i.e. at the top of main ):
#ifdef __INSURE__
_Inuse_display_values(0);
#endif
When your program begins, the animation is normally turned on - this
call will turn it off. We next modify malloc_a_lot
to turn the animation back on for the duration of the function. To do
this, we modify the function malloc_a_lot from
malloc_a_lot() {
... code ...
}
to
malloc_a_lot() {
#ifdef __INSURE__
_Inuse_display_values(1);
#endif
... code ...
#ifdef __INSURE__
_Inuse_display_values(0);
#endif
}
Now when you compile and run your program, it will only display data
between the calls to _Inuse_display_values .
It's worth noting that you can call this routine from a debugger
to control the profiling without recompiling your code at all.
An additional option is turning just the Inuse
GUI on and off at will during the execution of your program, which allows
more flexibility in exploiting Inuse's power without sacrificing
speed.
You can now turn the GUI on and off using the command:
_Inuse_enable(boolean)
Calling _Inuse_enable with true (1) turns
the GUI on, while calling with false (0) turns the GUI off.
This feature allows you to avoid waiting for many thousands of
mallocs during the initialization of your code, and
then still be able to turn Inuse on without halting the program's
execution or losing information.
To make use of this feature, you cannot have the inuse on
option set in your .psrc file. With that option set,
Inuse will automatically start working when you execute your
program.
This option is only available if you are using the Insure++
version of malloc . If you are using Insure++
to monitor your own malloc (as explained in the
Insure++ Users Guide and in
the next section), this command will not be available.
Internally, there are two important components to the way that
Inuse operates
- The graphical user interface.
- A version of the memory allocation functions which transmits
data to the user interface.
Communication between the two pieces is achieved with standard UNIX
sockets using a protocol defined by Inuse. If you have your own
memory allocation scheme, you can still use Inuse by adding calls
to your code which send their results to the user interface.
For example, if you have a routine which performs a similar function
to the regular malloc system call, you can add to
it a call to the following function:
_Inuse_malloc(void *ptr, size_t length);
which causes the display system to indicate an allocated block of the
indicated size, at the address given.
A full list of the available functions and their usage is given in the
manual page which can be found on-line as well as in this manual
(Manual Pages).
If you are instrumenting your own memory allocation scheme, be sure
to disable the default by having the option
"malloc_replace off " in your
.psrc file when you link your program.
By default, programs which use Inuse make calls to a special
library which contains implementations of various memory allocation
functions, such as malloc , realloc ,
calloc , and free . As a result,
the data that you see displayed in the user interface does not match
exactly the behavior of the native runtime system on the target machine,
but it is representative of the true behavior.
An alternative approach is to have your own application call different
functions which
- call the genuine memory allocation routines on your system, and
- send the results to the user interface for display.
To achieve this, use the following steps:
This process will create an executable which uses the normal memory
allocation package, but which also includes calls to display the
results in Inuse's graphical user interface.
Note that this process will only track calls to memory allocation
routines that occur in user code which contains the five #define
lines given above. It will not track calls made in system or 3rd party
libraries which have not been recompiled.
|