神奇的一行代码,Python 轻松跑赢 C++

Python客栈

共 9254字,需浏览 19分钟

 ·

2021-04-08 21:00

我们会在今天推文的留言中选出三位幸运粉丝送出6.6元红包,快来和小编互动吧~

Python 是一个用途非常广泛的编程语言,拥有成千上万的第三方库,在人工智能、机器学习、自动化等方面有着广泛的应用,众所周知,Python 是动态语言,有全局解释器锁,比其他静态语言要慢,也正是这个原因,你也许会转向其他语言如 Java、C++,不过先等等,今天分享一个可以让 Python 比 C++ 还要快的技术,看完再决定要不要转吧。

今天的主角就是 Numba,Numba 是一个开源的即时编译器(JIT compiler),可将 Python 和 NumPy 的代码的转换为快速的机器码,从而提升运行速度。可以达到 C 或 FORTRAN 的速度。

这么牛逼是不是很难用呢?No,No,No,So easy,你不需要替换 Python 解释器,不需要单独编译,甚至不需要安装 C / C ++ 编译器。只需将 Numba 提供的装饰器放在 Python 函数上面就行,剩下的就交给 Numba 完成。举个简单的例子:

from numba import jit
import 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 万以内所有的素数,代码的算法逻辑是相同的:

Python 代码:

import math
import time

def 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

def run_program(N):
    total = 0
    for i in range(N):
        if is_prime(i):
            total += 1
    return total


if __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 664579
cost 47.386465072631836s

C++ 代码如下:

#include <iostream>
#include <cmath>
#include <time.h>
using namespace std;


bool isPrime(int num) {

    if (num == 2return true;
    if (num <= 1 || num % 2 == 0return false;
    double sqrt_num = sqrt(double(num));
    for (int div = 3; div <= sqrt_num; div +=2){
       if (num % div == 0return 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 664579
cost 2.36221s
c++

C++ 确实牛逼,才 2.3 秒,不过好戏还在后头,现在我们使用 Numba 来加速一下,操作很简单,不需要改动原有的代码,先导入 Numba 的 njit,再在函数上方放个装饰器 @njit 即可,其他保持不变,代码如下:

import math
import time
from numba import njit

# @njit 相当于 @jit(nopython=True) 
@njit
def 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
def run_program(N):
    total = 0
    for i in range(N):
        if is_prime(i):
            total += 1
    return total


if __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 664579
cost 3.0948808193206787s

相比 C++ 的 2.3 秒还是有一点慢,你可能会说 Python 还是不行啊。等一等,我们还有优化的空间,就是 Python 的 for 循环,那可是 1000 万的循环,对此,Numba 提供了 prange 参数来并行计算,从而并发处理循环语句,只需要将 range 修改为 prange,装饰器传个参数:parallel = True,其他不变,代码改动如下:

import math
import time
from numba import njit, prange

@njit
def 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 total


if __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.py
total prime num is 664579
cost 1.4398791790008545s

才 1.43 秒,比 C++ 还快,Numba 真的牛逼!我又运行了两次,确认自己没看错,平均就是 1.4 秒:

Python

看到这里,Numba 又让我燃起了对 Python 的激情,我不转 C++ 了,Python 够用了。

Numba 如何做到的呢?官方文档这样介绍:它读取装饰函数的 Python 字节码,并将其与有关函数输入参数类型的信息结合起来,分析和优化代码,最后使用编译器库(LLVM)针对你的 CPU 生成量身定制的机器代码。每次调用函数时,都会使用此编译版本,你说牛逼不?

Numba 还有更多详细的用法,这里不多说,想了解的请移步官方文档。

最后的话

Python 几乎在每一个领域都有对应的解决方案,本文提到的 Numba 库就是专门解决 Python 在计算密集型任务方面性能不足的问题,如果你从事机器学习、数据挖掘等领域,这个会非常有帮助,如果本文对你有用,请点赞、在看、关注支持。

············END············

书籍中奖名单




恭喜六位中奖的童鞋,快加小编微信(Mayyy530)来领取吧!

昨日留言红包名单


恭喜三位中奖的童鞋,快加小编微信(Mayyy530)来领取红包一份吧!





  1. 小米,可一点都不傻!

  2. 他只靠写代码,登上了胡润富豪榜!(文末送书)

  3. 妙啊!程序员防猝死指南来了,Python小技能咱得有!

  4. 一枚比特币变换一台车,特斯拉与比特币还要绑定多久?

  5. 笔记本电脑或经历十年来最大涨价!全球缺芯愈演愈烈



在看”和“点赞”都是对我们最好的支持 

浏览 59
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

分享
举报