加倍数 – 左移与乘法

有什么区别

int size = (int)((length * 200L) / 100L); // (1) 

 int size = length << 1; // (2) 

(两种情况下长度均为int)

我假设两个代码片段都想要加倍长度参数。

我很想使用(2)……所以使用(1)有什么好处吗? 我查看溢出发生时的边缘情况,两个版本似乎都有相同的行为。

请告诉我我错过了什么。

这是第三种选择:

 int size = length * 2; // Comment explaining what is 2 or what means this multiplication 

这一定是最好的选择。 因为它易读且易于理解您想要做什么。 至于性能,编译器正在生成相当优化的代码,因此无需担心这么简单的操作。 如果您对溢出有任何疑虑,可以使用checked block。

编辑正如许多其他人所提到的,只需使用任何有意义的变量而不是2

认为<<比乘法更快的想法是因为.NET jit编译器实际上是一个在1970年代写的不太优化的C编译器。 即使它是真的,这个时间点的差异将以皮秒为单位,即使存在差异,也可能没有。

编写代码以便于阅读 。 让编译器处理微微优化。 基于剖析现实场景优化代码,而不是第二次猜测编译器将生成什么。

此外,移位运算符具有与乘法相同的语义。 例如,请考虑以下编辑顺序:

Jill的原创节目:

 int x = y * 2; 

鲍勃编辑:傻吉尔,我会让这个“更快”:

 int x = y << 1; 

由实习生拉里编辑:哦,我们有一个错误,我们一个接一个,让我解决这个问题:

 int x = y << 1 + 1; 

而拉里刚刚推出了一个新的bug。 y * 2 + 1与y << 1 + 1不同; 后者实际上是y * 4。

我在实际的实时生产代码中看到了这个bug 。 精神上很容易进入“转移是乘法”的思维模式,忘记转移的优先级低于加法,而乘法则优先级更高

我从来没有见过有人通过写x * 2得到算术优先级错误乘以2。人们理解+和*的优先级。 很多人都忘记了转变的优先顺序。 您实际上没有节省的皮秒是否值得任何数量的潜在错误? 我拒绝。

这对普通程序员来说更具可读性:

 int size = length * 2; int size = length << 1; 

除非它们来自强大的C ++位调整背景,否则我会打赌你的普通程序员会立即知道第一行是什么(它甚至有“2”代表“double”)但是必须停止并暂停第二行线。

事实上,我倾向于评论第二行,解释它的作用,当你可以让代码像第一行那样进行说话时,这似乎是多余的。

有趣的是,大多数答案都认为编译器会优化乘以2的幂乘以比特移位。 很明显,没有一个响应者真的尝试过编译一个bithift而不是一个乘法来看看编译器实际产生了什么。

这纯粹是一种学术活动; 正如几乎所有人都指出的那样,乘法更容易阅读(尽管为什么“* 200L / 100L”部分存在于任何人的猜测中 – 这只是用来混淆事物)。 很明显,即使在紧密的循环中,用C#中的bitshift替换乘法也不会有任何显着的性能提升。 如果您需要这种优化,那么您使用的是错误的平台和语言。

让我们看看当我们使用Visual Studio 2010附带的CSC(C#编译器)编译一个简单的程序并启用了优化时会发生什么。 这是第一个程序:

 static void Main(string[] args) { int j = 1; for (int i = 0; i < 100000; ++i) { j *= 2; } } 

使用ildasm反编译生成的可执行文件为我们提供了以下CIL列表:

 .method private hidebysig static void Main(string[] args) cil managed { .entrypoint // Code size 23 (0x17) .maxstack 2 .locals init ([0] int32 j, [1] int32 i) IL_0000: ldc.i4.1 IL_0001: stloc.0 IL_0002: ldc.i4.0 IL_0003: stloc.1 IL_0004: br.s IL_000e IL_0006: ldloc.0 IL_0007: ldc.i4.2 IL_0008: mul IL_0009: stloc.0 IL_000a: ldloc.1 IL_000b: ldc.i4.1 IL_000c: add IL_000d: stloc.1 IL_000e: ldloc.1 IL_000f: ldc.i4 0x186a0 IL_0014: blt.s IL_0006 IL_0016: ret } // end of method Program::Main 

这是第二个程序:

 static void Main(string[] args) { int j = 1; for (int i = 0; i < 100000; ++i) { j <<= 1; } } 

反编译这给我们提供了以下CIL列表:

 .method private hidebysig static void Main(string[] args) cil managed { .entrypoint // Code size 23 (0x17) .maxstack 2 .locals init ([0] int32 j, [1] int32 i) IL_0000: ldc.i4.1 IL_0001: stloc.0 IL_0002: ldc.i4.0 IL_0003: stloc.1 IL_0004: br.s IL_000e IL_0006: ldloc.0 IL_0007: ldc.i4.2 IL_0008: shl IL_0009: stloc.0 IL_000a: ldloc.1 IL_000b: ldc.i4.1 IL_000c: add IL_000d: stloc.1 IL_000e: ldloc.1 IL_000f: ldc.i4 0x186a0 IL_0014: blt.s IL_0006 IL_0016: ret } // end of method Program::Main 

注意第8行的差异。程序的第一个版本使用乘法(mul),而第二个版本使用左移(shl)。

JIT在执行代码时对它做了什么我不确定,但C#编译器本身可以certificate没有优化乘以2的幂乘以位移。

200L100L代表什么? 在我看来,你正在使用魔术数字 。 您应该尝试以尽可能好地描述其意图的方式编写您的程序; 使其尽可能可读。 对这些值使用命名常量而不是幻数是一种很好的方法。 同样在其自己的命名方法中提取计算也有帮助。 执行此操作时,您将立即看到无法将其重写为x << 1 。 不是因为结果会有所不同,而是因为可维护性会受到影响。 当您编写类似x << 1代码时,下一个程序员不知道这实际意味着什么,它会每分钟增加着名的WTF

alt text http://sofzh.miximages.com/c%23/wtfm.jpg


我认为你的代码看起来应该更像这样:

 int size = CalculateSizeOfThing(length); private static int CalculateSizeOfThing(int length) { const long TotalArraySize = 200L; const long BytesPerElement = 100L; return (length * TotalArraySize) / BytesPerElement; } 

当然,这些const值的名称是一个疯狂的猜测:-)

这听起来像是过早的优化。 只做长度* 2的优点是编译器肯定会对此进行优化,并且更容易维护。

当我看到成语时:

 int val = (someval * someConstant) / someOtherConstant; 

我认为代码的意图是以某种方式进行扩展。 它可以是手动定点代码,也可以避免整数除法的问题。 一个具体的例子:

 int val = someVal * (4/5); // oops, val = 0 - probably not what was intended 

或编写以避免来回浮点:

 int val = (int)(someVal * .8); // better than 0 - maybe we wanted to round though - who knows? 

当我看到这个成语时:

 int val = someVal << someConstant; 

我简单地想知道someVal中的各个位是否具有需要移位的更深层含义,然后我开始查看周围的上下文以了解为什么这样做。

要记住的是,当您编写具有以下内容的代码时:

 int val = expr; 

有无数种方法来创建expr,这样val将始终具有相同的值。 重要的是要考虑您在expr中表达的意图对于将要跟随的人。

对于任何现代编译器,左移1或乘以2将生成相同的代码。 它可能是一个添加指令,将数字添加到自身,但它取决于您的目标。

另一方面,由于第一次乘法可能会溢出,因此执行(x * 200)/ 100与将数字加倍是不同的,因此无法安全地优化以使数字加倍。

为了回答你的实际问题,当这两个代码snippits之间似乎没有行为差异,当length是非负int并且代码文本在unchecked上下文中时。

在已checked上下文(int.MaxValue * 200L)中将永远不会溢出,因为结果很容易适合long (int.MaxValue * 200L)(int)((length * 200L) / 100L只会在length * 2溢出时溢出。在任何情况下,移位运算符都不会溢出。因此,代码snippits在checked上下文中表现不同。

如果length为负,则移位操作将产生不正确的结果,而乘法将正常工作。 因此,如果允许length变量为负,则两个代码snippits是不同的。

(与流行的观点相反,x << 1与x * 2不同。如果x是无符号整数,它们只是相同。)