home *** CD-ROM | disk | FTP | other *** search
/ DOS/V Power Report 2004 March / VPR0403.ISO / talks / 127 / paper.dkb < prev    next >
Encoding:
Extensible Markup Language  |  2003-09-18  |  15.8 KB  |  332 lines

  1. <?xml version="1.0" encoding="ISO-8859-1"?>
  2.  
  3. <article id="paper-999">
  4.   <articleinfo>
  5.     <title>Be More Rational: Open Source Memory Profiling and Performance Tuning</title>
  6.     <author>
  7.       <firstname>Matthias Kalle</firstname>
  8.       <surname>Dalheimer</surname>
  9.     </author>
  10.     <affiliation>
  11.       <jobtitle>President & CEO</jobtitle>
  12.       <orgname>Klar舁vdalens Datakonsult AB</orgname>
  13.     </affiliation>
  14.     <copyright>
  15.       <year>2003</year>
  16.       <holder>Matthias Kalle Dalheimer</holder>
  17.     </copyright>
  18.   </articleinfo>
  19.  
  20. <section>
  21.   <title>Open Source Memory Profiling and Performance Tuning</title>
  22.   
  23.     <section>
  24.       <title>Motivation</title>
  25.       <para>Tool support for programmers has come a long way, from the
  26.         first mnemonic assemblers (and text editors even!) via
  27.         high-level language compilers, debuggers, cross-referencers to
  28.         today's complex IDEs and reverse engineering systems.</para>
  29.       <para>In general, programmers can choose as many of these tools
  30.         as they want (or can afford); there are numerous programmers
  31.         who deliberately or for want of knowledge of more advanced
  32.         tools use nothing but a text editor and a compiler. But there
  33.         are two types of problems in software that are close to
  34.         impossible to fix without advanced tool support:</para>
  35.       <itemizedlist>
  36.         <listitem>
  37.           <para>Memory corruption problems</para>
  38.         </listitem>
  39.         <listitem>
  40.           <para>Performance problems</para>
  41.         </listitem>
  42.       </itemizedlist>
  43.       <para>We will look into how open source tools can help you fix
  44.         these problems in your software.</para>
  45.     </section>
  46.  
  47.     <section>
  48.       <title>Fixing Memory Corruption Problems</title>
  49.       <para>Memory corruption problems are among those types of
  50.         software problems that are most difficult to fix, but the
  51.         right tools can make this ugly and painstaking task simpler
  52.         and more efficient.</para>
  53.       <para>As an example, take the following faulty C++ code snippet:</para>
  54.       <programlisting>
  55. class MyClass
  56. {
  57. public:
  58.     MyClass();
  59.     ~MyClass();
  60.  
  61.     // ... more methods
  62.  
  63. private:
  64.     MyOtherClass* pOtherClass;
  65.     // more instance variables
  66. }
  67.  
  68.  
  69. MyClass::MyClass()
  70. {
  71.     pOtherClass = new MyOtherClass();
  72.     // more initialization
  73. }
  74.  
  75.  
  76. MyClass::~MyClass()
  77. {
  78.     delete pOtherClass;
  79.     // more destruction
  80. }
  81. </programlisting>
  82.       <para>This looks innocent enough, but as soon as you get
  83.         assignments or copy construction into the picture, hell will
  84.         break lose:</para>
  85.       <programlisting>
  86. void myFunc()
  87. {
  88.     MyClass myClass;
  89.     MyClass myClass2 = myClass;
  90.     // more stuff...
  91. }
  92. </programlisting>
  93.       <para>What happens here is that on exit from
  94.         <literal>myFunc()</literal>, the destructors of both
  95.         <literal>myClass</literal> and <literal>myClass2</literal>
  96.         will be invoked, but since both contain a pointer to the same
  97.         <literal>MyOtherClass</literal> object, this object will be
  98.         destroyed once (the reason for this being that the compiler
  99.         will generate a bitwise assignment operator if you fail to
  100.         provide one; see any good C++ book about this problem which is
  101.         often called the <emphasis>Pointer Aliasing
  102.           Problem</emphasis>).</para>
  103.       <para>Why is this such a bad problem? If you get a crash, you
  104.         can fire up the debugger and just find the place where it
  105.         crashed, can't you?</para>
  106.       <para>No, you can't. The problem is that these memory
  107.         corruptions usually do not lead to a crash immediately, but
  108.         rather a lot (computer-wise) later, when the stack trace will
  109.         look completely different. Often, the crashes occur when a
  110.         (typically completely unrelated object) is allocated.</para>
  111.       <para>So how do you find those errors? Careful code inspection
  112.         can achieve that, of course, but we all know how difficult it
  113.         is to spot errors in your own code. Static automated code
  114.         inspection tools (that operate on the source code) can do a
  115.         lot, but these types of defecs can be fairly hidden and
  116.         therefore would likely not be uncovered.</para>
  117.       <para>This is where runtime memory profiling tools come into
  118.         play. These control your application while it runs and log all
  119.         memory allocations and accesses. Therefore, it can immediately
  120.         inform you if a faulty memory operation takes place and log the
  121.         location in your program where it occurs, even if there is no
  122.         crash (yet).</para>
  123.       <para>Apparently, tools like this are immensely helpful, but
  124.         also very difficult to write. They have therefore in the past
  125.         mostly been limited to very expensive closed source
  126.         tools. Here are some of the tools together with the technology
  127.         they use:</para>
  128.       <variablelist>
  129.         <varlistentry>
  130.           <term>Rational Purify</term>
  131.           <listitem>
  132.             <para>Rational Purify uses a patented technique called
  133.               "Object Code Insertion" which works by changing the
  134.               executable and library files on-the-fly. The memory
  135.               audit code is inserted directly into these files, and
  136.               the "instrumented" libraries are then cached for later
  137.               use. This means that the executables and libraries need
  138.               to be reinstrumented after each change to the code,
  139.               which is time-consuming, but not very cumbersome, since
  140.               Purify detects this automatically and will perform the
  141.               necessary instrumentations in a "make-like"
  142.               fashion.</para>
  143.             <para>Purify is fairly thorough at detecting memory
  144.               problems and also comes with a very good viewer that
  145.               makes it a lot easier to sift through the generated
  146.               reports (which can be very lenghty).</para>
  147.             <para>Purify's central drawback (besides the high price
  148.               tag) is platform availability; only the Windows version
  149.               is really maintained; there are versions for commercial
  150.               Unix systems that seem to be stuck with very old
  151.               versions. No Linux version.</para>
  152.           </listitem>
  153.         </varlistentry>
  154.         <varlistentry>
  155.           <term>Insure++</term>
  156.           <listitem>
  157.             <para>Insure++ by Parasoft works by replacing the
  158.               preprocessing stage with a special preprocessor that
  159.               inserts additional calls to Insure++'s memory audit
  160.               functions. This has the drawback that upon code changes,
  161.               the code needs to be both recompiled and relinked with
  162.               Insure++ which is both time-consuming and
  163.               cumbersome. Unlike with Purify, you cannot run Insure++
  164.               on applications to which you don't have the source code,
  165.               and to get a full coverage, you need the source code all
  166.               libraries involved (Insure++ ships with replacements for
  167.               standard libraries that already contain the
  168.               instrumentation). This scheme also makes Insure++ fairly
  169.               fragile to system changes and updates.</para>
  170.             <para>Insure++ is very expensive as well, but available on
  171.               a large number of platforms, including Linux/Intel.</para>
  172.           </listitem>
  173.         </varlistentry>
  174.         <varlistentry>
  175.           <term>Replacement libraries that replace
  176.             <literal>malloc()</literal> and
  177.             <literal>free()</literal>.</term>
  178.           <listitem>
  179.             <para>There are a number of libraries (both open source
  180.               and proprietary) that replace the memory
  181.               allocation/deallocation calls
  182.               <literal>malloc()</literal> and
  183.               <literal>free()</literal> with versions of their own
  184.               that contain additional tracking and housekeeping. By
  185.               using the <literal>LD_PRELOAD</literal> mechanism, it is
  186.               ensured that these implementations are used instead of the
  187.               standard ones.</para>
  188.             <para>While these libraries can detect "double deletes"
  189.               (like in the example above), they cannot detect other
  190.               types of memory problems, like accessing the tenth
  191.               element in an array of five elements.</para>
  192.           </listitem>
  193.         </varlistentry>
  194.         <varlistentry>
  195.           <term>Overloading <literal>operator new</literal> and
  196.             <literal>operator delete</literal></term>
  197.           <listitem>
  198.             <para>C++ allows to overload the two standard operators
  199.               <literal>operator new</literal> and <literal>operator
  200.                 delete</literal> with versions of your own. There are
  201.               tools that do just this, and the overloaded versions of
  202.               course contain additional tracking and housekeeping. The
  203.               disadvantage of this is that inclusion of the header file
  204.               with the overloaded version is necessary in each and
  205.               everywhere source file, so it is an intrusive
  206.               technique that requires you to change your code
  207.               first. Also, like with the
  208.               <literal>malloc()</literal>/<literal>free()</literal>
  209.               replacement, this technique can only detect a certain
  210.               type of memory errors.</para>
  211.           </listitem>
  212.         </varlistentry>
  213.         <varlistentry>
  214.           <term>Valgrind</term>
  215.           <listitem>
  216.             <para>Valgrind by Julian Seward is a fairly new addition
  217.               to this list, and has quickly become the memory
  218.               debugging tool of choice for many open source
  219.               developers. Valgrind is open source itself and has a
  220.               comparable functionality to the expensive proprietary
  221.               tools. Valgrind works by implementing a virtual machine
  222.               in which the application is run. This virtual machine
  223.               tracks all memory accesses and can thus notify you about
  224.               memory corruption problems on the spot.</para>
  225.             <para>The big advantage of this technique is that you can
  226.               run applications under Valgrind's control at any time,
  227.               without any preparation. We will have more to say about
  228.               Valgrind below.</para>
  229.           </listitem>
  230.         </varlistentry>
  231.       </variablelist>
  232.       <section>
  233.         <title>Valgrind</title>
  234.         <para>As already mentioned, Valgrind is an excellent tool for
  235.           debugging memory corruption problems. You simply prepend
  236.           <literal>valgrind</literal> to the application command line,
  237.           and your application will run as usual (albeit a lot
  238.           slower).</para>
  239.         <para>Here is an example of Valgrind output:</para>
  240.         <programlisting>
  241. ==22173== Memcheck, a.k.a. Valgrind, a memory error detector for x86-linux.
  242. ==22173== Copyright (C) 2002, and GNU GPL'd, by Julian Seward.
  243. ==22173== Using valgrind-1.9.3, a program instrumentation system for x86-linux.
  244. ==22173== Copyright (C) 2000-2002, and GNU GPL'd, by Julian Seward.
  245. ==22173== Estimated CPU clock rate is 502 MHz
  246. ==22173== For more details, rerun with: -v
  247. ==22173==
  248. ==22173== Invalid free() / delete / delete[]
  249. ==22173==    at 0x4016898F: free (/home/kalle/valgrind-1.9.3/coregrind/vg_clientfuncs.c:182)
  250. ==22173==    by 0x80484BB: main (/home/kalle/valgrind-1.9.3/memcheck/tests/doublefree.c:10)
  251. ==22173==    by 0x402439EC: __libc_start_main (in /lib/libc.so.6)
  252. ==22173==    by 0x80483B0: (within /home/kalle/valgrind-1.9.3/memcheck/tests/doublefree)
  253. ==22173==    Address 0x40F53024 is 0 bytes inside a block of size 177 free'd
  254. ==22173==    at 0x4016898F: free (/home/kalle/valgrind-1.9.3/coregrind/vg_clientfuncs.c:182)
  255. ==22173==    by 0x80484BB: main (/home/kalle/valgrind-1.9.3/memcheck/tests/doublefree.c:10)
  256. ==22173==    by 0x402439EC: __libc_start_main (in /lib/libc.so.6)
  257. ==22173==    by 0x80483B0: (within /home/kalle/valgrind-1.9.3/memcheck/tests/doublefree)
  258. ==22173==
  259. ==22173== ERROR SUMMARY: 1 errors from 1 contexts (suppressed: 0 from 0)
  260. ==22173== malloc/free: in use at exit: 0 bytes in 0 blocks.
  261. ==22173== malloc/free: 1 allocs, 2 frees, 177 bytes allocated.
  262. ==22173== For a detailed leak analysis,  rerun with: --leak-check=yes
  263. ==22173== For counts of detected errors, rerun with: -v
  264. </programlisting>
  265.         <para>This comes from a "double delete" error, only that
  266.           <literal>free()</literal> was used here instead of
  267.           <literal>operator delete</literal>. Together with the stack
  268.           trace information including the filenames and line numbers,
  269.           this makes it fairly simple to find the trouble spot.</para>
  270.         <para>However, Valgrind output rarely is as simple and obvious
  271.           as in this case. Often, it is many hundreds or thousands of
  272.           lines long, including a number of "false positives". These
  273.           often stem from third-party libraries like X11 that you
  274.           cannot or do not want to debug. For this, there are
  275.           so-called suppressions, however, configuring those
  276.           suppressions is very difficult at this time. This is one of
  277.           the areas where the proprietary tools still have an
  278.           advantage. However, open source developers (including the
  279.           author) are already working on remedies to this
  280.           problem.</para>
  281.       </section>
  282.     </section>
  283.  
  284.     <section>
  285.       <title>Profiling</title>
  286.       <para>The other software development task that is difficult to
  287.         do without software tools is optimizing an
  288.         application. Actually, it is usually not so difficult to take
  289.         any arbitrary piece of code from your application and make it
  290.         run faster; the problem is how to find the spots that actually
  291.         need optimization - the so-called inner loops - so that
  292.         you do not spend time optimizing code that is rarely executed
  293.         or simply not a performance bottleneck at all. This task is
  294.         called <emphasis>profiling</emphasis>. In profiling, you want
  295.         to find out which functions or methods are called most often
  296.         and which have the longest runtime (and particularly of course
  297.         which functions or methods are called often
  298.         <emphasis>and</emphasis> have a long runtime).</para>
  299.       <para>In Linux development, <command>gprof</command> is the
  300.         traditional profiling command. However, gprof's output is
  301.         neither very comprehensive nor very easy to
  302.         understand. Probably the best profiler on the market is
  303.         Rational Quantify which employs the same code-instrumenting
  304.         technique as Rational Purify mentioned above, and which also
  305.         has a very good viewer, but suffers from the same problems
  306.         (price tag and platform availability) as Purify.</para>
  307.       <para>Again, Valgrind comes to the rescue. Because of the
  308.         virtual machine technique, Valgrind can actually be used for
  309.         more than just finding memory problems. An additional module,
  310.         a so-called <emphasis>skin</emphasis>, called
  311.         <emphasis>cachegrind</emphasis> tracks the number and duration
  312.         of all function and method calls and thus gives you valuable
  313.         data about where the performance bottleneck in your code
  314.         are.</para>
  315.       <para>While Valgrind provides more data than gprof, it would
  316.         only be a small improvement if it was not for KCacheGrind, a
  317.         KDE frontend to cachegrind. This provides very simple
  318.         navigation to the collected data and makes it really easy to
  319.         locate the performance bottlenecks.</para>
  320.     </section>
  321.   </section>
  322.  
  323.   <section>
  324.     <title>Summary</title>
  325.     <para>With the advent of Valgrind and its various skins like
  326.       cachegrind, high-quality tools for memory debugging and
  327.       profiling are now available to the open-source
  328.       community. Frontends like KCacheGrind make these tools more
  329.       accessible for software developers.</para>
  330.   </section>
  331. </article>
  332.