加倍数 - 左移与乘法

11 .net c# algorithm

有什么区别

int size = (int)((length * 200L) / 100L); // (1)
Run Code Online (Sandbox Code Playgroud)

int size = length << 1; // (2)
Run Code Online (Sandbox Code Playgroud)

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

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

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

请告诉我我错过了什么.

Eri*_*ert 42

<<比乘法更快的想法是推理,好像.NET jit编译器实际上是一个写得不好的C编译器,写于1970年代.即使它是真的,这个时间点的差异将以皮秒为单位进行测量,即使存在差异,也可能没有.

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

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

Jill的原创节目:

int x = y * 2;
Run Code Online (Sandbox Code Playgroud)

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

int x = y << 1;
Run Code Online (Sandbox Code Playgroud)

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

int x = y << 1 + 1;
Run Code Online (Sandbox Code Playgroud)

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

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

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

  • 对.声称并不是说比特移位是合理的,因为它更具可读性,而是因为它更快,所以它是合理的.我说哪个更快更无关紧要:当你想要的操作在逻辑上*乘以数字*然后*乘以*.当你想要的操作在逻辑上*操作位*然后*操作位*.当您将数字视为位数组时,您将在错误的抽象级别上运行.除非有令人信服的*理由,否则不应利用数字作为位数组实现的事实. (15认同)
  • 我很高兴有人指出这一点.我喜欢"pico-optimizations"这个词.在我们的语言中,我们称之为"bitneuken"(无法翻译,或者我会被禁止:-) (4认同)
  • 令我感到惊讶的是,很多人认为`x << 1`的可读性低于`x*2`.如果我想乘以2的幂,我通常使用shift,因为它实际上约束了被乘数,所以更好地说明了意图.你如何为你的`[Flags]`枚举写出值?当然你使用十六进制或移位,而不是否定? (2认同)
  • @ToxicAvenger:您无法将位图标记与简单乘法进行比较.如果你想乘以32,那么说值*32而不是值<< 5则更具可读性.Bitshift运营商有它们的用途,但不能用于倍增.如果您以后必须将代码更改为值*31,该怎么办? (2认同)

Inc*_*ito 27

And here is the 3rd option:

int size = length * 2; // Comment explaining what is 2 or what means this multiplication
Run Code Online (Sandbox Code Playgroud)

And this must be the best option. As it is readable and easy to understand what you want to do. As for performance, compilers are generating pretty optimized code, so no need to worry for such a simple operation. If you have any concerns concerning overflow you can use checked block.

EDIT As mentioned by many others just use any meaningful variable instead of 2 here.

  • @Everyone:如果有人无法弄清楚长度*2是什么,他们就会有脑损伤.这一点关于可读性是一堆垃圾.将任何数字乘以任何整数都是DROP DEAD EASY易读:将'thing'乘以'#'因子.您可以通过使用常量使其"更明显可理解":`const int SCALE_FACTOR = 2; int size = length*SCALE_FACTOR;`然而,这有一个问题,就是要求读者弄清楚SCALE_FACTOR是什么,如果他们想知道它们乘以的是什么.无法保证常数的位置是众所周知的. (7认同)
  • 我认为`length*2'实际上比第一个选项更差.您的提案描述了代码的意图较少.有一个选项,我认为更好.他应该使用命名常量而不是'200L`和'100L'.这在描述代码的意图方面做得更好. (3认同)
  • @Everyone:有第五个选项使"不易理解"的代码的意图易于理解,常量与否:它被称为注释. (2认同)

Mat*_*ell 7

Which is more readable to your average programmer:

int size = length * 2;
int size = length << 1;
Run Code Online (Sandbox Code Playgroud)

Unless they come from a strong C++ bit tweaking background I'd wager your average programmer knows immediately what the first line does (it even has the number "2" for "double" in it) but would have to stop and pause for the second line.

In fact I'd feel inclined to comment the second line explaining what it does which seems redundant when you can get the code do the talking as in the first line.

  • 如果有人需要一个解释移位操作符的评论,那么在我眼中根本不应该编程. (5认同)
  • 因为你可能来自ac/c ++背景.在我看来,任何移位运算符的使用都是过度优化.我可以把第一行带到街上的一个人,他们可以猜到它的作用.尝试使用第二行.如果程序员在5秒钟内无法解决问题,那么我可能会担心,但这只是不必要的额外思考,没有可想象的好处. (3认同)
  • 在一天结束时,软件的目标是解决问题,而不是内部美观或超级优化.虽然我同意第二种方法在理论意义上是"更纯粹",但第一种方法更容易阅读 - 没有任何意义可以追溯到以前的方式作为使用更混淆的形式的理由这个操作.他们都做了完全相同的事情,我敢打赌编译器知道它.使用"独占"版本的操作没有任何意义. (3认同)
  • 他真的没有.如果没有三元运算符,您可以在没有移位运算符的情况下进行编程.你把我的论点变成了一个稻草人 - 我并不是说我们的目标是让我们的程序对街上的人可读,但如果有两种方法可以做某事而唯一的区别就是清晰度是有意义的选择更清晰的一个.守则应传达意图. (2认同)
  • 在我看来,程序员的傲慢是软件开发和整体团队合作中最大的障碍之一. (2认同)

Ste*_*ven 6

What do 200L and 100L represent? Seems to me you are using magic numbers. You should try to write your program in such a way that it describes its intent as good as possible; making it as readable as possible. Using named constants for these values instead of magic numbers is a good way of doing this. Also extracting the calculation in its own well named method helps too. When you do this, you will immediately see that there is no way of rewriting it to x << 1. Not because the results will be different, but because maintainability will suffer. When you write code like x << 1, the next programmer has no idea what that actually means and it will increase the famous WTFs per minute:

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;
}
Run Code Online (Sandbox Code Playgroud)

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


Ant*_*Ant 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

使用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
Run Code Online (Sandbox Code Playgroud)

这是第二个程序:

static void Main(string[] args)
{
    int j = 1;

    for (int i = 0; i < 100000; ++i)
    {
        j <<= 1;
    }
}
Run Code Online (Sandbox Code Playgroud)

反编译这给我们提供了以下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
Run Code Online (Sandbox Code Playgroud)

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

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

  • C#编译器没有优化的事实并不意味着JIT编译器不会.一个体面的JIT编译器将优化它生成的代码.毕竟,C#编译器生成的只是中间代码. (2认同)