之间有什么区别
int size = (int)((length * 200L) / 100L); // (1)
和
int size = length << 1; // (2)
(两种情况下长度均为int)
我假设两个代码片段都想要加倍长度参数。
我很想使用(2)......那么使用(1)有什么好处吗?当溢出发生时,我查看了边缘情况,两个版本似乎都有相同的行为。
请告诉我我错过了什么。
答案 0 :(得分:38)
<<
比乘法更快的想法是推理,就好像.NET jit编译器实际上是一个写得不好的C编译器,写于1970年代。即使它是真的,这个时间点的差异将以皮秒为单位进行测量,即使存在差异,也可能没有。
编写代码以便于阅读。让编译器处理微微优化。基于分析实际场景优化代码,而不是第二次猜测编译器将生成什么。
此外,移位运算符 not 与乘法具有相同的语义。例如,请考虑以下编辑顺序:
Jill的原创节目:
int x = y * 2;
鲍勃编辑:傻吉尔,我会让这个“更快”:
int x = y << 1;
由实习生拉里编辑:哦,我们有一个错误,我们一个人关闭,让我解决这个问题:
int x = y << 1 + 1;
拉里刚刚介绍了一个新的bug。 y * 2 + 1与y&lt;&lt;&lt; 1 + 1;后者实际上是y * 4.
我在真实的实时制作代码中看到了这个错误。精神上很容易进入“转移是乘法”的心态,忘记转移低于优先级而不是添加,而乘法是更高的优先级。
我从来没有见过有人通过写x * 2得到算术优先级错误乘以2。人们理解+和*的优先级。很多人都忘记了转移的优先顺序。您实际上没有值任何潜在错误的皮秒数?我拒绝。
答案 1 :(得分:26)
这是第三个选项:
int size = length * 2; // Comment explaining what is 2 or what means this multiplication
这一定是最好的选择。因为它易读且易于理解您想要做什么。
至于性能,编译器正在生成相当优化的代码,因此无需担心这么简单的操作。
如果您对溢出有任何疑虑,可以使用checked
阻止。
编辑正如许多其他人所提到的,只需使用任何有意义的变量而不是2
。
答案 2 :(得分:7)
对于普通程序员来说,这更具可读性:
int size = length * 2;
int size = length << 1;
除非他们来自强大的C ++位调整背景,否则你的平均程序员会立即知道 第一行所做的事情(它甚至有“2”代表“double”)但必须停下来并暂停第二行。
事实上,我觉得我倾向于评论第二行,解释它的作用,当你可以让代码像第一行那样进行说话时,这似乎是多余的。
答案 3 :(得分:5)
200L
和100L
代表什么?在我看来你正在使用magic numbers。您应该尝试以尽可能好地描述其意图的方式编写您的程序;使其尽可能可读。对这些值使用命名常量而不是幻数是一种很好的方法。同样在其自己的命名方法中提取计算也有帮助。执行此操作时,您将立即看到无法将其重写为x << 1
。不是因为结果会有所不同,而是因为可维护性会受到影响。当您编写像x << 1
这样的代码时,下一个程序员不知道这实际意味着什么,它会增加每分钟着名的 WTF :
alt text http://www.osnews.com/images/comics/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
值的名称是一个疯狂的猜测: - )
答案 4 :(得分:5)
有趣的是,大多数答案都认为编译器会优化乘以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#编译器本身明显地不优化乘以2的幂乘以位移。
答案 5 :(得分:2)
这听起来像过早的优化。只做长度* 2的好处是编译器肯定会优化它并且更容易维护。
答案 6 :(得分: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中表达的意图对于那些将要跟随的人。
答案 7 :(得分:1)
任何现代编译器左移1或乘以2都会生成相同的代码。它可能是一个添加指令,将数字添加到自身,但它取决于你的目标。
另一方面,由于第一次乘法可能会溢出,所以做(x * 200)/ 100与数字加倍是不一样的,所以无法安全地优化这个数字加倍。
答案 8 :(得分:0)
要回答您的实际问题,当length
为非负int
且代码文本位于unchecked
内时,这两个代码段之间似乎没有行为差异} context。
在checked
上下文中(int.MaxValue * 200L)
永远不会溢出,因为结果很容易适合long
。 (int)((length * 200L) / 100L
只会在length * 2
溢出时溢出。在任何情况下,移位运营商都不会溢出。因此,代码snippits在checked
上下文中的行为方式不同。
如果length
为负,则移位操作将产生不正确的结果,而乘法将正常工作。因此,如果允许length
变量为负数,则两个代码snippits会有所不同。
(与流行的观点相反,x&lt;&lt; 1与x * 2不同。如果x是无符号整数,它们只是相同的。)
答案 9 :(得分:0)
无论代码可读性如何:位移和整数乘法,即使是 2 的常数幂,通常也不相同。
任何编译器都不会将 x * 2
优化为 x << 1
,除非它可以向自己证明 x 是一个非负整数。 (如果 x 的类型是 unsigned int
,那么根据定义这当然是正确的。)它还需要知道溢出行为是相同的,或者溢出不会发生。
考虑以下 C# 示例:
int x = 2000000000, multiplied, shifted;
// Intention is to store results in 64-bit in case x is large:
long multA, shiftA, multB, shiftB;
checked
{
shifted = x << 1; // Overflows to negative number.
multiplied = x * 2; // Throws OverflowException.
shiftA = x << 1; // Maybe this overflows to negative number too?
multA = x * 2; // Maybe this throws OverflowException too?
shiftB = x << 1L; // Happens to double OK, because x is positive.
multB = x * 2L; // Works regardless of the value of x.
}
如果我错了,请纠正我。