【问题标题】:Is & faster than % when checking for odd numbers?检查奇数时 & 比 % 快吗?
【发布时间】:2010-11-08 13:31:21
【问题描述】:

要检查奇数和偶数,最低位检查是否比使用模数更有效?

>>> def isodd(num):
        return num & 1 and True or False

>>> isodd(10)
False
>>> isodd(9)
True

【问题讨论】:

  • "and True or False"是什么意思?
  • 如果你想得到一个布尔结果,只需执行 bool(num & 1)
  • “0 为真,1 为假”?!现在这将是一种有趣的编程语言,确实......!!!
  • 我搞砸了。在 Python 中,0 为假,1 为真。 (但在 *nix 中,退出代码 0 表示成功。bash 确实很有趣。)
  • 是的,我记得曾经(在用于实验室控制的 IBM Instruments 小型计算机上)结束一个程序,并在打印控制台上收到 16 条可怕的错误消息(结果返回码被解释为位对于 16 种可能的系统错误类别中的每一种,按位表示 1 = 成功,0 = 失败;-) - 这就是为什么 ANSI C 指定 EXIT_SUCCESS 而不是 0 的原因,我相信(尽管我认为没有人使用它;-)。

标签: python performance bit-manipulation modulo


【解决方案1】:

是的。标准库中的 timeit 模块是您检查这些内容的方式。例如:

$ python -m timeit -s 'def isodd(x): x & 1' 'isodd(9)'
1000000 loops, best of 3: 0.446 usec per loop
$ python -m timeit -s 'def isodd(x): x & 1' 'isodd(10)'
1000000 loops, best of 3: 0.443 usec per loop
$ python -m timeit -s 'def isodd(x): x % 2' 'isodd(9)'
1000000 loops, best of 3: 0.461 usec per loop
$ python -m timeit -s 'def isodd(x): x % 2' 'isodd(10)'
1000000 loops, best of 3: 0.453 usec per loop

如您所见,在我的(第一天==旧==慢;-)Macbook Air 上,& 解决方案比% 解决方案快 7 到 18 纳秒。

timeit 不仅告诉你什么更快,而且告诉你多少(只需运行几次测试),这通常表明它是多么不重要(你真的关心 10 纳秒吗? ' 的区别,当调用函数的开销在 400 左右时?!-)...

让程序员相信微优化本质上是无关紧要的,这已被证明是一项不可能完成的任务——尽管自 Knuth wrote 以来已经过去了 35 年(计算机的速度已经提高了几个数量级!)

我们应该忘记小 效率,说大约 97% 时间:过早优化是 万恶之源。

正如他所解释的,这是从 Hoare 更早的声明中引用的。我想每个人都完全相信他们的案件属于剩下的 3%!

因此,我们(尤其是蒂姆·彼得斯(Tim Peters)应得的荣誉)而不是无休止地重复“没关系”,而是将标准 Python 库模块 timeit 放入,这使得测量这些微基准和因此,至少 一些 程序员可以说服自己,嗯,这种情况确实属于 97% 的群体!-)

【讨论】:

  • 不,Python 编译器本身经过优化,以最大限度地简单、可靠和快速——它不进行诸如更改正在使用的操作之类的优化(为此它必须推断 @例如,987654328@ 始终是整数)。如果您需要这种低级优化(即如果每一纳秒都很重要),请尝试 psyco。
  • 感谢您补充一点关于这么小的差异并不重要的观点。即使执行数百甚至数千次计算,这也可能是您可以做出的最糟糕的“优化”之一 - 它不仅会损害可读性 (IMO),而且您不会从中获得太多回报。我永远不想付出比我得到的更多的钱。
  • 哦,是的,我忘了 Python 不是静态类型的。 (对吗?)Python 无知警报又来了。
  • @Thomas,我实际上反复编辑了答案,以准确解释它是多么不相关,提供 Knuth 的报价,解释为什么我们将 timeit 放在库中,&c;但是 SO 非常类似于 100 码冲刺,所以为了获得任何代表,我选择先提供解决方案,然后再提供布道。看起来这次你选择只提供布道,没有代码或解决方案,正在赢得代表比赛;-)。 (顺便说一句,在这种情况下,我不认为 &1 或 %2 的可读性低于另一个)。
  • 假设任何给定的优化都为时过早是有缺陷的
【解决方案2】:

老实说,我认为这并不重要。

第一个问题是可读性。什么对其他开发人员更有意义?在检查数字的奇偶性时,我个人会期望模数。我希望大多数其他开发人员会期待同样的事情。通过引入一种不同的、意想不到的方法,您可能会使代码阅读和维护变得更加困难。

第二个事实是您在执行任一操作时可能永远不会遇到瓶颈。我支持优化,但早期优化是您在任何语言或环境中都能做的最糟糕的事情。如果由于某种原因,确定一个数字是偶数还是奇数是一个瓶颈,那么找到解决问题的最快方法。然而,这让我回到了我的第一点——第一次编写例程时,应该尽可能以最易读的方式编写。

【讨论】:

  • 我对这个问题有一半的期待,但我认为这对于任何想知道这个(或任何类似)问题的人来说都很重要,所以它会留下来。
  • 不是投反对票...但是,在这种情况下,Pythonic 的解决方案是分析“明显”选项并选择最快的选项。
  • semiuseless:我还在学习 Python,但我个人认为模运算符会更 Pythonic,因为你正在做预期的事情。至少对我来说,它更加明显和清晰。
  • 我支持你。正如 Donald Knuth 所说,“过早的优化是万恶之源。”
  • @gutofb7:我给出了完整的引用 Knuth 伟大文章的 PDF 链接(我相信也是第一篇提出缩进作为指示块的唯一方法的文章) 在您发表评论前大约 8 分钟我的回答中;-)。顺便说一句,在这种情况下,我相信 %2 和 &1 具有完全等效的可读性(任何不按位理解的人 - 并且将难以理解与百分比完全无关的 '%' 等;-)。
【解决方案3】:

"返回 num & 1 和 True or False" ?哇!如果你对速度很着迷 (1) "return num & 1" (2) 内联它:if somenumber % 2 == 1 清晰易读并且胜过isodd(somenumber),因为它避免了 Python 函数调用。

【讨论】:

  • 是的,避免函数调用是一个巨大的胜利(尽管 ==1 确实是多余的)——避免调用从我在答案中测量的 450 纳秒左右减少了大约 300 纳秒。跨度>
【解决方案4】:

约翰提出了一个很好的观点。真正的开销在函数调用中:

me@localhost ~> python -mtimeit -s'9 % 2'
10000000 loops, best of 3: 0.0271 usec per loop
me@localhost ~> python -mtimeit -s'10 % 2'
10000000 loops, best of 3: 0.0271 usec per loop

me@localhost ~> python -mtimeit -s'9 & 1'
10000000 loops, best of 3: 0.0271 usec per loop
me@localhost ~> python -mtimeit -s'9 & 1'
10000000 loops, best of 3: 0.0271 usec per loop

me@localhost ~> python -mtimeit -s'def isodd(x): x % 2' 'isodd(10)'
1000000 loops, best of 3: 0.334 usec per loop
me@localhost ~> python -mtimeit -s'def isodd(x): x % 2' 'isodd(9)'
1000000 loops, best of 3: 0.358 usec per loop

me@localhost ~> python -mtimeit -s'def isodd(x): x & 1' 'isodd(10)'
1000000 loops, best of 3: 0.317 usec per loop
me@localhost ~> python -mtimeit -s'def isodd(x): x & 1' 'isodd(9)'
1000000 loops, best of 3: 0.319 usec per loop

有趣的是,这两种方法在没有函数调用的情况下同时重复。

【讨论】:

  • 如果我用 lambda 替换函数呢? isodd = lambda num: num & 1 and True or False
【解决方案5】:

您可以获得的最佳优化是将测试放入函数中。 'number % 2' 和 'number & 1' 是检查奇数/偶数的非常常用的方法,有经验的程序员会立即识别出这种模式,并且您可以随时输入诸如 '# if number is odd, then blah blah blah 这样的注释' 如果你真的需要它是显而易见的。

# state whether number is odd or even
if number & 1:
    print "Your number is odd"
else:
    print "Your number is even"

【讨论】:

  • 你能解释一下%&在python中的作用吗?
  • 这个数&1如何给出奇数?
【解决方案6】:

除了邪恶的优化之外,它还带走了每个编码人员无需看两次就能理解的非常惯用的“var % 2 == 0”。所以这也违反了蟒蛇的禅宗,收获甚微。

此外,a = b 和 True 或 False 已被

取代以获得更好的可读性

如果 num & 1 则返回 True,否则返回 False

【讨论】:

    【解决方案7】:

    真的很惊讶上述答案都没有变量设置(时间文字是不同的故事)和没有函数调用(这显然隐藏了“较低的术语”)。坚持 ipython 的 timeit 的时间,在那里我得到了明显的赢家 x&1 - 使用 python2.6 的结果约为 18%(使用 python3.1 的结果约为 12%)。

    在我很旧的机器上:

    $ python -mtimeit -s 'x = 777' 'x&1'
    10000000 loops, best of 3: 0.18 usec per loop
    $ python -mtimeit -s 'x = 777' 'x%2'
    1000000 loops, best of 3: 0.219 usec per loop
    
    $ python3 -mtimeit -s 'x = 777' 'x&1'
    1000000 loops, best of 3: 0.282 usec per loop
    $ python3 -mtimeit -s 'x = 777' 'x%2'
    1000000 loops, best of 3: 0.323 usec per loop
    

    【讨论】:

      【解决方案8】:

      使用 Python 3.6,答案是 。在 2017 MBP 上使用下面的代码表明使用模数更快。

      # odd.py
      from datetime import datetime
      
      iterations = 100_000_000
      
      
      def is_even_modulo(n):
          return not n % 2
      
      
      def is_even_and(n):
          return not n & 1
      
      
      def time(fn):
          start = datetime.now()
          for i in range(iterations, iterations * 2):
              fn(i)
          print(f'{fn.__name__}:', datetime.now() - start)
      
      
      time(is_even_modulo)
      time(is_even_and)
      

      给出这个结果:

      $ python3 -m odd
      is_even_modulo: 0:00:14.347631
      is_even_and: 0:00:17.476522
      $ python3 --version
      Python 3.6.1
      

      正如其他答案所建议的,函数调用是一个很大的开销,但是,删除它表明模仍然比按位更快,并且在 Python 3.6.1 中:

      # odd.py
      from datetime import datetime
      
      iterations = 100_000_000
      
      
      def time_and():
          start = datetime.now()
          for i in range(iterations):
              i & 1 
          print('&:', datetime.now() - start)
      
      
      def time_modulo():
          start = datetime.now()
          for i in range(iterations):
              i % 2
          print('%:', datetime.now() - start)
      
      
      time_modulo()
      time_and()
      

      结果:

      $ python3 -m odd
      %: 0:00:05.134051
      &: 0:00:07.250571
      

      奖励:事实证明,这需要在 Python 2.7 中运行大约两倍的时间。

      $ time python2 -m odd
      ('&:', '0:00:20.169402')
      ('%:', '0:00:19.837755')
      
      real    0m41.198s
      user    0m39.091s
      sys 0m1.899s
      $ time python3 -m odd
      &: 0:00:11.375059
      %: 0:00:08.010738
      
      real    0m19.452s
      user    0m19.354s
      sys 0m0.042s
      

      【讨论】:

        【解决方案9】:

        更新最新python版本的结果,%比&快

        ➜ ✗ python -m timeit -s 'def isodd(x): x & 1' 'isodd(9)'
        5000000 loops, best of 5: 78.4 nsec per loop
        ➜ ✗ python -m timeit -s 'def isodd(x): x & 1' 'isodd(10)'
        5000000 loops, best of 5: 79.6 nsec per loop
        ➜ ✗ python -m timeit -s 'def isodd(x): x % 2' 'isodd(9)'
        5000000 loops, best of 5: 65.8 nsec per loop
        ➜ ✗ python -m timeit -s 'def isodd(x): x % 2' 'isodd(10)'
        5000000 loops, best of 5: 68.5 nsec per loop
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2011-02-18
          • 2014-05-03
          • 2010-09-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多