你所不知道的 有些时候 Python 中乘法比位运算竟然会更快

属先我们开始正文,一个技术群里老哥提出了这样一个问题,为什么在一些情况下,Python 中的简单乘/除法比位运算要慢。

首先秉持着实事求是的精神,我们先来验证一下:

很多人学习python,不知道从何学起。
很多人学习python,掌握了基本语法过后,不知道在哪里寻找案例上手。
很多已经做案例的人,却不知道如何去学习更加高深的知识。
那么针对这三类人,我给大家提供一个好的学习平台,免费领取视频教程,电子书籍,以及课程的源代码!??¤
QQ群:1057034340

我们发现几个很有趣的现象:

  • 在值 x<=2^30 时,乘法比直接位运算要快
  • 在值 x>2^32 时,乘法显著慢于位运算

这个现象很有趣,那么这个现象的 root cause 是什么?实际上这和 Python 底层的实现有关。

简单聊聊

1. PyLongObject 的实现

在 Python 2.x 时期,Python 中将整型分为两类,一类是 long, 一类是 int 。在 Python3 中这两者进行了合并。目前在 Python3 中这两者做了合并,仅剩一个 long。

首先来看看 long 这样一个数据结构底层的实现:

在这里不用关心,PyObject_VAR_HEAD 的含义,我们只需要关心 ob_digit 即可。

在这里,ob_digit 是使用了 C99 中的“柔性数组”来实现任意长度的整数的存储。这里我们可以看一下官方代码中的文档:

简而言之,Python 是将一个十进制数转为 2^(SHIFT) 进制数来进行存储。这里可能不太好了理解。我来举个例子,在我的电脑上,SHIFT 为 30 ,假设现在有整数 1152921506754330628 ,那么将其转为 2^30 进制表示则为: 4*(2^30)^0+2*(2^30)^1+1*(2^30)^2 。那么此时 ob_digit 是一个含有三个元素的数组,其值为 [4,2,1]。

OK,在明白了这样一些基础知识后,我们回过头去看看 Python 中的乘法运算。

2. Python 中的乘法运算

Python 中的乘法运算,分为两部分,其中关于大数的乘法,Python 使用了 Karatsuba 算法1,具体实现如下:

这里不对 Karatsuba 算法1 的实现做单独解释,有兴趣的朋友可以参考文末的 reference 去了解具体的详情。

在普通情况下,普通乘法的时间复杂度为 n^2 (n 为位数),而 K 算法的时间复杂度为 3n^(log3) ≈ 3n^1.585 ,看起来 K 算法的性能要优于普通乘法,那么为什么 Python 不全部使用 K 算法呢?

很简单,K 算法的优势实际上要在当 n 足够大的时候,才会对普通乘法形成优势。同时考虑到内存访问等因素,当 n 不够大时,实际上采用 K 算法的性能将差于直接进行乘法。

所以我们来看看 Python 中乘法的实现:

在这里我们看到,当两个数皆小于 2^30-1 时,Python 将直接使用普通乘法并返回,否则将使用 K 算法进行计算

这个时候,我们来看一下位运算的实现,以右移为例:

在这里我们能看到,在两侧都是小数的情况下,位移动算法将比普通乘法,存在更多的内存分配等操作。这样也会回答了我们文初所提到的一个问题,“为什么一些时候乘法比位运算更快”。

总结本文差不多就到这里了,实际上通过这次分析我们能得到一些很有趣但是也很冷门的知识。实际上我们目前看到这样一个结果,是 Python 对于我们常见且高频的操作所做的一个特定的设计。而这也提醒我们,Python 实际上对于很多操作都存在自己内建的设计哲学,在日常使用的时候,其余语言的经验,可能无法复用。

posted @ 2020-11-10 20:02  陈python  阅读(771)  评论(0编辑  收藏  举报