哪个性能更高:< = 0或< 1?

时间:2013-01-17 17:10:01

标签: c# performance

在我学习C和装配的那天,我们被教导,最好使用简单的比较来提高速度。例如,如果你说:

if(x <= 0)

if(x < 1)

哪个会执行得更快?我的论点(可能是错误的)是第二个几乎总是执行得更快,因为只有一个比较),即小于一,是或否。

如果数字小于0,则第一个将快速执行,因为这等于为真,不需要检查等于使得它与第二个一样快,但是,如果数字为0,则总是会更慢或​​者更多,因为它必须进行第二次比较,看它是否等于0。

我现在正在使用C#,而开发台式机时速度不是问题(至少没有达到他的观点值得争论的程度),我仍然认为这些论点需要考虑,因为我也在为移动设备开发它的功能远远低于台式机,速度确实成为这类设备的一个问题。

为了进一步考虑,我说的是整数(没有小数)和数字,其中不能有负数如-1或-12,345 等(除非有错误),例如,当你处理列表或数组时,你不能有一个负数的项目,但你想检查列表是否为空(或如果有问题,将x的值设置为负以指示错误,一个例子是在那里是列表中的一些项目,但由于某种原因您无法检索整个列表,并指出您将数字设置为负数,这与说没有项目不一致。

由于上述原因,我特意遗漏了明显的

if(x == 0)

if(x.isnullorempty())

以及用于检测没有项目的列表的其他此类项目。

同样,为了考虑,我们讨论的是从数据库中检索项目的可能性,可能使用具有上述功能的SQL存储过程(即标准(至少在该公司中)是返回负数来表示问题)。

所以在这种情况下,使用上面的第一个或第二个项目会更好吗?

9 个答案:

答案 0 :(得分:19)

他们是完全相同的。两者都不比另一个快。假设x是一个整数,他们都会问同样的问题。 C#不是汇编。您要求编译器生成最佳代码以获得您要求的效果。您没有具体说明如何获得该结果。

另见this answer

  

我的论点(可能是错的)是第二个几乎总是执行得更快,因为只有一个比较),即小于一,是或否。

显然这是错的。看看如果你认为这是真的会发生什么:

<<=快,因为它问的问题较少。 (你的论点。)

><=的速度相同,因为它会回答相同的问题,只是反复回答。

因此<>快!但同样的论点表明><更快。

  

“只是一个倒置的答案”似乎潜入了一个额外的布尔操作,所以我不确定我是否遵循这个答案。

出于同样的原因,这是错误的(对于硅,它有时对软件是正确的)。考虑:

3 != 4的计算成本比3 == 4贵,因为3 != 4的答案是倒置的,是一个额外的布尔操作。

3 == 43 != 4贵,因为3 != 4的答案是倒置的,是一个额外的布尔操作。

因此,3 != 4比自己贵。

反向回答恰恰相反,不是额外的布尔运算。或者,为了更精确,它使用不同的比较结果到最终答案的映射。 3 == 43 != 4都要求您比较3和4.该比较会导致以太“等于”或“不等于”。问题只是将“平等”和“不平等”映射到“真实”和“假”不同。两种映射都不比另一种更昂贵。

答案 1 :(得分:6)

至少在大多数情况下,不,一个人没有优势。

<=通常不会被实现为两个单独的比较。在典型的(例如,x86)CPU上,您将有两个单独的标志,一个用于表示相等,另一个用于表示负数(也可以表示“小于”)。除此之外,您将拥有依赖于这些标志组合的分支,因此<会转换为jljb(如果更少则跳转或跳转到下方 - 前者用于签名的数字,后者用于未签名的数字)。 <=会转换为jlejbe(如果小于或等于则跳转,如果低于或等于则跳转)。

不同的CPU将使用不同的名称/助记符作为指令,但大多数仍具有相同的指令。在我所知道的每一种情况下,所有这些都以相同的速度执行。

编辑:哎呀 - 我想提一下我上面提到的一般规则的一个可能的例外。虽然它并非完全来自<<=,但如果/当您可以与0进行比较而不是任何其他数字时,您有时可以获得一些(微不足道的)优势。例如,让我们假设你有一个变量,你要倒数,直到达到最低限度。在这种情况下,如果你可以倒数到0而不是倒数到1,你可能会获得一点优势。原因很简单:我之前提到的标志受大多数指令的影响。我们假设你有类似的东西:

   do {
       // whatever
   } while (--i >= 1);

编译器可能会将其翻译为:

loop_top:
    ; whatever

    dec i
    cmp i, 1
    jge loop_top

相反,如果您将其与0(while (--i > 0)while (--i != 0))进行比较,则可能会转换为类似的内容;

loop_top:
    ; whatever

    dec i
    jg loop_top
    ; or: jnz loop_top

这里dec设置/清除零标志以指示减量的结果是否为零,因此条件可以直接基于dec的结果,从而消除{ {1}}在其他代码中使用。

但是,我应该补充说,虽然这很有效,比如说,30多年前,大多数现代编译器都可以在没有你帮助的情况下处理这样的翻译(尽管有些编译器可能没有,特别是像小型嵌入式系统这样的东西)。 IOW,如果你关心一般的优化,你几乎可能有一天会关心 - 但至少对我而言,对C#的应用似乎充其量是可疑的。

答案 2 :(得分:5)

大多数现代硬件都有内置指令,用于在单个指令中检查小于或等于的指令,其执行速度与检查小于<的指令完全一样快/ em>条件。应用于(更多)旧硬件的论点不再适用 - 选择您认为最易读的替代方案,即更好地将您的想法传达给代码读者的方案。

答案 3 :(得分:4)

以下是我的功能:

public static void TestOne()
{
    Boolean result;
    Int32 i = 2;

    for (Int32 j = 0; j < 1000000000; ++j)
        result = (i < 1);
}

public static void TestTwo()
{
    Boolean result;
    Int32 i = 2;

    for (Int32 j = 0; j < 1000000000; ++j)
        result = (i <= 0);
}

这是IL代码,它是相同的:

L_0000: ldc.i4.2 
L_0001: stloc.0 
L_0002: ldc.i4.0 
L_0003: stloc.1 
L_0004: br.s L_000a
L_0006: ldloc.1 
L_0007: ldc.i4.1 
L_0008: add 
L_0009: stloc.1 
L_000a: ldloc.1 
L_000b: ldc.i4 1000000000
L_0010: blt.s L_0006
L_0012: ret 

经过几次测试后,显然结果是两者都没有比另一个更快。差异仅在几毫秒内不能被认为是真正的差异,并且产生的IL输出无论如何都是相同的。

答案 4 :(得分:3)

ARM和x86处理器都会有“小于”和“小于或等于”的专用指令(也可以评估为“不大于”),所以如果你真的没有现实世界的区别使用任何半现代编译器。

答案 5 :(得分:1)

在重构时,如果你改变主意逻辑,if(x<=0) 更快(并且更不容易出错)否定(即if(!(x<=0)),与{{1}相比这不能正确否定)但这可能不是你所指的表现。 ; - )

答案 6 :(得分:0)

如果x<1更快,那么现代编译器会将x<=0更改为x<1(假设x是一个整数)。因此对于现代编译器而言,这应该无关紧要,它们应该生成相同的机器代码。

答案 7 :(得分:0)

即使x <= 0被编译为不同于x <1的指令,性能差异也是如此微不足道,以至于大部分时间都不值得担心;您的代码中很可能会有其他更高效的区域进行优化。黄金法则是对您的代码进行分析并优化在现实世界中实际上较慢的位,而不是您认为假设可能很慢的位,或者不是理论上可能的速度。还要专注于让你的代码对其他人可读,而不是在一阵编译器烟雾中消失的幻像微优化。

答案 8 :(得分:0)

@Francis Rodgers,你说:

  

如果数字小于0,则第一个将快速执行   因为这相当于真,所以没有必要检查等于   尽管如此,它会像第二个一样快,但总是会慢一些   数字是0或更多,因为它必须进行第二次比较   看它是否等于0。

和(在评论中),

  

你能解释一下&gt;与&lt; =相同,因为这没有   我逻辑世界的感觉。例如,&lt; = 0与&gt; 0 in不同   事实上,完全相反。我想举个例子,我可以   更好地理解你的答案

你寻求帮助,你需要帮助。我真的很想帮助你,我担心很多其他人也需要这个帮助。

从更基本的事情开始。您的想法是测试&gt;与测试&lt; =逻辑错误(不仅仅是在任何编程语言中)不同。看看这些图表,放松一下,思考一下。如果你知道A和B中的X <= Y,会发生什么?如果您知道X&gt;会发生什么?每个图中的Y? enter image description here 对,没什么变化,它们是等价的。图表的关键细节是A和B中的truefalse位于相对的两侧。这意味着编译器(或者一般来说 - 编码器)可以自由地重新组织程序流,使得两个问题都是等价的。这意味着,没有必要将&lt; =分成两个步骤,只有在你的流程中重新组织一点。只有非常糟糕的编译器或解释器才能做到这一点。与任何汇编程序无关。我们的想法是,即使对于没有足够标志进行所有比较的CPU,编译器也可以使用最适合其特性的测试生成(伪)汇编代码。但是增加了CPU在电子级并行检查多个标志的能力,编译器的工作非常简单。 您可能会发现好奇/有趣的阅读3-14到3-15和5-3到5-5页(最后一个包含跳转说明可能会让您感到惊讶)http://download.intel.com/products/processor/manual/325462.pdf

无论如何,我想更多地讨论相关情况。

与0或1相比:@Jerry Coffin在汇编程序级别有一个非常好的解释。深入研究机器代码级别的变量与1进行比较需要将1“硬编码”到CPU指令中并将其加载到CPU中,而另一种变体设法不这样做。无论如何,这里的收益绝对很小。我不认为在任何真实的现场情况下它的速度都是可测量的。作为旁注,指令cmp i, 1将只是一种减法i-1(不保存结果)但设置标志,你最终实际比较为0 !!

更重要的可能是这种情况:比较X<=YY>=X显然是逻辑上等效的,但如果XY与{{1}}表达,则会产生严重的副作用需要进行评估,并可能影响对方的结果!非常糟糕,可能未定义。

现在,回到图表,看看@Jerry Coffin的汇编示例。我在这里看到以下问题。真正的软件是内存中的一种线性链。您选择其中一个条件并跳转到另一个程序存储器位置继续,而相反的只是继续。选择更频繁的条件成为继续的条件是有意义的。我没有看到我们如何在这些情况下给编译器一个提示,显然编译器无法弄清楚它自己。如果我错了,请纠正我,但这些优化问题非常普遍,程序员必须在没有编译器帮助的情况下自行决定。

但同样,在任何情况下,我都会编写代码,查看一般的静态和可读性,而不是这些局部的小优化。