Valid C # item 31: prefer small, simple functions
For experienced programmers, No matter what language they use before getting started with C #, there are some ways to improve code efficiency. However, although some practices are effective in the previous language, they are counterproductive in the. NET environment. This is especially evident when we try to manually optimize algorithms for the C # compiler. Our actions often make JIT compilation unable to be optimized more effectively. For optimization purposes, the result is often a slower generation of code. We do not have to pursue the most straightforward code, and some work can be done by the compiler. Some optimizations may cause problems. A typical example is to create a long and complex function to avoid function calls. This will reduce the performance of. NET applications, which is contrary to the original intention. Let's pay attention to the details.
Here we will briefly introduce how JIT compilation works .. Net at runtime, the JIT compiler converts the Il generated by the C # compiler into machine code. This task runs throughout the life cycle of the program running. JIT does not process the entire application at the beginning of the program, but processes a function. When a program starts, it only processes some required functions. Other code is compiled by JIT only when necessary. Functions that will never be called will never be compiled by JIT. Compared with the less and larger function design, the smaller and more function design reduces the additional code overhead. Let's consider the following code:
Public String buildmsg (bool takefirstpath)
{
Stringbuilder MSG = new stringbuilder ();
If (takefirstpath)
{
MSG. append ("A problem occurred ");
MSG. append ("\ nthis is a problem ");
MSG. append ("imagine much more text ");
}
Else
{
MSG. append ("this path is not so bad ");
MSG. append ("\ NIT is only a minor inconvenience ");
MSG. append ("add more detailed diagnostics here ");
}
Return msg. tostring ();
}
Buildmsg is called immediately, and all code is compiled by JIT. However, only one path of the Code is useful. However, we can consider improving the function as follows:
Public String buildmsg (bool takefirstpath)
{
Stringbuilder MSG = new stringbuilder ();
If (takefirstpath)
{
Return firstpath ();
}
Else
{
Return secondpath ();
}
}
Different from the initial code, each branch now calls their respective functions. This method saves the runtime consumption, although it seems to be negligible. But let's consider an even more extreme example: Each of the two branches of an if contains 20 or more branches. The original practice will read the entire function at the beginning, causing unnecessary consumption. If you refine the function, the JIT compiler will compile the function based on the required logic. The unnecessary code will not be compiled immediately. For long switch branches, defining each case as a different function can save several times.
Small and simple functions help the JIT compiler easily register them. Through registration, local variables can be stored in registers rather than stacks. Creating fewer local variables helps the JIT compiler find the best candidate variables. Similarly, the control process affects the JIT compiler variable registration. If a function contains a loop, the loop variable is likely to be registered. However, once a function has multiple loops, the JIT compiler must make some choices among these loop variables. A simple function may contain fewer local variables, which helps the JIT compiler optimize the use of registers.
The JIT compiler is also related to inline functions. Inline functions replace function calls with function bodies. Consider the following example:
Private string _ name;
Public string name
{
Get
{
Return _ name;
}
}
// Access
String val = obj. Name
Property accessors use less command code than function calls. Function calling not only saves the register status, execution method code, and return values, but also requires more code when its parameters need to be written into the stack. If we write this statement, fewer machine commands are required:
String val = obj. _ name
Of course, we all understand that using attributes is too good to directly create public data members. To ensure both efficiency and specification, the JIT compiler uses the inline attribute accessors. The JIT compiler will inline some functions that can obtain speed and space optimization. We do not need to define additional rules for inline functions. inline functions are not our responsibility. The C # language does not provide a keyword that can only be used by the compiler for the inline method. In fact, the C # compiler does not provide any inline information for the JIT compiler. All we can do is keep the code clear so that the JIT compiler can make the best judgment. Smaller functions are suitable for inline operations. But note that no virtual function or function containing try/catch can be an inline function, even if it is very small.
We do not need to determine the best performance at the machine level for our algorithms. C # the compiler and JIT compiler will do this for us. C # The Compiler generates il for each method. The JIT compiler then converts these il into the machine code of the target machine. Instead of focusing too much on every rule used by the JIT compiler, we should devote this effort to How to Make Algorithms more standardized, let the running environment tools work more effectively for us.
Two steps are required to convert C # code into executable code on the machine. C # The Compiler generates Il, And the JIT compiler generates machine code for each method as needed. Small functions allow the JIT compiler to process in stages to reduce consumption and facilitate inline. However, it is not enough to be small, and a simpler control process is also important. Reducing the control flow branch helps the JIT compiler register temporary variables. This is not only related to the clarity of our code, but also to the execution efficiency.
Translated from Objective C #: 50 specific ways to improve your C # by Bill Wagner
Back to directory