performance 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204
  1. * Writing performant .NET and Mono applications
  2. The following document contains a few hints on how to improve
  3. the performance of your Mono/.NET applications.
  4. These are just guidelines, and you should still profile your
  5. code to find the actual performance problems in your
  6. application. It is never a smart idea to make a change with the
  7. hopes of improving the performance of your code without first
  8. measuring. In general, these guidelines should serve as ideas
  9. to help you figure out `how can I make this method run faster'.
  10. It is up to you to figure out, `Which method is running slowly.'
  11. ** Using the Mono profiler
  12. So, how does one measure what method are running slowly? A profiler
  13. helps with this task. Mono includes a profiler that is built
  14. into the runtime system. You can invoke this profiler on your program
  15. by running with the --profile flag.
  16. <pre>
  17. mono --profile program.exe
  18. </pre>
  19. The above will instruct Mono to instrument your application
  20. for profiling. The default Mono profiler will record the time
  21. spent on a routine, the number of times the routine called,
  22. the memory consumed by each method broken down by invoker, and
  23. the total amount of memory consumed.
  24. It does this by asking the JIT to insert a call to the profiler
  25. every time a method is entered or left. The profiler times the
  26. amount of time elapsed between the beginning and the end of the
  27. call. The profiler is also notified of allocations.
  28. When the program has finished executing, the profiler prints the
  29. data in human readable format. It looks like:
  30. <pre>
  31. Total time spent compiling 227 methods (sec): 0.07154
  32. Slowest method to compile (sec): 0.01893: System.Console::.cctor()
  33. Time(ms) Count P/call(ms) Method name
  34. ########################
  35. 91.681 1 91.681 .DebugOne::Main()
  36. Callers (with count) that contribute at least for 1%:
  37. 1 100 % .DebugOne::Main(object,intptr,intptr)
  38. ...
  39. Total number of calls: 3741
  40. ...
  41. Allocation profiler
  42. Total mem Method
  43. ########################
  44. 406 KB .DebugOne::Main()
  45. 406 KB 1000 System.Int32[]
  46. Callers (with count) that contribute at least for 1%:
  47. 1 100 % .DebugOne::Main(object,intptr,intptr)
  48. Total memory allocated: 448 KB
  49. </pre>
  50. At the top, it shows each method that is called. The data is sorted
  51. by the total time that the program spent within the method. Then
  52. it shows how many times the method was called, and the average time
  53. per call.
  54. Below this, it shows the top callers of the method. This is very useful
  55. data. If you find, for example, that the method Data::Computate () takes
  56. a very long time to run, you can look to see if any of the calls can be
  57. avoided.
  58. Two warnings must be given about the method data. First,
  59. the profiler has an overhead associated with it. As such,
  60. a high number of calls to a method may show up as comsuming
  61. lots of time, when in reality they do not consume much time
  62. at all. If you see a method that has a very high number of
  63. calls, you may be able to ignore it. However, do consider
  64. removing calls if possible, as that will sometimes help
  65. performance. This problem is often seen with the use
  66. of built in collection types.
  67. Secondly, due to the nature of the profiler, recursive calls
  68. have extermely large times (because the profiler double counts
  69. when the method calls itself). One easy way to see this problem
  70. is that if a method is shown as taking more time than the Main
  71. method, it is very likely recursive, and causing this problem.
  72. Below the method data, allocation data is shown. This shows
  73. how much memory each method allocates. The number beside
  74. the method is the total amount of memory. Below that, it
  75. is broken down into types. Then, the caller data is given. This
  76. data is again useful when you want to figure out how to eliminate calls.
  77. You might want to keep a close eye on the memory consumption
  78. and on the method invocation counts. A lot of the
  79. performance gains in MCS for example came from reducing its
  80. memory usage, as opposed to changes in the execution path.
  81. ** Memory Management in the .NET/Mono world.
  82. Since Mono and .NET offer automatic garbage collection, the
  83. programmer is freed from having to track and dispose the
  84. objects it consumes (except for IDispose-like classes). This
  85. is a great productivity gain, but if you create thousands of
  86. objects, that will make the garbage collector do more work,
  87. and it might slow down your application.
  88. Remember, each time you allocate an object, the GC is forced
  89. to find space for the object. Each object has an 8 byte overhead
  90. (4 to tell what type it is, then 4 for a sync block). If
  91. the GC finds that it is running out of room, it will scan every
  92. object for pointers, looking for unreferenced objects. If you allocate
  93. extra objects, the GC then must take the effort to free the objects.
  94. Mono uses the Boehm GC, which is a conservative collector,
  95. and this might lead to some memory fragmentation and unlike
  96. generational GC systems, it has to scan the entire allocated
  97. memory pool.
  98. *** Boxing
  99. The .NET framework provides a rich hierchy of object types.
  100. Each object not only has value information, but also type
  101. information associated with it. This type information makes
  102. many types of programs easier to write. It also has a cost
  103. associated with it. The type information takes up space.
  104. In order to reduce the cost of type information, almost every
  105. Object Oriented language has the concept of `primitatives'.
  106. They usually map to types such as integers and bools. These
  107. types do not have any type information associated with them.
  108. However, the language also must be able to treat primitatives
  109. as first class datums -- in the class with objects. Languages
  110. handle this issue in different ways. Some choose to make a
  111. special class for each primative, and force the user to do an
  112. operation such as:
  113. <pre>
  114. // This is Java
  115. list.add (new Integer (1));
  116. System.out.println (list.get (1).intValue ());
  117. </pre>
  118. The C# design team was not satisfied with this type
  119. of construct. They added a notion of `boxing' to the language.
  120. Boxing preforms the same thing as Java's <code>new Integer (1)</code>.
  121. The user is not forced to write the extra code. However,
  122. behind the scenes the <em>same thing</em> is being done
  123. by the runtime. Each time a primative is cast to an object,
  124. a new object is allocated.
  125. You must be careful when casting a primative to an object.
  126. Note that because it is an implicit conversion, you will
  127. not see it in your code. For example, boxing is happening here:
  128. <pre>
  129. ArrayList foo = new ArrayList ();
  130. foo.Add (1);
  131. </pre>
  132. In high performance code, this operation can be very costly.
  133. *** Using structs instead of classes for small objects
  134. For small objects, you might want to consider using value
  135. types (structs) instead of object (classes).
  136. However, you must be careful that you do not use the struct
  137. as an object, in that case it will actually be more costly.
  138. As a rule of thumb, only use structs if you have a small
  139. number of fields (totaling less than 32 bytes), and
  140. need to pass the item `by value'. You should not box the object.
  141. *** Assisting the Garbage Collector
  142. Although the Garbage Collector will do the right thing in
  143. terms of releasing and finalizing objects on time, you can
  144. assist the garbage collector by clearing the fields that
  145. points to objects. This means that some objects might be
  146. elegible for collection earlier than they would, this can help
  147. reduce the memory consumption and reduce the work that the GC
  148. has to do.
  149. ** foreach
  150. The <tt>foreach</tt> C# statement handles various kinds of
  151. different constructs (about seven different code patterns are
  152. generated). Typically foreach generates more efficient code
  153. than loops constructed manually, and also ensures that objects
  154. which implement IDispose are properly released.
  155. But foreach sometimes might generate code that under stress
  156. performs badly. Foreach performs badly when its used in tight
  157. loops, and its use leads to the creation of many enumerators.
  158. Although technically obtaining an enumerator for some objects
  159. like ArrayList is more efficient than using the ArrayList
  160. indexer, the pressure introduced due to the extra memory
  161. requirements and the demands on the garbage collector make it
  162. more inneficient.
  163. There is no straight-forward rule on when to use foreach, and
  164. when to use a manual loop. The best thing to do is to always
  165. use foreach, and only when profile shows a problem, replace
  166. foreach with for loops.