C # Performance Optimization Practice

original
2014/06/11 11:58
Reading number 1.3K

Performance is an important indicator to evaluate the quality of a software product, and it is as important as the function of the product. When users choose a software product, they will basically test and compare the performance of similar products in person. As one of the important factors for purchasing that software.

What does software performance mean

  1. Reduce memory consumption
    In software development, memory consumption is generally taken as a secondary consideration, because today's computers generally have large memory. In many cases, the means of performance optimization is to exchange space for time. However, it does not mean that we can squander memory without fear. If it is necessary to support use cases with large amounts of data, the operating system will exchange internal and external memory frequently if memory is exhausted. The execution speed drops sharply.
  2. Increase execution speed
    1. Loading speed.
    2. Response speed for a specific operation. Including, click, keyboard input, scroll, sorting and filtering.

Principles of performance optimization

  1. Understanding needs
    Taking the MultiRow product as an example, one of the performance requirements of MultiRow is: "Smooth scrolling under the data binding of millions of rows." This goal has always been taken into account in the development process of the entire MultiRow project.
  2. Understanding bottlenecks
    According to experience, 99% of the performance consumption is caused by 1% of the code. Therefore, most performance optimizations are targeted at the 1% bottleneck code. The specific implementation is divided into two steps. First, identify bottlenecks, and then eliminate them.
  3. Avoid excessive
    First of all, it must be recognized that performance optimization itself has costs. This cost is not only reflected in the workload of performance optimization. It also includes complex code written for performance optimization, additional maintenance costs, new bugs, additional memory overhead, etc. A common problem is that some students who are new to software development will apply performance optimization techniques or design patterns to some unnecessary points, bringing unnecessary complexity. Performance optimization often requires trade-offs between benefits and costs.

How to find performance bottlenecks

As mentioned in the previous section, the first step of performance optimization is to find performance bottlenecks. This section mainly introduces some practices of locating performance bottlenecks.

  1. How to get memory consumption
    The following code can obtain the memory consumption of an operation.
     //Write some code that may consume memory here. For example, if you want to know how much memory is needed to create a GcMultiRow software, you can execute the following code long start = GC.GetTotalMemory(true); var gcMulitRow1 = new GcMultiRow(); GC.Collect(); //Ensure that all memory is recycled by GC GC.WaitForFullGCComplete(); long end = GC.GetTotalMemory(true); long useMemory = end - start;

      

  2. How to obtain time consumption
    The following code can obtain the time consumption of an operation.
     System.Diagnostics.Stopwatch watch = new System.Diagnostics.Stopwatch(); watch.Start(); for (int i = 0;  i < 1000; i++) { gcMultiRow1.Sort(); } watch.Stop(); var useTime = (double)watch. ElapsedMilliseconds / 1000;

     

    Here we execute an operation cycle for 1000 times, and then divide the consumed time by 1000 to determine the final consumed time. The results can be more accurate and stable, excluding unexpected data.

  3. Find performance problems through CodeReview.
    In many cases, you can find performance problems through CodeReview. Pay special attention to the circulation of large amounts of data. The logic in the loop should be executed as fast as possible.
  4. ANTS Performance Profiler
    ANTS Profiler is a powerful performance testing software. It can help us find performance bottlenecks. Using this software to locate performance bottlenecks can achieve twice the result with half the effort. Using this tool skillfully, we can quickly and accurately locate the code with performance problems. This tool is very powerful, but it is not perfect. First of all, this is a paid software, and the department has only a few license numbers. Secondly, the working principle of this software is to add some hooks in IL to record the time. Therefore, in the analysis, the execution speed of the software will be slower than the actual operation, and the data obtained is not 100% accurate. The data analyzed by the software should be used as a reference to help quickly locate the problem, but do not rely entirely on it, but also combine other techniques to analyze the performance of the program.

 

Methods and techniques of performance optimization

After locating the performance problem, there are many solutions. This section will introduce some performance optimization techniques and practices.

  1. Optimize program structure
    For program structure, it should be considered when designing to evaluate whether the performance requirements can be met. If a performance problem is found in the later stage, it will bring a lot of overhead to adjust the structure. give an example:
    1. GcMultiRowGcMultiRow should support 1 million rows of data. Assuming that each row has 10 columns, there should be 10 million cells, and each cell has many attributes. If no optimization is made, the memory overhead of a GcMultiRow software will be quite large when there is a large amount of data. GcMultiRow uses a hash table to store row data. Only the rows modified by the user are placed in the hash table, while most of the unchanged rows are directly replaced by templates. The purpose of saving memory is achieved.
    2. The drawing of Spread for WPF/Silverlight (SSL) WPF is different from that of Winform. It is realized by combining View elements. SSL also supports millions of data, but it cannot assign a View to every cell. So SSL uses VirtualizePanel to implement the drawing method. The idea is that each View is a cell presentation module. It can be separated from the cell data module. So. You only need to create a View for the displayed cell. When scrolling occurs, some cells will roll out of the screen and some cells will roll into the screen. At this time, separate the cell and view that roll out of the screen. Then reuse this part of View to the new cell entering the screen. This cycle. In this way, only a few hundred views are needed to support many cells.
  2. cache
    Cache is the most commonly used optimization method in performance optimization. It is applicable to obtain some data frequently, which takes a long time each time. At this time, the normal method will be used for the first acquisition, and the data will be cached after acquisition. Then use the cached data. If the cache optimization method is used, special attention should be paid to cache data synchronization. That is, if the real data changes, the cache data should be cleared in time to ensure that the wrong data will not be used because of the cache. give an example:
    1. Caching is often used. The simplest case is caching to a Field or temporary variable.
       for(int i = 0;  i < gcMultiRow.RowCount; i++)  {  // Do something;  }  The above code is generally OK, but if the number of GcMultiRow lines is large. When the value of the RowCount property is slow, you need to use the cache to optimize the performance. int rowCount = gcMultiRow.RowCount; for (int i = 0;  i < rowCount; i++) { // Do something; }

        

    2. Using object pool is also a common caching scheme, which is slightly more complex than using Field or temporary variables. For example, in MultiRow, you need to use a lot of Brush and Pen to draw edges and backgrounds. These GDI objects should be created before each use and destroyed after use. The process of creation and destruction is relatively slow. The solution used by GcMultiRow is to create a GDIPool. In essence, it is some dictionaries that use color as the key. Therefore, only the first retrieval needs to be created, and the previously created ones will be used directly in the future. The following is the code of GDIPool:
       public static class GDIPool  {  Dictionary<Color, Brush > _cacheBrush = new Dictionary<Color, Brush>();  Dictionary<Color, Pen> _cachePen = new Dictionary<Color, Pen>();  public static Pen GetPen(Color color)  {  Pen pen;  if_cachePen.TryGetValue(color, out pen))  {  return pen;  }  pen = new Pen(color);  _cachePen. Add(color, pen);  return pen;  }  }

        

    3. Lazy structure
      Sometimes, some object creation takes a long time. This object may not be used in all scenarios. At this time, using the method of Lai construction can effectively improve the performance. Example: Object A needs to create object B internally. Object B takes a long time to construct. General practice:
       public class A { public B _b = new B(); }

        

      In general, object A is constructed at the same time as object B, which slows down the construction speed of object A. Optimization practices:

       public class A { private B _b; public B BProperty { get { if(_b == null) { _b = new B(); } return _b; } } }

        

       After optimization, B objects need not be created when constructing A, but only when they need to be used.
    4. optimization algorithm
      The optimization algorithm can effectively improve the performance of specific operations. When using an algorithm, you should understand the application of the algorithm, the best case and the worst case. Taking GcMultiRow as an example, the original sorting algorithm of MultiRow used the classic quick sorting algorithm. This seems to be no problem. However, for table software, users often sort ordered tables, such as switching between order and reverse order. The worst case of the classic quick sort algorithm is the basic order. Therefore, the classic quick sort algorithm is not suitable for MultiRow. Finally, this problem is solved by the modified sorting algorithm. The improved quick sort algorithm uses three midpoints instead of one midpoint of the classic quick sort algorithm. Each exchange selects one of the three midpoints. In this way, out of order and basically in order are not the worst case of the algorithm, thus optimizing the performance.
    5. Understand the data structure provided by the Framework
      The. net framework platform we are working on now has many ready-made data structures. We should understand these data structures to improve the performance of our programs: for example:
      1. String addition operator VS StringBuilder: String operation is one of the basic operations we often encounter. We often write such codes as string string=str1+str2. When there are few strings to operate on, this operation is OK. However, if there are a lot of operations (such as Save/Load of text files and Render of Asp.net), this will cause serious performance problems. At this point, we should use StringBuilder instead of adding strings.
      2. Dictionary VS List Dictionary and List are the two most commonly used collection classes. Choosing the right collection class can greatly improve the performance of the program. In order to make the right choice, we should have a better understanding of the performance of various operations of Dictionary and List. The following table outlines the performance comparison of the two data structures.

        operation

        List

        Dictionary

        Indexes

        fast

        slow

        Find(Contains)

        slow

        fast

        Add

        fast

        slow

        Insert

        slow

        fast

        Remove

        slow

        fast

      3. TryGetValue For the value of Dictionary, the direct method is as follows:
         if(_dic. ContainKey("Key") { return _dic\["Key"\]; }

          

        When a large number of values are needed, such fetching will cause performance problems. The optimization method is as follows:

         object value; if(_dic. TryGetValue("Key", out value)) { return value; }

          

        Using TryGetValue can double the performance of first containing and then taking values.

      4. Select the appropriate Key for the Dictionary. In most cases, the performance of dictionary values depends on the performance of Equals and GetHashCode methods of the key object. If possible, the best performance is to use Int as a key. If a custom class is used as a key, the following two points should be guaranteed: 1 The GetHashCode repetition rate of different objects is low. 2. The GetHashCode and Equals methods are immediately simple and efficient.
      5. The Sort and BinarySearch functions of List are very good. If they can meet the functional requirements, it is recommended to use them directly instead of rewriting them yourself.
         List<int> list = new List<int>{3, 10, 15}; list.BinarySearch(10); //  For existing values, the result is 1 list.BinarySearch(8); //  For nonexistent values, a negative number will be used to represent the position. For example, when searching for 8, the result is - 2, when searching for 0, the result is - 1, and when searching for 100, the result is - 4

          

    6. Increase response time asynchronously
      1. Multithreading
        Some operations really take a long time. If the user's operation is stuck during this period of time, it will bring a poor user experience. Sometimes, the use of multithreading technology can solve this problem. For example, CalculatorEngine needs to initialize all functions during construction. Because there are many functions, the initialization time will be long. This is the use of multithreading technology. The initialization of Functions in the worker thread will not affect the main thread's rapid response to other user operations. The codes are as follows:
         public CalcParser() { if (_functions == null) { lock (_obtainFunctionLocker) { if (_functions == null) { System.Threading.ThreadPool.QueueUserWorkItem((s) => { if (_functions == null) { lock (_obtainFunctionLocker) { if (_functions == null) { _functions = EnsureFunctions(); } } } }); } } } }

          

        The slower operation here is the EnsureFunctions function, which is executed in another thread and does not affect the response of the main thread. Of course, using multithreading is a more difficult solution, and cross thread access and deadlock need to be fully considered.

      2. Add delay time
        When GcMultiRow implements the AutoFilter function, it uses a scheme similar to delayed execution to improve the response speed. The function of AutoFilter is to update the filtering results according to the user's input during the input process. When the amount of data is large, one filtering takes a long time, which will affect the continuous input of users. Using multiple threads may be a good solution, but using multiple threads will increase the complexity of the program. The solution of MultiRow is that when receiving a user's keyboard input message, it does not start the Filter immediately, but waits for 0.3 seconds. If the user enters continuously, he will receive the keyboard message again within 0.3 seconds, and then wait another 0.3 seconds. The Filter will not be triggered until there is no new keyboard message for 0.3 seconds. It ensures the purpose of fast response to user input.
      3. Application.Idle event
        In the Designer of GcMultiRow, the Disable/Enable status of the buttons on the ToolBar should always be refreshed according to the current status. A refresh takes a long time. If users input continuously, they will feel stuck, which will affect the user experience. GcMultiRow's optimization solution is to hang the application of the system Idle event. When the system is idle, the system will trigger this event. Receiving this event indicates that the user has completed continuous input at this time, and then the status of the button can be refreshed calmly.
      4. Invalidate, BeginInvoke. The PostEvent platform itself also provides some asynchronous schemes.
        For example; Under Winform, when triggering an area redraw, it is generally not applicable to Refresh but Invalidate, which will trigger asynchronous refresh. You can Invalidate multiple times before triggering. BeginInvoke and PostMessage can also trigger asynchronous behavior.
    7. Understanding Platform Features
      For example, compared with CLR property, WPF's DP DP is very slow, including Get and Set, which is different from the general texture of getting faster and Set slower. If a DP needs to be read multiple times, it is recommended that CLR property cache it.
    8. Progress bar to improve user experience
      Sometimes, none of the above solutions can quickly respond to user operations, progress bars, pictures that keep circling, and suggestive words such as "Your operation may take a long time, please wait patiently". Can improve the user experience. It can be considered as the final scheme.
Expand to read the full text
Loading
Click to lead the topic 📣 Post and join the discussion 🔥
Reward
zero comment
zero Collection
zero fabulous
 Back to top
Top