使用按位运算代替测试偶数/奇数

时间:2014-09-28 19:43:28

标签: python bitwise-operators

我试图理解这个特殊的解决方案如何进行推理分解(取自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>>1)<<1比模运算更有效,如建议的here),是否存在从具有多个按位运算符的表达式中提取基础逻辑的一般策略?


更新

根据答案中的建议,我将步骤和步骤的版本都计时,差异是不可察觉的:

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

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

2 个答案:

答案 0 :(得分: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&#39; %timeit向其中输入各种数字,我得到的结果范围从.92x到1.08x。换句话说,它似乎是洗漱......

答案 1 :(得分:0)

理论上,三位移位比单次乘法和单次划分更有效。在实践中,应对这些代码进行分析,以确保最终的优化提供足够的速度提升,以证明可读性的损失。

任何采用这种优化的代码都应该清楚地记录代码的作用以及为什么优化被认为是有用的,只是为了未来的维护者可能会试图用更可读的东西替换代码。