软硬件环境

  • windows 10 64bit
  • python 3.8
  • numba 0.52.0

前言

Python是一门应用非常广泛的高级语言,但是,长久以来,Python的运行速度一直被人诟病,相比c/c++javac#javascript等一众高级编程语言,完全没有优势。

那么真的没有办法提升Python程序的运行速度吗?相信看完本文,你应该会有答案。

示例

这里以找出1000000以内的质数为例,分别计算下需要花费多长的时间?

首先来回顾下什么是质数?

质数(Prime number),又称素数,指在大于1的自然数中,除了1和该数本身外,无法被其他自然数整除(也可定义为只有1与该数本身两个因数)。举个例子,比如说数字7,从2开始一直到6,都不能被它整除,只有1和它本身7才能被7整除,所以7就是一个质数。

下面来看看python的代码实现

import math
import time


def is_prime(num):
    if num == 2:
        return True
        
    if num <= 1 or not num % 2:
        return False

    # 从3开始,到int(根号num)+1,步长是2,如3,5,7 ...
    for i in range(3, int(math.sqrt(num)) + 1, 2):
        if not num % i:
            return False

    return True


def run_program(N):
    for i in range(N):
        is_prime(i)


if __name__ == '__main__':
    N = 1000000
    start = time.time()
    run_program(N)
    end = time.time()
    print(end - start)

执行代码,可以看到在我的老旧i5机器上总共花费了5秒多

python1至50中是7的倍数的数值之和 python 7的倍数_java


python_prime

改进

大家都知道解释型语言,解释器不产生目标机器代码,而是产生中间代码,解释器通常会导致执行效率较低。

因此,问题就变成了,能不能将python代码翻译成机器码?那执行效率肯定就会大大提升了

numba就是这么一款工具,它是python的即时编译器(just-in-time compiler),它使用LLVMpython代码翻译成机器码,特别是在使用numpy数组以及循环操作上,效果最佳。

numba的使用比较简单,我们不需要更换python的解释器,只需要将numba的装饰器写在python方法上,当这个带有numba装饰器的方法被调用时,就会被just-in-time即时编译为机器代码,然后执行。

使用numba之前,我们需要安装这个库

pip install numba
或者
conda install numba

下面来看看numba版本的质数问题

import math
import time
from numba import njit


@njit(fastmath=True, cache=True)
def is_prime(num):
    if num == 2:
        return True
    if num <= 1 or not num % 2:
        return False

    for i in range(3, int(math.sqrt(num)) + 1, 2):
        if not num % i:
            return False
    return True


@njit(fastmath=True, cache=True)
def run_program(N):
    for i in range(N):
        is_prime(i)


if __name__ == '__main__':
    N = 10000000
    start = time.time()
    run_program(N)
    end = time.time()
    print(end - start)

执行上述代码,可以看到,速度提升了4倍左右,不到1秒,效果还是非常明显

python1至50中是7的倍数的数值之和 python 7的倍数_编程语言_02


python_numba_prime

最后,作为横向比较,我们使用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 i = 3; i <= sqrt_num; i += 2) {
        if (num % i == 0) 
            return false;
    }
    return true;
}

int main()
{
    int N = 1000000;
    clock_t start, end;
    start = clock();
    for (int i=0; i < N; i++) 
        isPrime(i);
    end = clock();
    cout << (end - start) / ((double)CLOCKS_PER_SEC);
    return 0;
}

编译后执行,可以看到,只花了0.4秒

python1至50中是7的倍数的数值之和 python 7的倍数_go_03


c++_prime

小结

从上面的对比示例中可以看到,使用了just-in-time compiler后(numbapypy都是类似的实现),代码的执行效率已经直逼C++等编译型语言了。

参考资料