【问题标题】:Use of bitwise operations instead of testing for even/odd使用按位运算而不是测试偶数/奇数
【发布时间】:2014-09-28 19:43:27
【问题描述】:

我正在尝试了解这种特殊的素数分解解决方案(取自http://rosettacode.org/wiki/Prime_decomposition#Python:_Using_floating_point),并且对step 的定义中位运算符的使用感到有些困惑

def fac(n):
    step = lambda x: 1 + (x<<2) - ((x>>1)<<1)
    maxq = long(floor(sqrt(n)))
    d = 1
    q = n % 2 == 0 and 2 or 3 
    while q <= maxq and n % q != 0:
        q = step(d)
        d += 1
    return q <= maxq and [q] + fac(n//q) or [n]

我理解它的作用(乘以 x 3,然后如果 x 是偶数则加 1,如果 x 是奇数则加 2),但我不太明白为什么在这种情况下会诉诸按位运算。除了这个公式明显简洁之外,是否有理由使用按位运算符而不是更明确的解决方案:

mystep = lambda x: (3 * x) + 1 if (x % 2 == 0) else (3 * x) + 2

如果有充分的理由(例如,(x&gt;&gt;1)&lt;&lt;1 比模算术更有效,正如 here 所建议的那样),是否有从具有多个位运算符的表达式中提取底层逻辑的通用策略?


更新

按照答案中的建议,我用 step 和 my step 对版本进行了计时,差异是难以察觉的:

 %timeit fac(600851475143)
1000 loops, best of 3: 306 µs per loop

%timeit fac2(600851475143)
1000 loops, best of 3: 307 µs per loop

【问题讨论】:

    标签: python bitwise-operators


    【解决方案1】:

    这可能是围绕branch misprediction 进行优化的尝试。现代 CPU 是大规模流水线的;他们推测性地提前执行 10 条或更多指令。一个几乎随机的条件分支一半时间走一条路,另一半走另一条路,这意味着 CPU 将不得不在一半时间内抛出 10 条有价值的工作指令,使你的工作慢 5 倍。至少对于 CPython,分支错误预测的大部分成本隐藏在开销中,但您仍然可以轻松找到它们将时间增加至少 12% 的情况,如果不是您在 C 中可以预期的 500%。

    另一种选择是作者正在优化一些更不相关的东西。在 70 年代和 80 年代的硬件上,用位运算代替算术运算通常会带来巨大的加速,这只是因为 ALU 很简单而且编译器没有进行太多优化。即使是那些实际上并不期望在今天获得相同加速的人也已经内化了所有标准的比特旋转技巧,并且不假思索地使用它们。 (或者,当然,作者可能只是从 C 或 Scheme 或其他一些语言中移植了一些代码而没有真正考虑过,而这些代码可能是几十年前编写的,当时这种优化产生了很大的不同。)

    无论如何,这段代码几乎肯定在错误的地方进行了优化。在你的内部循环中定义一个每次调用的函数,而不是仅仅在那里内联单行表达式,所增加的开销远远超过 12%。并且代码使用step = lambda x: … 而不是def step(x): … 的事实非常强烈地表明作者对Python 并不熟悉并且不知道如何对其进行优化。如果你真的想让它变得更快,几乎可以肯定有很多事情会比你为step 使用的实现产生更大的不同。

    话虽如此,对于您不确定的任何优化,正确的做法是对其进行测试。以两种方式实现它,使用timeit 来查看差异,如果您不理解结果,请使用 Python 级别的分析器或硬件级别的性能计数器(例如,通过 cachegrind)或其他东西来获得更多信息信息。通过针对您的替代方案对原始代码进行非常快速的测试,使用 IPython 的%timeit 向它抛出各种数字,我得到了您版本的 0.92x 到 1.08x 时间的结果。也就是说,好像是洗了……

    【讨论】:

      【解决方案2】:

      理论上,三个位移位比一次乘法和一次除法更有效。在实践中,应分析此类代码,以确保生成的优化提供足够的速度提升,以证明可读性损失是合理的。

      任何诉诸此类优化的代码都应清楚地记录代码的作用以及优化被认为有用的原因,如果只是为了将来可能想用更易读的代码替换代码的维护者。

      【讨论】:

      • 请记住,这是 Python,因此除了相应的 ALU 指令之外,乘法或除法或位移可能还需要数十条指令。
      猜你喜欢
      • 1970-01-01
      • 2020-06-24
      • 1970-01-01
      • 2016-06-19
      • 1970-01-01
      • 1970-01-01
      • 2019-02-14
      • 2011-09-06
      • 1970-01-01
      相关资源
      最近更新 更多