一、运行速度是Python天生的短板
1.1 编译型语言:C++
对于编译型语言,开发完成以后需要将所有的源代码都转换成可执行程序,比如 Windows 下的.exe文件,可执行程序里面包含的就是机器码。只要我们拥有可执行程序,就可以随时运行,不用再重新编译了,也就是“一次编译,无限次运行”。
在运行的时候,我们只需要编译生成的可执行程序,不再需要源代码和编译器了,所以说编译型语言可以脱离开发环境运行。
编译型语言一般是不能跨平台的,也就是不能在不同的操作系统之间随意切换。
1.2 解释型语言:Python
对于解释型语言,每次执行程序都需要一边转换一边执行,用到哪些源代码就将哪些源代码转换成机器码,用不到的不进行任何处理。每次执行程序时可能使用不同的功能,这个时候需要转换的源代码也不一样。
因为每次执行程序都需要重新转换源代码,所以解释型语言的执行效率天生就低于编译型语言,甚至存在数量级的差距。计算机的一些底层功能,或者关键算法,一般都使用 C/C++ 实现,只有在应用层面(比如网站开发、批处理、小工具等)才会使用解释型语言。
在运行解释型语言的时候,我们始终都需要源代码和解释器,所以说它无法脱离开发环境。
1.3 速度对比:C++比Python快25倍
我不会C++语言、也没有C++语言的运行的环境,借用网友的对比结果:
编译后,运行C++代码,生成全部13-mers共6700万个大约需要2.42秒。这意味着运行相同算法,Python用时是C++的25倍多。
二、Numba使用与否的对比,计算1000万以内的素数
2.1 原生Python,计算1000万以内的素数
def U27_1000W以内的素数():
import math
import time
def is_prime(num):
if num == 2:
return True
if num <= 1 or num % 2 == 0:
return False
for div in range(3, int(math.sqrt(num) + 1), 2):
if num % div == 0:
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"1000万以内所有的素数有 {total} 个")
print(f"纯Python耗时: {end - start} 秒\b")
return end - start
2.2 Numba装饰器,计算1000万以内的素数
def U28_1000W以内的素数_Numba装饰器():
import math
import time
from numba import njit, prange
# @njit 相当于 @jit(nopython=True)
@njit
def is_prime(num):
if num == 2: # 2为素数
return True
if num <= 1 or num % 2 == 0: # 偶数中除了2都不是素数
return False
for div in range(3, int(math.sqrt(num) + 1), 2):
if num % div == 0:
return False
return True
#使用Numba的prange来进行并发循环计算
@njit(parallel = True)
def run_program(N):
total = 0
#使用Numba提供的prange参数来进行并行计算
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"1000万以内所有的素数有 {total} 个")
print(f"Numba装饰器耗时: {end - start} 秒\b")
return end - start
2.3 实测速度:使用numba装饰器,速度提升 22.0 倍,逼近C++
t0 = U27_1000W以内的素数()
t1 = U28_1000W以内的素数_Numba装饰器()
print(f'使用numba装饰器,速度提升 {round(t0/t1, 0)} 倍')
1000万以内所有的素数有 664579 个
纯Python耗时: 86.78110885620117 秒
1000万以内所有的素数有 664579 个
Numba装饰器耗时: 3.9410934448242188 秒
使用numba装饰器,速度提升 22.0 倍
三、素数算法
质数也就是大于1的整数中,除了1和它本身以外不能被其他整数整除的数,也叫素数。
# 算法一:针对输入的数字x,我们可以遍历从2到x-1这个区间中的数,如果x能被这个区间中任意一个数整除,那么它就不是质数。
def is_prime1(x):
for i in range(2, x):
if x % i == 0:
return False
return True
# 算法二:对算法一的优化,事实上只需要遍历从2到√x即可。
def is_prime2(x):
for i in range(2, int(x ** 0.5) + 1):
if x % i == 0:
return False
return True
# 算法三:偶数中除了2都不是质数,且奇数的因数也没有偶数,因此可以进一步优化。
def is_prime3(x):
if x == 2:
return True
elif x % 2 == 0:
return False
for i in range(3, int(x ** 0.5) + 1, 2):
if x % i == 0:
return False
return True
# 算法四:任何一个自然数,总可以表示成以下六种形式之一:6n,6n+1,6n+2,6n+3,6n+4,6n+5(n=0,1,2...)我们可以发现,除了2和3,只有形如6n+1和6n+5的数有可能是质数。且形如6n+1和6n+5的数如果不是质数,它们的因数也会含有形如6n+1或者6n+5的数,因此可以得到如下算法:
def is_prime4(x):
if (x == 2) or (x == 3):
return True
if (x % 6 != 1) and (x % 6 != 5):
return False
for i in range(5, int(x ** 0.5) + 1, 6):
if (x % i == 0) or (x % (i + 2) == 0):
return False
return True
四、Numba
4.1 官方文档
numba 是一款可以将 python 函数编译为机器代码的JIT编译器,经过 numba 编译的python 代码(仅限数组运算),其运行速度可以接近 C 或 FORTRAN 语言。
官方文档链接:http://numba.pydata.org/numba-doc/latest/index.html