python求偶奇数和差编程_[Python Puzzlers]判断奇偶时位运算居然比取模慢?

疑惑

在普遍的认知中,位运算是比取模运算快的。然而在使用Python进行简单的测试中,却意外发现位运算居然比取模慢,比如下面的代码:

>>> timeit.timeit('[i for i in range(1000) if i % 2]', number=1000)

0.07428571907803416

>>> timeit.timeit('[i for i in range(1000) if i & 1]', number=1000)

0.10028700600378215

计算1000以内的奇数,位运算居然比取模慢?

思考

这其中到底发生了什么?通过资料搜集,最终在stackoverflow上找到了线索。首先,Python中的int跟C语言中的int是有区别的,它们是对象Object。在底层实现上,使用的是一个数字列表,这也是Python中int不限制大小的原因。有兴趣可以了解一下CPython对int的定义。

其次,针对single limb integer,*、/、%三种运算有优化;而位运算则是针对任意精度的。

single limb integer这个说法笔者也是第一次接触,解释如下:a "limb" is a base-2^30 "digit", stored as a binary integer in a uint32_t or equivalent. CPython stores big integers in chunks of that size. Just like how you can add decimal numbers on paper one digit at a time, using base-2^30 digits allows CPython to get lots of work done with each primitive add operation the CPU does. (Using base 2^32 chunks would allow using add / add-with-carry in asm to take advantage of hardware support for big-integers on most architectures, but that's not what CPython does. 2^30 has some advantages, though, esp. for pure C without asm.)

可以简单理解成,小于2^30的数,为single limb integer。

到目前为止,导致文章开头诡异表现的原因已经分析得差不多了。让我们使用大于2^30的数,验证一下。

# coding: utf-8

import timeit

def fun_1(n):

return [i for i in range(2 ** n, 2 ** n + 1000) if i % 2]

def fun_2(n):

return [i for i in range(2 ** n, 2 ** n + 1000) if i & 1]

print('{}\t{}\t{}'.format('n', 'modulo', 'bit_operation'))

for n in range(25, 45):

fun_1_time = timeit.timeit(

'fun_1(n)',

setup='from __main__ import n, fun_1',

number=1000

)

fun_2_time = timeit.timeit(

'fun_2(n)',

setup='from __main__ import n, fun_2',

number=1000

)

print('{}\t{:f}\t{:f}'.format(

n,

fun_1_time,

fun_2_time,

))

输出结果如下:

n modulo bit_operation

25 0.070913 0.091450

26 0.075533 0.091473

27 0.069695 0.096567

28 0.076114 0.100146

29 0.077445 0.091671

30 0.119914 0.091599

31 0.118146 0.098614

32 0.131388 0.098873

33 0.121872 0.102023

34 0.114846 0.092842

35 0.119020 0.091165

36 0.118940 0.098211

37 0.123240 0.099836

38 0.128368 0.092894

39 0.112679 0.091436

40 0.123808 0.100071

41 0.114746 0.091711

42 0.113422 0.091883

43 0.118400 0.091734

44 0.113491 0.091707

很明显的发现,位运算在 2^25 和 2^44 的表现很稳定,也从侧面验证了位运算是针对所有精度实现的。而取模则呈线性增长,而且笔者猜测跟limb的数目有关。

总结

使用Python的话,对于 2^30 以内的数字操作,并没有必要使用位运算来提高效率,直接使用*、/、%即可。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值