简体   繁体   English

哪种代码流模式在C#/。NET中更有效?

[英]Which code-flow pattern is more efficient in C#/.NET?

Consider the situation in which the main logic of a method should only actually run given a certain condition. 考虑一种情况,其中方法的主要逻辑仅应在特定条件下实际运行。 As far as I know, there are two basic ways to achieve this: 据我所知,有两种基本方法可以实现此目的:

If inverse condition is true, simply return: 如果逆条件为真,则只需返回:

public void aMethod(){
    if(!aBoolean) return;
    // rest of method code goes here
}

or 要么

If original condition is true, continue execution: 如果原始条件为真,则继续执行:

public void aMethod(){
    if(aBoolean){
        // rest of method code goes here
    }
}

Now, I would guess that which of these implementations is more efficient is dependent on the language its written in and/or how if statements and return statements, and possibly method calls, are implemented by the compiler/interpreter/VM (depending on language); 现在,我猜想,这些实现的是更有效的是依赖于语言的书写和/或如何if语句和return语句,以及可能的方法调用,由编译器/解释器实现/ VM(取决于语言) ; so the first part of my question is, is this true? 所以我的问题的第一部分是,这是真的吗?

The second part of my question is, if the the answer to the first part is "yes", which of the above code-flow patterns is more efficient specifically in C#/.NET 4.6.x? 我的问题的第二部分是,如果第一部分的答案是“是”,那么上述哪种代码流模式在C#/。NET 4.6.x中更有效?

Edit: In reference to Dark Falcon's comment: the purpose of this question is not actually to fix performance issues or optimize any real code I've written, I am just curious about how each piece of each pattern is implemented by the compiler, eg for arguments sake, if it was compiled verbatim with no compiler optimizations, which would be more efficient? 编辑:参考Dark Falcon的评论:这个问题的目的并不是要解决性能问题或优化我编写的任何实际代码,我只是对编译器如何实现每个模式的每个部分感到好奇,例如参数的缘故,如果它是在没有编译器优化的情况下逐字编译的,哪一种效率更高?

TL;DR It doesn't make a difference. TL; DR没什么关系。 Current generations of processors (circa Ivy Bridge and later) don't use a static branch-prediction algorithm that you can reason about anymore, so there is no possible performance gain in using one form or the other. 当前一代的处理器(大约在Ivy Bridge和更高版本中)不再使用可以推理的静态分支预测算法,因此使用一种形式或另一种形式不会带来性能提升。

On most older processors, the static branch-prediction strategy is generally that forward conditional jumps are assumed to be taken, while backwards conditional jumps are assumed not-taken. 在大多数较旧的处理器上,静态分支预测策略通常是假定采用前向条件跳转,而假定不采用后向条件跳转。 Therefore, there might be a small performance advantage to be gained the first time the code is executed by arranging for the fall-through case to be the most likely— ie , 因此,在第一次执行代码时,通过安排掉线情况最有可能获得较小的性能优势,
if { expected } else { unexpected } . if { expected } else { unexpected }

But the fact is, this kind of low-level performance analysis makes very little sense when writing in a managed, JIT-compiled language like C#. 但是事实是,当使用托管的,JIT编译的语言(如C#)编写时,这种低级性能分析几乎没有意义。

You're getting a lot of answers that say readability and maintainability should be your primary concern when writing code. 您会得到很多答案,其中说,编写代码时应首先考虑可读性和可维护性。 This is regrettably common with "performance" questions, and while it is completely true and unarguable, it mostly skirts the question instead of answering it. 遗憾的是,这在“性能”问题中很常见,尽管它是完全正确且无可争辩的,但它通常会忽略问题而不是回答问题。

Moreover, it isn't clear why form "A" would be intrinsically more readable than form "B", or vice versa. 此外,尚不清楚为什么表格“ A”本质上比表格“ B”更具可读性,反之亦然。 There are just as many arguments one way or the other—do all parameter validation at the top of the function, or ensure there is only a single return point—and it ultimately gets down to doing what your style guide says, except in really egregious cases where you'd have to contort the code in all sorts of terrible ways, and then you should obviously do what is most readable. 一种方法或另一种方法有很多参数–在函数顶部进行所有参数验证,或确保只有一个返回点–最终归结为您的样式指南所说的,除非确实如此糟糕在这种情况下,您必须以各种可怕的方式来扭曲代码,然后显然应该做一些最易读的事情。

Beyond being a completely reasonable question to ask on conceptual/theoretical grounds, understanding the performance implications also seems like an excellent way to make an informed decision about which general form to adopt when writing your style guide. 除了要从概念/理论基础上提出一个完全合理的问题外,理解性能影响似乎也是一种明智的决定方式,可以明智地决定编写样式指南时采用哪种一般形式。

The remainder of the existing answers consist of misguided speculation, or downright incorrect information. 现有答案的其余部分包括误导性猜测或彻头彻尾的错误信息。 Of course, that makes sense. 当然,这是有道理的。 Branch prediction is complicated, and as processors get smarter, it only gets harder to understand what is actually happening (or going to happen) under the hood. 分支预测很复杂,并且随着处理器变得越来越聪明,它只会越来越难以理解引擎盖下实际发生(或将要发生)的事情。

First, let's get a couple of things straight. 首先,让我们弄清楚一些事情。 You make reference in the question to analyzing the performance of unoptimized code. 您可以在问题中参考分析未优化代码的性能。 No, you don't ever want to do that. 不,您永远都不想这样做。 It is a waste of time; 这是浪费时间。 you'll get meaningless data that does not reflect real-world usage, and then you'll try and draw conclusions from that data, which will end up being wrong (or maybe right, but for the wrong reasons, which is just as bad). 您将获得无法反映实际使用情况的无意义的数据,然后您将尝试从该数据中得出结论,最终将得出错误的结论(或也许是正确的,但由于错误的原因,这同样很糟糕) )。 Unless you're shipping unoptimized code to your clients (which you shouldn't be doing), then you don't care how unoptimized code performs. 除非您将未优化的代码交付给客户(您不应该这样做),否则您不会在乎未优化的代码的性能。 When writing in C#, there are effectively two levels of optimization. 使用C#编写时,实际上有两个优化级别。 The first is performed by the C# compiler when it is generating the intermediate language (IL). 第一种由C#编译器生成中间语言(IL)时执行。 This is controlled by the optimization switch in the project settings. 这由项目设置中的优化开关控制。 The second level of optimization is performed by the JIT compiler when it translates the IL into machine code. 当JIT编译器将IL转换为机器代码时,将执行第二级优化。 This is a separate setting, and you can actually analyze the JITed machine code with optimization enabled or disabled. 这是一个单独的设置,您实际上可以分析启用或禁用优化的JIT机器代码。 When you're profiling or benchmarking, or even analyzing the generated machine code, you need to have both levels of optimizations enabled. 在进行性能分析或基准测试,甚至分析生成的机器代码时,需要同时启用两个优化级别。

But benchmarking optimized code is difficult, because the optimization often interferes with the thing you're trying to test. 但是对优化代码进行基准测试比较困难,因为优化经常会干扰您要测试的内容。 If you tried to benchmark code like that shown in the question, an optimizing compiler would likely notice that neither one of them is actually doing anything useful and transform them into no-ops. 如果您尝试像问题中所示的那样对代码进行基准测试,则优化的编译器可能会注意到它们实际上都没有做任何有用的事情并将其转换为无操作。 One no-op is equally fast as another no-op—or maybe it's not, and that's actually worse, because then all you're benchmarking is noise that has nothing to do with performance. 一个无操作操作与另一个无操作操作同样快-也许不是,这实际上更糟,因为那时您要进行基准测试的只是与性能无关的噪音。

The best way to go here is to actually understand, on a conceptual level, how the code is going to be transformed by a compiler into machine code. 最好的方法是从概念上实际理解编译器将如何将代码转换为机器代码。 Not only does that allow you to escape the difficulties of creating a good benchmark, but it also has value above and beyond the numbers. 这不仅使您能够逃避创建良好基准的困难,而且还具有超越数字的价值。 A decent programmer knows how to write code that produces correct results; 体面的程序员知道如何编写可产生正确结果的代码。 a good programmer knows what is happening under the hood (and then makes an informed decision about whether or not they need to care). 一个好的程序员知道幕后的情况( 然后就是否需要照顾做出明智的决定)。

There has been some speculation about whether the compiler will transform form "A" and form "B" into equivalent code. 有人猜测编译器是否会将形式“ A”和形式“ B”转换为等效代码。 It turns out that the answer is complicated. 事实证明,答案很复杂。 The IL will almost certainly be different because it will be a more or less literal translation of the C# code that you actually write, regardless of whether or not optimizations are enabled. IL几乎可以肯定会有所不同,因为无论您是否启用优化,IL或多或少都是您实际编写的C#代码的字面翻译。 But it turns out that you really don't care about that, because IL isn't executed directly. 但是事实证明,您实际上并不在乎,因为IL不会直接执行。 It's only executed after the JIT compiler gets done with it, and the JIT compiler will apply its own set of optimizations. 它仅在JIT编译器完成后才执行,并且JIT编译器将应用自己的优化集。 The exact optimizations depend on exactly what type of code you've written. 确切的优化取决于您编写的代码类型。 If you have: 如果你有:

int A1(bool condition)
{
    if (condition)    return 42;
    return 0;
}

int A2(bool condition)
{
    if (!condition)   return 0;
    return 42;
}

it is very likely that the optimized machine code will be the same. 优化后的机器代码很有可能是相同的。 In fact, even something like: 实际上,即使这样:

void B1(bool condition)
{
    if (condition)
    {
        DoComplicatedThingA();
        DoComplicatedThingB();
    }
    else
    {
        throw new InvalidArgumentException();
    }
}

void B2(bool condition)
{
    if (!condition)
    {
        throw new InvalidArgumentException();
    }
    DoComplicatedThingA();
    DoComplicatedThingB();
}

will be treated as equivalent in the hands of a sufficiently capable optimizer. 在功能足够强大的优化程序中将被视为等效项。 It is easy to see why: they are equivalent. 很容易理解为什么:它们等效的。 It is trivial to prove that one form can be rewritten in the other without changing the semantics or behavior, and that is precisely what an optimizer's job is. 证明一种形式可以用另一种形式重写而无需更改语义或行为,这很简单,而这正是优化器的工作。

But let's assume that they did give you different machine code, either because you wrote complicated enough code that the optimizer couldn't prove that they were equivalent, or because your optimizer was just falling down on the job (which can sometimes happen with a JIT optimizer, since it prioritizes speed of code generation over maximally efficient generated code). 但是,让我们假设它们确实为您提供了不同的机器代码,或者是因为您编写了足够复杂的代码以致优化器无法证明它们是等效的,或者是因为您的优化器刚刚落伍了(有时可能会发生在JIT中)优化程序,因为它优先于代码生成的速度而不是最大效率的生成代码。 For expository purposes, we'll imagine that the machine code is something like the following (vastly simplified): 出于说明目的,我们将假设机器代码类似于以下内容(大大简化了):

C1:
    cmp  condition, 0        // test the value of the bool parameter against 0 (false)
    jne  ConditionWasTrue    // if true (condition != 1), jump elsewhere;
                             //  otherwise, fall through
    call DoComplicatedStuff  // condition was false, so do some stuff
    ret                      // return
ConditionWasTrue:
    call ThrowException      // condition was true, throw an exception and never return
C2:
    cmp  condition, 0        // test the value of the bool parameter against 0 (false)
    je   ConditionWasFalse   // if false (condition == 0), jump elsewhere;
                             //  otherwise, fall through
    call DoComplicatedStuff  // condition was true, so do some stuff
    ret                      // return
ConditionWasFalse:
    call ThrowException      // condition was false, throw an exception and never return

That cmp instruction is equivalent to your if test: it checks the value of condition and determines whether it's true or false, implicitly setting some flags inside the CPU. cmp指令等效于您的if测试:它检查condition的值并确定它是true还是false,从而在CPU内部隐式设置一些标志。 The next instruction is a conditional branch: it branches to the specification location/label based on the values of one or more flags. 下一条指令是条件分支:它根据一个或多个标志的值分支到规范位置/标签。 In this case, je is going to jump if the "equals" flag is set, while jne is going to jump if the "equals" flag is not set. 在这种情况下,如果设置了“等于”标志,则je将跳转,而如果设置“等于”标志,则jne将跳转。 Simple enough, right? 很简单吧? This is exactly how it works on the x86 family of processors, which is probably the CPU for which your JIT compiler is emitting code. 这正是在x86处理器系列上的工作方式,这可能是您的JIT编译器为其发出代码的CPU。

And now we get to the heart of the question that you're really trying to ask; 现在,我们进入了您真正要问的问题的核心; namely, does it matter whether we execute a je instruction to jump if the comparison set the equal flag, or whether we execute a jne instruction to jump if the comparison did not set the equal flag? 也就是说,如果比较设置相等标志是否执行je指令跳转,或者如果比较设置相等标志是否执行jne指令跳转是否重要? Again, unfortunately, the answer is complicated, but enlightening. 再次,不幸的是,答案很复杂,但却很有启发性。

Before continuing, we need to develop some understanding of branch prediction . 在继续之前,我们需要对分支预测有一些了解。 These conditional jumps are branches to some arbitrary section in the code. 这些条件跳转是代码中任意部分的分支。 A branch can either be taken (which means the branch actually happens, and the processor begins executing code found at a completely different location), or it can be not taken (which means that execution falls through to the next instruction as if the branch instruction wasn't even there). 可以采用分支(这意味着分支实际上发生了,并且处理器开始执行在完全不同的位置找到的代码),也可以不采用分支(这意味着执行将进入下一条指令,就像分支指令一样)甚至不在那里)。 Branch prediction is very important because mispredicted branches are very expensive on modern processors with deep pipelines that use speculative execution. 分支预测非常重要,因为在带有使用推测执行的深度管道的现代处理器上, 错误预测的分支非常昂贵 If it predicts right, it continues uninterrupted; 如果它预测正确,它将继续不间断; however, if it predicts wrong, it has to throw away all of the code that it speculatively executed and start over. 但是,如果它预测错误,则必须丢弃推测性执行的所有代码,然后重新开始。 Therefore, a common low-level optimization technique is replacing branches with clever branchless code in cases where the branch is likely to be mispredicted. 因此, 一种常见的低级优化技术是在分支可能被错误预测的情况下, 用巧妙的无分支代码替换分支 A sufficiently smart optimizer would turn if (condition) { return 42; } else { return 0; } if (condition) { return 42; } else { return 0; } if (condition) { return 42; } else { return 0; } if (condition) { return 42; } else { return 0; } into a conditional move that didn't use a branch at all, regardless of which way you wrote the if statement, making branch prediction irrelevant. if (condition) { return 42; } else { return 0; }转换为根本不使用分支的条件移动,无论您使用哪种方式编写if语句,都使分支预测无关紧要。 But we're imagining that this didn't happen, and you actually have code with a conditional branch—how does it get predicted? 但是我们在想这没有发生,实际上您的代码带有条件分支-如何预测它?

How branch prediction works is complicated, and getting more complicated all the time as CPU vendors continue to improve the circuitry and logic inside of their processors. 分支预测的工作原理很复杂,并且随着CPU供应商不断改进其处理器内部的电路和逻辑,它一直变得越来越复杂。 Improving branch prediction logic is a significant way that hardware vendors add value and speed to the things they're trying to sell, and every vendor uses different and proprietary branch-prediction mechanisms. 改进分支预测逻辑是硬件供应商为其尝试销售的产品增加价值和速度的重要途径,并且每个供应商都使用不同的专有分支预测机制。 Worse, every generation of processor uses slightly different branch-prediction mechanisms, so reasoning about it in the "general case" is exceedingly difficult. 更糟糕的是,每一代处理器都使用略有不同的分支预测机制,因此在“一般情况”下进行推理非常困难。 Static compilers offer options that allow you to optimize the code they generate for a particular generation of microprocessor, but this doesn't generalize well when shipping code to a large number of clients. 静态编译器提供了一些选项,这些选项使您可以优化它们为特定一代微处理器生成的代码,但这在将代码交付给大量客户端时并不能很好地推广。 You have little choice but to resort to a "general purpose" optimization strategy, although this usually works pretty well. 您别无选择,只能求助于“通用”优化策略,尽管这通常效果很好。 The big promise of a JIT compiler is that, because it compiles the code on your machine right before you use it, it can optimize for your specific machine, just like a static compiler invoked with the perfect options. JIT编译器的最大希望是,因为它可以在使用之前在您的计算机上编译代码,所以它可以针对您的特定计算机进行优化,就像使用完美选项调用的静态编译器一样。 This promise hasn't exactly been reached, but I won't digress down that rabbit hole. 尚未完全实现这一诺言,但我不会偏离那个兔子洞。

All modern processors have dynamic branch prediction, but how exactly they implement it is variable. 所有现代处理器都有动态分支预测,但是它们执行的准确度是可变的。 Basically, they "remember" whether a particular (recent) branch was taken or not taken, and then predict that it will go this way the next time. 基本上,他们“记住”某个特定的(最近的)分支是否被采用,然后预测下一次它将以这种方式进行。 There are all kinds of pathological cases that you can imagine here, and there are, correspondingly, all kinds of cases in or approaches to the branch-prediction logic that help to mitigate the possible damage. 您可以在这里想象各种病理情况,相应地,分支预测逻辑中或方法中的各种情况也有助于减轻可能的损害。 Unfortunately, there isn't really anything you can do yourself when writing code to mitigate this problem—except getting rid of branches entirely, which isn't even an option available to you when writing in C# or other managed languages. 不幸的是,编写代码来缓解此问题时,您实际上无法做任何事情-除了完全摆脱分支,使用C#或其他托管语言编写时甚至没有选择。 The optimizer will do whatever it will; 优化器将尽其所能。 you just have to cross your fingers and hope that it is the most optimal thing. 您只需要交叉手指,并希望这是最理想的选择。 In the code we're considering, then, dynamic branch prediction is basically irrelevant and we won't talk about it any more. 在我们正在考虑的代码中,动态分支预测基本上是不相关的,我们不再赘述。

What is important is static branch prediction—what prediction is the processor going to make the first time it executes this code, the first time it encounters this branch, when it doesn't have any real basis on which to make a decision? 重要的是静态分支预测-当处理器没有真正的决策依据时,处理器第一次执行该代码,第一次遇到该分支时将进行什么预测? There are a bunch of plausible static prediction algorithms: 有很多合理的静态预测算法:

  • Predict all branches are not taken (some early processors did, in fact, use this). 预测不采用所有分支(实际上,某些早期的处理器确实使用了此分支)。
  • Assume "backwards" conditional branches are taken, while "forwards" conditional branches are not taken. 假定采用“向后”条件分支,而不采用“向前”条件分支。 The improvement here is that loops (which jump backwards in the execution stream) will be correctly predicted most of the time. 此处的改进是循环(在执行流中向后跳)将在大多数时间正确预测。 This is the static branch-prediction strategy used by most Intel x86 processors, up to about Sandy Bridge. 这是大多数Intel x86处理器使用的静态分支预测策略,直至Sandy Bridge为止。

    Because this strategy was used for so long, the standard advice was to arrange your if statements accordingly: 由于此策略使用了很长时间,因此标准建议是相应地安排您的if语句:

     if (condition) { // most likely case } else { // least likely case } 

    This possibly looks counter-intuitive, but you have to go back to what the machine code looks like that this C# code will be transformed into. 这看起来可能违反直觉,但是您必须回到将C#代码转换为机器代码的样子。 Compilers will generally transform the if statement into a comparison and a conditional branch into the else block. 编译器通常会将if语句转换为比较,将条件分支转换为else块。 This static branch prediction algorithm will predict that branch as "not taken", since it's a forward branch. 该静态分支预测算法会将该分支预测为“未采用”,因为它是前向分支。 The if block will just fall through without taking the branch, which is why you want to put the "most likely" case there. if块将不通过分支而通过,这就是为什么要在其中放置“最可能”的情况的原因。

    If you get into the habit of writing code this way, it might have a performance advantage on certain processors, but it's never enough of an advantage to sacrifice readability. 如果您习惯以这种方式编写代码,那么它在某些处理器上可能具有性能上的优势,但是牺牲可读性永远是不够的。 Especially since it only matters the first time the code is executed (after that, dynamic branch prediction kicks in), and executing code for the first time is always slow in a JIT-compiled language! 特别是因为它仅在一次执行代码时才重要(此后,动态分支预测开始),并且第一次使用JIT编译语言执行代码总是很慢!

  • Always use the dynamic predictor's result, even for never-seen branches. 即使对于从未见过的分支,也始终使用动态预测器的结果。

    This strategy is pretty strange, but it's actually what most modern Intel processors use (circa Ivy Bridge and later). 这种策略很奇怪,但是实际上是大多数现代英特尔处理器使用的方法(大约在Ivy Bridge和更高版本中)。 Basically, even though the dynamic branch-predictor may have never seen this branch and therefore may not have any information about it, the processor still queries it and uses the prediction that it returns. 基本上,即使动态分支预测器可能从未见过该分支,因此可能没有任何有关该分支的信息,但处理器仍会查询该分支并使用它返回的预测。 You can imagine this as being equivalent to an arbitrary static-prediction algorithm. 您可以想象这等同于任意静态预测算法。

    In this case, it absolutely does not matter how you arrange the conditions of an if statement, because the initial prediction is essentially going to be random. 在这种情况下,如何安排if语句的条件绝对不重要,因为初始预测本质上将是随机的。 Some 50% of the time, you'll pay the penalty of a mispredicted branch, while the other 50% of the time, you'll benefit from a correctly predicted branch. 大约有50%的时间,您将支付分支预测错误的罚款,而其余50%的时间,您将从正确预测的分支中受益。 And that's only the first time—after that, the odds get even better because the dynamic predictor now has more information about the nature of the branch. 这只是第一次,在那之后,可能性更大,因为动态预测器现在具有有关分支性质的更多信息。

This answer has already gotten way too long, so I'll refrain from discussing static prediction hints (implemented only in the Pentium 4) and other such interesting topics, bringing our exploration of branch prediction to a close. 这个答案已经得到太多时间,所以我会从讨论静态预测提示等这样的有趣的话题(仅在奔腾4实现),使我们的分支预测的探索密切避免。 If you're interested in more, examine the CPU vendor's technical manuals (although most of what we know has to be empirically determined), read Agner Fog's optimization guides (for x86 processors), search online for various white-papers and blog posts, and/or ask additional questions about it. 如果您有更多兴趣,请查看CPU供应商的技术手册(尽管我们知道的大多数内容都需要凭经验确定),阅读Agner Fog的优化指南 (针对x86处理器),在线搜索各种白皮书和博客文章,和/或提出其他问题。

The takeaway is probably that it doesn't matter, except on processors that use a certain static branch-prediction strategy, and even there, it hardly matters when you're writing code in a JIT-compiled language like C# because the first-time compilation delay exceeds the cost of a single mispredicted branch (which may not even be mispredicted). 收获可能是没有关系,除了在使用某种静态分支预测策略的处理器上,甚至在那里,当您使用像C#这样的JIT编译语言编写代码时,也没有关系,因为这是第一次编译延迟超过了单个错误预测分支(甚至可能不会被错误预测)的代价。

Same issue when validating parameters to functions. 验证函数的参数时出现相同的问题。

It's much cleaner to act like a night-club bouncer, kicking the no-hopers out as soon as possible. 像夜总会的保镖一样工作,这样会更加干净,尽快将那些没有要求的人赶出去。

  public void aMethod(SomeParam p)
  {
     if (!aBoolean || p == null) 
         return;

     // Write code in the knowledge that everything is fine
  }

Letting them in only causes trouble later on. 让它们进入只会在以后造成麻烦。

  public void aMethod(SomeParam p)
  {
     if (aBoolean) 
     {
         if (p != null)
         {
             // Write code, but now you're indented 
             // and other if statements will be added later
         }

         // Later on, someone else could add code here by mistake.
     }

     // or here...
  }

The C# language prioritizes safety (bug prevention) over speed. C#语言优先考虑安全性(错误预防)而不是速度。 In other words, almost everything has been slowed down to prevent bugs, one way or another. 换句话说,几乎所有内容都已通过某种方式放慢速度以防止错误。 If you need speed so badly that you start worrying about if statements, then perhaps a faster language would suit your purposes better, possibly C++ 如果您非常需要速度,以至于开始担心if语句,那么也许更快的语言会更好地适合您的目的,可能是C ++

Compiler writers can and do make use of statistics to optimize code, for example "else clauses are only executed 30% of the time". 编译器编写者可以并且确实利用统计信息来优化代码,例如“其他子句仅在30%的时间内执行”。

However, the hardware guys probably do a better job of predicting execution paths. 但是,硬件人员可能在预测执行路径方面做得更好。 I would guess that these days, the most effective optimizations happen within the CPU, with their L1 and L2 caches, and compiler writers don't need to do a thing. 我猜想这些天来,最有效的优化发生在CPU的L1和L2缓存中,并且编译器编写者不需要做任何事情。

As [~Dark Falcon] mentioned you should not be concerned by micro optimization of little bits of code, the compiler will most probably optimize both approaches to the same thing. 正如[〜Dark Falcon]提到的那样,您不必担心代码的一点点微优化,编译器很可能会针对同一事物优化这两种方法。

Instead you should be very concerned about your program maintainability and ease of read 相反,您应该非常关注程序的可维护性易读性

From this perspective you should choose B for two reasons: 从这个角度来看,您应该选择B有两个原因:

  1. It only has one exit point (just one return) 它只有一个出口点(只有一个出口)
  2. The if block is surrounded by curly braces if块被花括号包围

edit But hey! 编辑但是,嘿! as told in the comments that is just my opinion and what I consider good practices 正如评论中所说的,这只是我的看法,也是我认为的良好做法

I am just curious about how each piece of each pattern is implemented by the compiler, eg for arguments sake, if it was compiled verbatim with no compiler optimizations, which would be more efficient? 我只是对每个模式的每个部分如何由编译器实现感到好奇,例如出于参数考虑,如果它是逐字编译而没有编译器优化,那会更有效吗?

The best way to test efficiency in this way is to run benchmarks on the code samples you're concerned with. 以此方式测试效率的最佳方法是在您关注的代码样本上运行基准测试。 With C# in particular it is not going to be obvious what the JIT is doing with these scenarios. 特别是对于C#,在这些情况下JIT所做的事情并不明显。

As a side note, I throw in a +1 for the other answers that point out that efficiency isn't only determined at the compiler level - code maintainability involves magnitudes of levels of efficiency more than what you'll get from this specific sort of pattern choice. 作为一个方面说明,我扔在一个+1为指出,效率不仅在编译器级别确定其他的答案-代码的可维护性涉及的效率水平的幅度超过什么,你会从这个特定形式的获得模式选择。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM