Objective C # principle 31: small and simple functions)

Source: Internet
Author: User

Objective C # principle 31: small and simple functions
Item 31: prefer small, simple functions

As an experiencedProgramClerk, No matter what language you used to use before using C #, we have integrated several ideas that can make it effective.Code. Sometimes, our efforts in the previous environment are the opposite in the. NET environment. Especially when you try to optimize some code manually. These actions often prevent the JIT compiler from performing the most effective optimization. Your extra work on the grounds of performance actually produces slower code. You 'd better write the code in the clearest way, and let the JIT compiler do the rest. The most common example is pre-optimization. If you create a very long and complex function, you want to use it to avoid too many function calls, which may cause many problems. In practice, it is harmful to. net programs to upgrade the logic of such a function to the loop body. This is the opposite of your reality. Let's look at some details.

This section describes how the JIT compiler works .. Net calls the JIT compiler during runtime to compile the Il commands generated by the C # compiler into machine code. This task is performed step by step during the running of the application. JIT does not compile the entire application at the beginning of the program. Instead, CLR calls the JIT compiler from a function to a function. This can minimize startup overhead to a reasonable level, but it is unreasonable that the application retains a large amount of code for later compilation. The JIT function that is never called will not compile it. You can break down the code into more small pieces through JIT to minimize a large number of irrelevant code. That is to say, smaller and more functions are better than larger and fewer functions. Consider this artificial example:

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 ();
}

When buildmsg is called for the first time, both options are compiled. In fact, only one is required. But suppose you write the code like this:

Public String buildmsg (bool takefirstpath)
{
If (takefirstpath)
{
Return firstpath ();
} Else
{
Return secondpath ();
}
}

Because each branch of the function body is decomposed into independent small functions, JIT requires these small functions, which is better than the previous buildmsg call. Indeed, this example is just artificial, and in fact it is nothing special. But think about it, do you often write more "expensive" Examples: Does an if statement contain 20 or more statements in each segment? Your overhead is to let JIT compile both branches when calling it for the first time. If a branch is not like an error condition, you will incur a waste that can be easily avoided. A small function means that the JIT compiler only compiles the logic it requires, rather than the code that is too long to be used immediately. For a long switch branch, JIT consumes multiple times of storage. Therefore, defining the content of each branch as inline is better than splitting it into a single function.

The JIT compiler can more easily register small and simple functions for processing. Registry refers to the local variables selected by the process to be stored in registers, which is better than storing them in the stack. Creating a small number of local variables can provide a better opportunity for JIT to put the most appropriate candidate objects in the register. This simple control process also affects whether JIT compilation can register variables as scheduled. If a function has only one loop, the loop variable is likely to be registered. However, when you use too many loops in a function, the JIT compiler has to make some difficult choices for variable registration. Simple is good. small and simple functions may only contain a few simple variables, so that JIT can easily optimize register usage.

The JIT compiler also determines the inline method. Inline means directly using the function body without calling the function. Consider this example:

// Readonly Name property:
Private string _ name;
Public string name
{
Get
{
Return _ name;
}
}

// Access:
String val = obj. Name;

Compared with the function call overhead, the property accessors entity contains a few instructions: For function calls, you must first store its status in the register, then execute it from start to end, and then store the returned results. This does not require more work to press a parameter to the stack when there is a parameter. If you write this statement, more machine commands will be generated:

String val = obj. _ name;

Of course, you should not do this because you already understand that it is best not to create public data members (see Principle 1 ). The JIT compiler understands that you need efficiency and conciseness, so it will inline attribute accessors. JIT will inline some methods when the speed or size is the target (or two at the same time), replacing the function call with the function body will make it more advantageous. In general, no additional rules need to be defined for inline, and any implemented inline may be changed in the future. In addition, inline functions are not your responsibility. The C # language does not provide any keywords to imply that the compiler says you want to inline a function. In fact, the C # compiler does not support any hint for JIT compilation to be inline. What you can do is make sure that your code is as clear as possible and that the JIT compiler can easily make the best decisions. I am familiar with the recommendation now: the smaller the method, the more likely it will become an inline object. Remember: No virtual methods or functions containing try/catch blocks cannot be inline.

Inline modified the principle that the code is about to be JIT. Here is an example of the access name attribute:

String val = "default name ";
If (OBJ! = NULL)
Val = obj. Name;

The JIT compiler has inline attribute accessors, which are bound to JIT code when the relevant methods are called.

You have no responsibility for yourAlgorithmDetermines the performance at the best machine level. C # the compiler and the JIT compiler have done this for you. C # The Compiler generates il code for each method, while the JIT compiler translates the Il code into machine instructions on the target machine. We don't need to worry too much about the exact principles of the JIT compiler in various situations; we can develop better algorithms at these times. Instead, you should consider how to express your algorithms in a good way, so that tools in the development environment can work in the best way. Fortunately, these principles you have considered have become good software development practices. Once again: use small and simple functions.

Remember, your C # code is compiled into machine executable commands after two steps. C # The Compiler generates the Il code in the form of an assembly. The JIT compiler generates machine commands in units of each function when necessary (when called in combination, or a group of methods ). Small functions make it very easy to be processed by JIT compiler in installments. Small functions are more likely to become inline candidates. Of course, it is not small enough: A simple control process is also very important. The Simple Control Branch in the function allows JIT to easily store variables. This is not just about writing clear code, but also about how to create code that is more effective at runtime.

======================================

item 31: prefer small, simple functions
as experienced programmers, in whatever language we favored before C #, We internalized several practices for developing more efficient code. sometimes what worked in our previous environment is counterproductive in. NET environment. this is very true when you try to hand-optimize algorithms for the C # compiler. your actions often prevent the JIT compiler from more efficient optimizations. your extra work, in the name of performance, actually generates slower code. you're better off writing the clearest code you can create. let the JIT compiler do the rest. one of the most common examples of premature optimizations causing problems is when you create longer, more complicated functions in the hopes of avoiding function CILS. practices such as hoisting function logic into the bodies of loops actually harm the performance of your. NET applications. it's counterintuitive, so let's go over all the details.

This chapter's introduction contains a simplified discussion of how the JIT compiler performs its work. the. net runtime invokes the JIT compiler to translate the Il generated by the C # compiler into machine code. this task is amortized processing ss the lifetime of your program's execution. instead of jiting your entire application when it starts, the CLR invokes the jiter on a function-by-function basis. this minimizes the startup cost to a reasonable level, yet keeps the application from becoming unresponsive later when more code needs to be jited. functions that do not ever get called do not get jited. you can minimize the amount of extraneous code that gets jited by factoring code into more, smaller functions rather than fewer larger functions. consider this rather contrived example:

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 ();
}

 

The first time buildmsg gets called, both paths are jited. Only one is needed. But suppose you rewrote the function this way:

Public String buildmsg (bool takefirstpath)
{
If (takefirstpath)
{
Return firstpath ();
} Else
{
Return secondpath ();
}
}

 

because the body of each clause has been factored into its own function, that function can be jited on demand rather than the first time buildmsg is called. yes, this example is contrived for space, and it won't make much difference. but consider how often you write more extensive examples: An if statement with 20 or more statements in both branches of the IF statement. you'll pay to JIT both clses the first time the function is entered. if one clause is an unlikely error condition, you'll incur a cost that you coshould easily avoid. smaller functions mean that the JIT compiler compiles the logic that's needed, not lengthy sequences of code that won't be used immediately. the JIT cost savings multiplies for long switch statements, with the body of each case statement defined inline rather than in separate functions.

Smaller and simpler functions make it easier for the JIT compiler to support enregistration. enregistration is the process of selecting which local variables can be stored in registers rather than on the stack. creating fewer local variables gives the JIT compiler a better chance to find the best candidates for enregistration. the simplicity of the control flow also affects how well the JIT compiler can enregister variables. if a function has one loop, that loop variable will likely be enregistered. however, the JIT compiler must make some tough choices about enregistering loop variables when you create a function with several loops. simpler is better. A smaller function is more likely to Fein fewer local variables and make it easier for the JIT compiler to optimize the use of the registers.

The JIT compiler also makes decisions about inlining methods. inlining means to substitute the body of a function for the function call. Consider this example:

// Readonly Name property:
Private string _ name;
Public string name
{
Get
{
Return _ name;
}
}

// Access:
String val = obj. Name;

 

The body of the property accessor contains fewer instructions than the Code necessary to call the function: Saving Register states, executing method prologue and epilogue code, and storing the function return value. there wocould be even more work if arguments needed to be pushed on the stack as well. there wocould be far fewer machine instructions if you were to write this:

String val = obj. _ name;

 

Of course, you wowould never do that because you know better than to create public data members (see item 1 ). the JIT compiler understands your need for both efficiency and elegance, so it inlines the property accessor. the JIT compiler inlines methods when the speed or size benefits (or both) Make it advantageous to replace a function call with the body of the called function. the standard does not define the exact rules for inlining, and any implementation cocould change in the future. moreover, it's not your responsibility to inline functions. the C # language does not even provide you with a keyword to give a hint to the compiler that a method shoshould be inlined. in fact, the C # compiler does not provide any hints to the JIT compiler regarding inlining. all you can do is ensure that your code is as clear as possible, to make it easier for the JIT compiler to make the best demo-possible. the recommendation shoshould be getting familiar by now: smaller methods are better candidates for inlining. but remember that even small functions that are virtual or that contain try/catch blocks cannot be inlined.

Inlining modifies the principle that code gets jited when it will be executed. Consider accessing the name property again:

String val = "default name ";
If (OBJ! = NULL)
Val = obj. Name;

 

If the JIT compiler inlines the property accessor, it must JIT that code when the containing method is called.

it's not your responsibility to determine the best machine-level representation of your algorithms. the C # compiler and the JIT compiler together do that for you. the C # compiler generates the IL for each method, and the JIT compiler translates that IL into machine code on the destination machine. you shoshould not be too concerned about the exact rules the JIT compiler uses in all cases; those will change over time as better algorithms are developed. instead, you shoshould be concerned about expressing your algorithms in a manner that makes it easiest for the tools in the environment to do the best job they can. luckily, those rules are consistent with the rules you already follow for good software-development practices. one more time: smaller and simpler functions

remember that translating your C # code into machine-executable code is a two-step process. the C # compiler generates Il that gets delivered in assemblies. the JIT compiler generates machine code for each method (or group of methods, when inlining is involved), as needed. small functions make it much easier for the JIT compiler to amortize that cost. small functions are also more likely to be candidates for inlining. it's not just smallness: simpler control flow matters just as much. fewer control branches inside functions make it easier for the JIT compiler to enregister variables. it's not just good practice to write clearer Code; it's how you create more efficient code at runtime.

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.