在C#中,>0和>=1哪个更快、更好?

21

在C#中, >0和>=1哪个更快、更好?


10
学究来了。我假设你指的是整数。对于浮点数,情况并不相同 :P - Jesse Millikan
45
>0更快是因为它少输入了一个字符。 - D'Arcy Rittich
12
不明白为什么这个问题被关闭,它是一个有正确答案的编程问题。虽然不是非常聪明的问题,但这并不意味着它不是一个问题。 - Billy ONeal
8
我同意这个问题不应该关闭。比如,在汇编语言中,你可以使用 cmp eax, ebx 后跟 jg <label>jge <label>。据我所知,以前 jge(如果大于或等于则跳转)操作在汇编中的开销要比 jg(如果大于则跳转)更大,但是(1)我认为现在已经不是这样了,而且(2)编译器应该能够进行优化。尽管如此,我认为这个问题很有价值,因为它涉及到底层的工作原理。 - CtrlDot
2
我绝对同意,请重新开放这个问题。显然,仅从答案中就可以看出还有更多的东西。 - Hardryv
显示剩余5条评论
11个回答

52

这两者是一样的,如果其中一个更快或更好,它们都应该编译成相同的东西。

更重要的是,大多数程序员可能会认为 > 0 更易读,而可读性比这种微小的优化更重要。


9
感谢您指出可读性的重要性,我给您点个赞。 - Matt
9
关于你的可读性断言,我不太确定;我将">= 1"解读为"1或更多",这是比"大于零"更自然的定义条件的方式。然而,在对话中,“大于五”也完全有意义。我认为可读性取决于上下文以及开发人员在编码的同时查看的任何要求文档或用户指南的存在和措辞。它还严重依赖于所涉及的数学类型;正如Jesse Millikan所说,两种比较在浮点数世界中并不等价。 - KeithS
@KiethS:我理解>0的意思是“正非零整数”。而读取>=1则需要我考虑整数没有小数点右边的数字来决定。是的,这两个表达式并不相同;尽管我以前没有做过大量的浮点数程序。是否有>=1更好的情况?当然有。但考虑到问题的写法,我认为“正非零整数”是意图。 - Billy ONeal
有一件事情让我想起来了。在考虑二进制的情况下,>=4比>3更好或更快吗?;) - Caspar Kleijne
>= 0> -1 呢?前者表示“至少为零”,后者表示“非负数”。 - Aaron Franke

30

更好的那一个是最能清晰地表达您的意图的那一个。

如果你要测试一个整数是否在区间[1,6]内,那么应该写成:

 if (x >= 1 && x <= 6) { ... }
写下这段代码看起来是可以工作的,但显然不能完全符合规范:
 if (x > 0 && x < 7) { ... }

我还假设您在这里谈论的是整数类型。如果您正在处理浮点或十进制数字,则它们不等同。


除非您对代码进行了剖析并发现它是瓶颈,否则您不应该担心微小优化。即使如此,检查C#编译器在每种情况下生成的代码是否编译为相同的IL可能会很有趣。可以使用.NET Reflector实现此目的。

if (x >= 1)
{
    Console.WriteLine("True!");
}

结果为:

L_000b: ldloc.0          // Load the value of x
L_000c: ldc.i4.1         // Load the constant 1 
L_000d: blt.s L_0019     // Branch if less than
L_000f: ldstr "True!"
L_0014: call void [mscorlib]System.Console::WriteLine(string)

鉴于:

if (x > 0)
{
    Console.WriteLine("True!");
}

结果会产生以下IL代码:

L_000b: ldloc.0          // Load the value of x
L_000c: ldc.i4.0         // Load the constant 0
L_000d: ble.s L_0019     // Branch if less than or equal
L_000f: ldstr "True!"
L_0014: call void [mscorlib]System.Console::WriteLine(string)
无论哪种情况,编译器都反转了比较条件。"大于或等于"测试被编译为"小于"指令,而"大于"测试被编译为"小于或等于"指令。通常编译器可以自由地进行这样的修改,如果使用不同版本的编译器可能会产生不同但等效的字节码。
考虑到它们不能编译成相同的中间语言代码,最好的方法是在一个循环中运行这段代码,并查看每个版本执行所需的时间。我尝试过这样做,但是我并没有看到两种写法之间有任何可度量的性能差异。

我同意!这是语句所期望的... > 0 和 >=1 真的做了两件不同(有时相同)的事情...你要么想要全部,要么只想要其中一部分,而不是一个都不要! - Saif Khan
我喜欢你展示了IL,特别是当它在字节码中切换比较时。 - Nick

12

未定义。您明确要求C# - 但这取决于处理器架构,即CLR运行时编译器。


1
我想这对大多数编程语言都是正确的。 - Andy
...而更好的是由上下文中更易读的内容来决定。 - Karmic Coder

7

我同意其他回答中的观点,通常不应考虑微观优化。但是,了解哪个版本具有更小/明显更快的IL可能会很有趣。

因此:

using System;

namespace IL_Test
{
    class Program
    {
        static void Main(string[] args)
        {
            int i = 3;
            if (i > 0)
            {
                Console.Write("i is greater than zero");
            }
        }
    }
}

翻译为:

(调试)

.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 2
    .locals init (
        [0] int32 i,
        [1] bool CS$4$0000)
    L_0000: nop 
    L_0001: ldc.i4.3 
    L_0002: stloc.0 
    L_0003: ldloc.0 
    L_0004: ldc.i4.0 
    L_0005: cgt 
    L_0007: ldc.i4.0 
    L_0008: ceq 
    L_000a: stloc.1 
    L_000b: ldloc.1 
    L_000c: brtrue.s L_001b
    L_000e: nop 
    L_000f: ldstr "i is greater than zero"
    L_0014: call void [mscorlib]System.Console::Write(string)
    L_0019: nop 
    L_001a: nop 
    L_001b: ret 
}

(发布)

.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 2
    .locals init (
        [0] int32 i)
    L_0000: ldc.i4.3 
    L_0001: stloc.0 
    L_0002: ldloc.0 
    L_0003: ldc.i4.0 
    L_0004: ble.s L_0010
    L_0006: ldstr "i is greater than zero"
    L_000b: call void [mscorlib]System.Console::Write(string)
    L_0010: ret 
}

while

using System;

namespace IL_Test
{
    class Program
    {
        static void Main(string[] args)
        {
            int i = 3;
            if (i >= 1)
            {
                Console.Write("i is greater than zero");
            }
        }
    }
}

进入

(调试)

.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 2
    .locals init (
        [0] int32 i,
        [1] bool CS$4$0000)
    L_0000: nop 
    L_0001: ldc.i4.3 
    L_0002: stloc.0 
    L_0003: ldloc.0 
    L_0004: ldc.i4.1 
    L_0005: clt 
    L_0007: stloc.1 
    L_0008: ldloc.1 
    L_0009: brtrue.s L_0018
    L_000b: nop 
    L_000c: ldstr "i is greater than zero"
    L_0011: call void [mscorlib]System.Console::Write(string)
    L_0016: nop 
    L_0017: nop 
    L_0018: ret 
}

(发布)

.method private hidebysig static void Main(string[] args) cil managed
{
    .entrypoint
    .maxstack 2
    .locals init (
        [0] int32 i)
    L_0000: ldc.i4.3 
    L_0001: stloc.0 
    L_0002: ldloc.0 
    L_0003: ldc.i4.1 
    L_0004: blt.s L_0010
    L_0006: ldstr "i is greater than zero"
    L_000b: call void [mscorlib]System.Console::Write(string)
    L_0010: ret 
}

据我所见,在调试模式下,i >= 1 稍微比 i > 0 快一些。
在发布模式下,唯一的区别在于偏移量0004处有一个BLE和BLT之间的差异。我猜这两个IL操作会转化为同样消耗CPU的本地操作。

这些是交换了的还是我漏掉了什么? - Caspar Kleijne
2
你说其中一个稍微快一点 - 为什么不发布你的基准测试结果呢?你测试用的机器是什么?进行了哪些优化?优化是由JIT完成的,而不是由C#编译器完成的,因此查看生成的IL对性能并没有太大帮助。 - Niki
2
@Caspar:第一个代码块的意思是:如果(i>0)等于false,则返回(不执行console.write)。第二个代码块的意思是:如果(i<1)等于true,则返回。Andrei的测试生成的IL代码比Mark Byers发现的要冗长得多。其他人提出的观点是:运行它,进行基准测试,问一下它是否真的比较慢?这里的代码可能是DEBUG编译的,或者使用另一个版本的编译器...我们不知道。是的,JIT可能会进行更多的优化,也可能不会。进行基准测试。 - maxwellb
@maxwellb。感谢您的回复!您真的让我更清楚了;) - Caspar Kleijne
是的,两个版本都是在DEBUG模式下编译的。我很快会添加RELEASE(优化)IL版本。 - Andrei Rînea
我最初忘记发布优化编译的IL。但是你可以清楚地看到IL的第一个版本是调试的,因为出现了周期性的NOPs。 - Andrei Rînea

7

这两种方式的性能差异可以忽略不计(如果有的话)。我正在努力证明它可能是什么(因为任何差异可能归结于JIT发出和执行的代码,所以这将依赖于平台)。

但请记住,从性能上来说,这是一种极端微观优化,很可能是没有必要的。

更好的选择是在你的代码中选择哪种更易读并传达你的意图。


4
当然,这取决于您的程序将在哪种CPU架构上运行。 在x86上,与此相关的jgejg指令需要相同数量的周期(如果我没记错的话)。 在测试>0的特定情况下,如果您使用的是无符号整数,则使用test指令而不是cmp可能会更快,因为对于无符号整数,>0等同于!= 0(我真的不知道)。 其他架构可能会有所不同。 关键是这是如此低级别的优化,即使在很少数情况下值得优化,也没有硬件独立的方式来优化它。
编辑:忘记提到:任何值得一试的编译器或VM都应该能够发现测试>= 1等同于测试>0,并在汇编语言级别执行这样微不足道的优化,即使它在这个层面上有所不同。

2

由于 CPU 内部执行两个数字的减法并检查结果和溢出,因此这两个指令没有任何区别。对于任一指令,都不需要额外的步骤。

当涉及到代码时,取决于您要记录的内容。>= 1 表示 1 是最低可能的数字。> 0 表示不允许为 0。这有一个小的语义差异,专业人士会注意选择正确的操作符来记录他们的意图。

如果您认为 >= n 和 >= n + 1 是相同的,那么您是错误的:>= int.MaxValue 和 > (int.MaxValue + 1) 是不同的^^


1
回答更快的那个问题,我不确定,但我认为它们是等价的。至于更好的那个问题,我认为这取决于上下文。

0

除非在应用程序中非常紧密的循环中,否则您不会注意到任何差异,这可能会影响性能。然后,您需要对代码进行分析以决定哪个更好。

在您的应用程序中使用最合适的那一个。


0
通常当我将某些东西与> 0或> = 1进行比较时,我试图查看数组/集合是否包含任何元素。 如果是这种情况,而不是使用.Count > 0,请尝试在System.Linq中使用帮助程序方法Enumerable.Any(),这应该会更快。

否则,我不知道 :)


为什么 Any() 会更快?我认为它会更慢,因为 Count 只是一个属性访问器,而 Any() 必须枚举序列的第一个项。 - We Are All Monica
@jnylen:这取决于你使用的计数方式。Enumerable.Count()Enumerable.Any()要慢得多。如果你的容器有一个Count属性(例如List),那么是的,你应该使用它。 - Billy ONeal
2
我认为 .Any() 会在集合支持该成员时评估为 .Count > 0。而且当集合不支持它时,它总是比 .Count() 更快。不确定它编译成什么 IL,所以可能没有太大帮助... - Zachary Yates

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接