1

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

if(x <= 0)

相对

if(x < 1)

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

如果数字小于 0,则第一个将快速执行,因为这等于 true,无需检查等号使其与第二个一样快,但是,如果数字为 0 或更大,它总是会变慢,因为它然后必须进行第二次比较以查看它是否等于 0。

我现在使用 C#,虽然为台式机开发速度不是问题(至少在他的观点值得争论的程度上),但我仍然认为需要考虑这些论点,因为我也在为移动设备开发不如台式机强大,速度确实成为此类设备上的一个问题。

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

由于上述原因,我故意省略了明显的

if(x == 0)

if(x.isnullorempty())

和其他用于检测没有项目的列表的项目。

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

那么在这种情况下,使用上面的第一项还是第二项更好呢?

4

9 回答 9

19

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

另请参阅此答案

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

显然这是错误的。如果您认为这是真的,请注意会发生什么:

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

>速度相同,<=因为它提出了相同的问题,只是答案相反。

从而<>!但同样的论点表明>它比<.

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

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

3 != 4计算起来比 更昂贵3 == 4,因为它3 != 4有一个反向的答案,一个额外的布尔运算。

3 == 4比 更昂贵3 != 4,因为它3 != 4有一个反向的答案,一个额外的布尔运算。

因此,3 != 4比它本身更昂贵。

反向答案只是相反的问题,而不是额外的布尔运算。或者,更准确地说,比较结果与最终答案的映射不同。两者都3 == 4要求3 != 4您比较 3 和 4。该比较的结果是以太“相等”或“不相等”。这些问题只是将“相等”和“不相等”以不同的方式映射到“真”和“假”。两种映射都不比另一种更昂贵。

于 2013-01-17T17:12:20.673 回答
6

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

A<=通常不会作为两个单独的比较来实现。在典型的(例如,x86)CPU 上,您将有两个单独的标志,一个表示相等,一个表示负数(也可以表示“小于”)。除此之外,您将拥有依赖于这些标志组合的分支,因此<转换为jljb(如果小于则跳转或如果低于则跳转——前者用于有符号数,后者用于无符号数)。A<=将转换为jlejbe(如果小于或等于则跳转,如果低于或等于则跳转)。

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

编辑:糟糕——我的意思是提到我上面提到的一般规则的一个可能的例外。虽然它不完全来自<vs. <=,但如果/当您可以比较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,消除cmp其他代码中使用的。

然而,我应该补充一点,虽然这在 30 多年前非常有效,但大多数现代编译器可以在没有你帮助的情况下处理这样的翻译(尽管有些编译器可能不会,尤其是对于小型嵌入式系统之类的东西)。IOW,如果您总体上关心优化,那么您几乎不可能有朝一日会关心——但至少对我来说,C# 的应用似乎充其量是值得怀疑的。

于 2013-01-17T17:16:19.303 回答
5

大多数现代硬件都有内置指令,用于在单个指令中检查小于或等于条件,该指令的执行速度与检查小于条件的指令完全相同。适用于(很多)旧硬件的论点不再适用 - 选择您认为最具可读性的替代方案,即可以更好地将您的想法传达给代码读者的替代方案。

于 2013-01-17T17:13:29.897 回答
4

Here are my functions:

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

Here is the IL code, which is identical:

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 

After a few testing sessions, obviously, the result is that neither is faster than the other. The difference consists only in few milliseconds which can't be considered a real difference, and the produced IL output is the same anyway.

于 2013-01-17T17:27:49.047 回答
3

ARM 和 x86 处理器都将具有“小于”和“小于或等于”的专用指令(也可以评估为“不大于”),因此如果您使用任何半现代编译器。

于 2013-01-17T17:14:22.350 回答
1

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

于 2013-01-17T17:21:50.977 回答
0

@Francis Rodgers,你说:

如果数字小于 0,则第一个将快速执行,因为这等于 true,因此无需检查等于,使其与第二个一样快,但是,如果数字为 0 或更大,它总是会变慢,因为它然后必须进行第二次比较以查看它是否等于 0。

和(在评论中),

你能解释一下 > 在哪里与 <= 相同,因为这在我的逻辑世界中没有意义。比如<=0其实和>0是不一样的,完全相反。我只想举个例子,这样我就能更好地理解你的答案

你寻求帮助,你需要帮助。我真的很想帮你,恐怕还有很多人也需要这个帮助。

从更基本的事情开始。您认为测试 > 与测试 <= 不同的想法在逻辑上是错误的(不仅在任何编程语言中)。看看这些图表,放松一下,思考一下。如果您知道 A 和 B 中的 X <= Y 会发生什么?如果您知道每个图中的 X > Y 会发生什么? 在此处输入图像描述 对,没有任何改变,它们是等价的。true图表的关键细节是false在 A 和 B 中位于相反的两侧。其含义是编译器(或通常 - 解码器)可以自由地以两个问题等效的方式重组程序流程。这意味着,不需要将 <= 拆分为两个步骤,只需重新组织一点点在你的流程中。只有非常糟糕的编译器或解释器才能做到这一点。与任何汇编程序无关。这个想法是,即使对于所有比较没有足够标志的 CPU,编译器也可以使用最适合其特性的测试生成(伪)汇编代码。但是增加 CPU 在电子级别并行检查多个标志的能力,编译器的工作要简单得多。http://download.intel.com/products/processor/manual/325462.pdf

无论如何,我想讨论更多有关相关情况的信息。

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

更重要的可能是这种情况:compare X<=Yor with 显然在逻辑上是等价的,但是如果和是表达式 with 需要被评估并且可能影响另一个的结果,Y>=X这可能会产生严重的副作用!仍然非常糟糕,并且可能未定义。XY

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

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

于 2013-01-18T11:05:29.650 回答
0

Even if x<=0 compiled to different instructions than x<1, the performance difference would be so miniscule as to not be worth worrying about most of the time; there will very likely be other more productive areas for optimizations in your code. The golden rule is to profile your code and optimise the bits that ARE actually slow in the real world, not the bits that you think hypothetically may be slow, or are not as fast as they theoretically could be. Also concentrate on making your code readable to others, and not phantom micro-optimisations that disappear in a puff of compiler smoke.

于 2013-01-17T17:27:53.530 回答
0

IFx<1更快,那么现代编译器将更x<=0改为x<1(假设x是积分)。所以对于现代编译器来说,这应该无关紧要,它们应该产生相同的机器代码。

于 2013-01-17T17:13:52.770 回答