前言
众所周知,Python 是动态语言,有全局解释器锁,比其他静态语言要慢,也正是这个原因,你也许会转向其他语言如 Java、C++,不过先等等,今天分享一个可以让 Python 比 C++ 还要快的技术,看完再决定要不要转吧。
![](http://image109.360doc.cn/DownloadImg/2021/04/0800/219560609_1_20210408121623552.jpg)
今天的主角就是Numba,Numba 是一个开源的即时编译器(JIT compiler),可将 Python 和 NumPy 的代码的转换为快速的机器码,从而提升运行速度。可以达到 C 或 FORTRAN 的速度。
Numba 是 python 的即时(Just-in-time)编译器,即当您调用 python 函数时,您的全部或部分代码就会被转换为“即时”执行的机器码,它将以您的本地机器码速度运行!它由 Anaconda 公司赞助,并得到了许多其他组织的支持。
在 Numba 的帮助下,您可以加速所有计算负载比较大的 python 函数(例如循环)。它还支持 numpy 库!所以,您也可以在您的计算中使用 numpy,并加快整体计算,因为 python 中的循环非常慢。 您还可以使用 python 标准库中的 math 库的许多函数,如 sqrt 等。
在文末有惊喜
这么牛逼是不是很难用呢?No,No,No,So easy,你不需要替换 Python 解释器,不需要单独编译,甚至不需要安装C / C ++ 编译器。只需将 Numba 提供的装饰器放在 Python 函数上面就行,剩下的就交给 Numba 完成。举个简单的例子:
from numba import jitimport random@jit(nopython=True)def monte_carlo_pi(nsamples): acc = 0 for i in range(nsamples):
x = random.random()
y = random.random()
if (x ** 2 + y ** 2) < 1.0:
acc += 1 return 4.0 * acc / nsamples
Numba 是专为科学计算而设计的,在与 NumPy 一起使用时,Numba 会为不同的数组数据类型生成专门的代码,以优化性能:
@numba.jit(nopython=True, parallel=True)def logistic_regression(Y, X, w, iterations): for i in range(iterations):
w -= np.dot(((1.0 /
(1.0 + np.exp(-Y * np.dot(X, w)))
- 1.0) * Y), X)
return w
现在我们来看看,同样的代码,使用 Numba 前后与 C++ 的性能对比。比如说我们要找出 1000 万以内所有的素数,代码的算法逻辑是相同的:
import mathimport timedef is_prime(num): if num == 2:
return True if num <= 1 or not num % 2:
return False for div in range(3, int(math.sqrt(num) + 1), 2):
if not num % div:
return False return Truedef run_program(N): total = 0 for i in range(N):
if is_prime(i):
total += 1 return totalif __name__ == '__main__':
N = 10000000 start = time.time()
total = run_program(N)
end = time.time()
print(f'total prime num is {total}')
print(f'cost {end - start}s')
执行耗时:
total prime num is 664579cost 47.386465072631836s
C++ 代码如下:
#include <iostream>#include <cmath>#include <time.h>using namespace std;bool isPrime(int num) {
if (num == 2) return true;
if (num <= 1 || num % 2 == 0) return false;
double sqrt_num = sqrt(double(num));
for (int div = 3; div <= sqrt_num; div +=2){
if (num % div == 0) return false;
}
return true;
}int run_program(int N){
int total = 0;
for (int i; i < N; i++) {
if(isPrime(i)) total ++;
}
return total;
}int main(){
int N = 10000000;
clock_t start,end;
start = clock();
int total = run_program(N);
end = clock();
cout << 'total prime num is ' << total;
cout << '\ncost ' << (end - start) / ((double) CLOCKS_PER_SEC) << 's\n';
return 0;
}
$ g++ isPrime.cpp -o isPrime
$ ./isPrime
total prime num is 664579cost 2.36221s
C++ 确实牛逼,才 2.3 秒,不过好戏还在后头,现在我们使用 Numba 来加速一下,操作很简单,不需要改动原有的代码,先导入 Numba 的 njit,再在函数上方放个装饰器@njit 即可,其他保持不变,代码如下:
import mathimport timefrom numba import njit# @njit 相当于 @jit(nopython=True) @njitdef is_prime(num): if num == 2:
return True if num <= 1 or not num % 2:
return False for div in range(3, int(math.sqrt(num) + 1), 2):
if not num % div:
return False return True@njitdef run_program(N): total = 0 for i in range(N):
if is_prime(i):
total += 1 return totalif __name__ == '__main__':
N = 10000000 start = time.time()
total = run_program(N)
end = time.time()
print(f'total prime num is {total}')
print(f'cost {end - start}s')
运行一下,可以看出时间已经从 47.39 秒降低到 3 秒。
total prime num is 664579cost 3.0948808193206787s
相比 C++ 的 2.3 秒还是有一点慢,你可能会说 Python 还是不行啊。等一等,我们还有优化的空间,就是 Python 的 for 循环,那可是 1000 万的循环,对此,Numba 提供了 prange 参数来并行计算,从而并发处理循环语句,只需要将 range 修改为 prange,装饰器传个参数:parallel = True,其他不变,代码改动如下:
import mathimport timefrom numba import njit, prange@njitdef is_prime(num): if num == 2:
return True if num <= 1 or not num % 2:
return False for div in range(3, int(math.sqrt(num) + 1), 2):
if not num % div:
return False return True@njit(parallel = True)def run_program(N): total = 0 for i in prange(N):
if is_prime(i):
total += 1 return totalif __name__ == '__main__':
N = 10000000 start = time.time()
total = run_program(N)
end = time.time()
print(f'total prime num is {total}')
print(f'cost {end - start}s')
现在运行一下:
python isPrime.pytotal prime num is 664579cost 1.4398791790008545s
才 1.43 秒,比 C++ 还快,Numba 真的牛逼!我又运行了两次,确认自己没看错,平均就是 1.4 秒:
![](http://image109.360doc.cn/DownloadImg/2021/04/0800/219560609_3_20210408121623770.jpg)
看到这里,Numba 又让我燃起了对 Python 的激情,我不转 C++ 了,Python 够用了。
Numba 如何做到的呢?官方文档这样介绍:它读取装饰函数的 Python 字节码,并将其与有关函数输入参数类型的信息结合起来,分析和优化代码,最后使用编译器库(LLVM)针对你的 CPU 生成量身定制的机器代码。每次调用函数时,都会使用此编译版本,你说牛逼不?
Numba 还有更多详细的用法,这里不多说,想了解的请移步官方文档。
最后的话
Python 几乎在每一个领域都有对应的解决方案,本文提到的 Numba 库就是专门解决 Python 在计算密集型任务方面性能不足的问题
原文链接:
https://mp.weixin.qq.com/s/4J6JZUP7f2k9Aq5n-fUc5w