|
|
| (44 intermediate revisions by 3 users not shown) |
| Line 1: |
Line 1: |
| Mozilla code has infrastructure that lets different parts of the code report on their memory usage. This is most obviously used in about:memory and telemetry. This document describes things that you should know when writing a memory reporter.
| | The contents of this page have moved to [https://developer.mozilla.org/en-US/docs/Mozilla/Performance/Memory_reporting MDN]. |
| | |
| == Normal and Multi-reporters ==
| |
| | |
| There are two basic ways to implement a memory reporter: as a normal reporter, or a multi-reporter.
| |
| | |
| Normal reporters make a single memory measurement. Each measurement involves several values, including:
| |
| | |
| * a path (which identifies the reporter);
| |
| * an amount (the most important thing);
| |
| * a unit (most commonly bytes, but sometimes a unitless count or percentage);
| |
| * a description of what is measured.
| |
| | |
| See the [https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIMemoryReporter nsIMemoryReporter documentation] and [http://mxr.mozilla.org/mozilla-central/source/xpcom/base/nsIMemoryReporter.idl nsIMemoryReporter.idl] for full details. The <tt>NS_MEMORY_REPORTER_IMPLEMENT_HELPER</tt> macro makes simple memory reporters easy to write.
| |
| | |
| Multi-reporters make multiple memory measurements. A multi-reporter implements a <tt>collectReports</tt> function which takes a <tt>nsIMemoryMultiReporterCallback</tt> argument; for each measurement the multi-reporter must call the callback, passing in as arguments the same values that are provided for a normal reporter (path, amount, unit, etc). See the [https://developer.mozilla.org/en/XPCOM_Interface_Reference/nsIMemoryMultiReporter nsIMemoryMultiReporter documentation] and [http://mxr.mozilla.org/mozilla-central/source/xpcom/base/nsIMemoryReporter.idl nsIMemoryReporter.idl] for full details.
| |
| | |
| Normal reporters are best used if you have a small, fixed number of measurements to make. Multi-reporters are best used if you have a large and/or non-fixed number of measurements to make (especially if you have one or more measurements per window/tab/compartment/whatever). One disadvantage of multi-reporters is that you can't extract a single measurement from a multi-reporter easily, which means you can't use them from telemetry.
| |
| | |
| == Making Measurements ==
| |
| | |
| nsIMemoryReporter and nsIMemoryMultiReporter provide the high-level interface for a memory reporter, but the heart of a memory reporter is the measurement of the "amount".
| |
| | |
| === Two Ways to Measure ===
| |
| | |
| Memory reporters can be divided into the two following kinds.
| |
| | |
| * ''Traversal-based'' reporters traverse one or more data structures and measure the size of all the allocated blocks in the data structure.
| |
| | |
| * ''Counter-based'' reporters maintain a counter that is incremented on each relevant allocation and decremented on each relevant deallocation.
| |
| | |
| Traversal-based reporters are preferable, for the following reasons.
| |
| | |
| * They are less error-prone (we've had multiple bugs in the past with counter-based reporters).
| |
| | |
| * The cost of reporting isn't incurred unless the memory reporter is queried.
| |
| | |
| * They provide more information to DMD, which is a tool that helps keep about:memory's "heap-unclassified" number low (see below for more details).
| |
| | |
| Sometimes counter-based reporters are unavoidable, particularly when writing memory reporters for third-party code that shouldn't be modified.
| |
| | |
| === An Example ===
| |
| | |
| Imagine a simple string class with the following data fields:
| |
| | |
| class MyString {
| |
| private:
| |
| char *mBuffer; // heap-allocated
| |
| size_t mLen;
| |
|
| |
| // ... methods ...
| |
| }
| |
| | |
| Here are what the measurement functions (yes, functions) should look like for this class.
| |
| | |
| size_t MyString::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf) const {
| |
| return aMallocSizeOf(mBuffer, mLen * sizeof(char));
| |
| }
| |
| size_t MyString::SizeOfIncludingThis(nsMallocSizeOfFun aMallocSizeOf) const {
| |
| return aMallocSizeOf(this, sizeof(MyString)) +
| |
| SizeOfExcludingThis(aMallocSizeOf);
| |
| }
| |
| | |
| [http://mxr.mozilla.org/mozilla-central/source/xpcom/base/nscore.h nscore.h] defines <tt>nsMallocSizeOfFun</tt> as follows:
| |
| | |
| typedef size_t(*nsMallocSizeOfFun)(const void *p, size_t computedSize);
| |
| | |
| (The JS engine has a synonym called <tt>JSMallocSizeOfFun</tt>.)
| |
| | |
| All this is more complex than you'd expect, but the above functions have the following crucial properties.
| |
| | |
| * They let you measure the <tt>MyString</tt> object itself or not as necessary. This is important because sometimes an object might be embedded in another object that is measured separately. And the names make it clear exactly what is being measured.
| |
| | |
| * On platforms that allow it, they count the actual memory in use, including slop bytes caused by the heap allocator rounding up request sizes (a.k.a. internal fragmentation). If slop bytes aren't measured they'll end up in about:memory's <tt>heap-unclassified</tt> entry, which is bad.
| |
| | |
| * They provide a computed size as well, which serves two purposes.
| |
| ** On platforms that cannot measure actual memory in use, this serves as a fallback.
| |
| ** On other platforms, the computed and actual sizes can be compared and if they differ sufficiently an assertion fails. This sanity checking has caught multiple bugs in existing memory reporters.
| |
| | |
| * They are flexible and integrate well with DMD. The <tt>aMallocSizeOf</tt> parameter allows <tt>nsMallocSizeOfFun</tt> functions with DMD-specific hooks to be passed in when they are used by memory reporters, but functions without such hooks (such as <tt>moz_malloc_size_of</tt>) can also be passed in when they are used in other circumstances.
| |
| | |
| Some other things to note:
| |
| | |
| * Please use <tt>size_t</tt> for the sizes in functions like this. Although <tt>nsIMemoryReporter</tt> uses <tt>PRInt64</tt>, this is only because you can't use <tt>size_t</tt> in IDL. So it's best to mostly use <tt>size_t</tt> and convert to <tt>PRInt64</tt> as late as possible.
| |
| | |
| * You don't always need both <tt>SizeOfExcludingThis</tt> and <tt>SizeOfIncludingThis</tt>. Implement one or both as needed. If you have both, <tt>SizeOfExcludingThis</tt> is the interesting one; <tt>SizeOfIncludingThis</tt> always has the same basic form.
| |
| | |
| And here's how you'd write a memory reporter if there was a single global MyString object.
| |
| | |
| NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN(MyStringMallocSizeOf, "mystring")
| |
|
| |
| MyString *gMyString;
| |
|
| |
| static PRInt64 GetMyStringSize()
| |
| {
| |
| return gMyString->SizeOfIncludingThis(MyStringMallocSizeOf);
| |
|
| |
| // BTW: If gMyString wasn't a pointer, you'd use
| |
| // |gMyString.SizeOfExcludingThis(MyStringMallocSizeOf)| instead.
| |
| }
| |
|
| |
| NS_MEMORY_REPORTER_IMPLEMENT(MyString,
| |
| "mystring",
| |
| KIND_HEAP,
| |
| UNITS_BYTES,
| |
| GetMyStringSize,
| |
| "Memory used for MyString.");
| |
| | |
| Note the use of the macro <tt>NS_MEMORY_REPORTER_MALLOC_SIZEOF_FUN</tt> to create <tt>MyStringMallocSizeOf</tt>, which is a function of type <tt>nsMallocSizeOfFun</tt> that is specific to this memory reporter (and will be identified as such in DMD's output).
| |
| | |
| === Other Considerations ===
| |
| | |
| A number of the existing basic data structures already have <tt>SizeOf{In,Ex}cludingThis</tt> functions, e.g. <tt>nsTArray</tt> and <tt>nsTHashtable</tt>. <tt>nsTHashtable</tt>'s functions take an extra argument which is a pointer to a function that measures the memory usage of any structures pointed to by entries in the hash table.
| |
| | |
| Sometimes you may need variations on the above forms. For example, if you have a function that just measures one member <tt>Foo</tt> of an object, it might be called <tt>SizeOfFoo</tt>. Try to make the names descriptive enough that it's clear what's being measured.
| |
| | |
| Sometimes you might want to split the measurements of an object into two or more numbers, e.g. because you want to show them separately in about:memory. In which case just return the multiple values by reference, like this:
| |
| | |
| void FooBar::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf,
| |
| size_t *foo, size_t* bar) const;
| |
| | |
| Alternatively, you could create a struct:
| |
| | |
| struct FooBarStats {
| |
| size_t foo;
| |
| size_t bar;
| |
| }
| |
| void FooBar::SizeOfExcludingThis(nsMallocSizeOfFun aMallocSizeOf,
| |
| FooBarStats *stats) const;
| |
| | |
| You could even put the <tt>nsMallocSizeOfFun</tt> in <tt>FooBarStats</tt> to reduce the number of arguments.
| |
| | |
| Sometimes it's difficult and/or not worth measuring every member of an object of in a <tt>Foo::SizeOfExcludingThis</tt> method. This is ok, but it's worth adding a comment explaining this to the method.
| |
| | |
| It's important that no memory is measured twice; this can lead to very strange results in about:memory. Avoiding double measurement is easy in tree-like structures. In graph-like structures (where an object might be pointed to by more than one other object) it gets more difficult, and might even require some way to mark objects that have been counted (and then a way to unmark them once the measurement is complete).
| |
| | |
| == DMD ==
| |
| | |
| DMD is a tool with two purposes.
| |
| | |
| * It identifies places in the code that allocate memory but do not have memory reporters for that memory. This helps drive about:memory's <tt>heap-unclassified</tt> number down.
| |
| | |
| * It detects certain defects in existing heap memory reporters: if non-heap memory is reported, or if a heap block is partially reported or double-reported.
| |
| | |
| DMD is absolutely crucial; these things cannot be done without it. That's why the integration of DMD and memory reporters is so important and thus mentioned multiple times above.
| |
| | |
| See [[Performance/MemShrink/DMD]] for instructions on how to run DMD.
| |