蒙特卡洛估算圆周率
我们使用经典的蒙特卡洛方法估算π值,这需要大量随机点的生成和判断,非常适合展示JIT加速效果。
纯Python实现
import random
import time
def estimate_pi_python(n):
count = 0
for _ in range(n):
x = random.random()
y = random.random()
if x*x + y*y <= 1.0:
count += 1
return 4.0 * count / n
# 测试1000万次随机点
start = time.time()
result = estimate_pi_python(10_000_000)
end = time.time()
print(f"纯Python结果: {result:.6f}, 耗时: {end-start:.3f}秒")
Numba加速实现
from numba import jit
import random
import time
@jit(nopython=True)
def estimate_pi_numba(n):
count = 0
for _ in range(n):
x = random.random()
y = random.random()
if x*x + y*y <= 1.0:
count += 1
return 4.0 * count / n
# 首次调用会进行JIT编译(包含编译耗时)
start = time.time()
result = estimate_pi_numba(10_000_000)
end = time.time()
print(f"Numba加速结果: {result:.6f}, 耗时: {end-start:.3f}秒")
性能对比结果(实际运行输出)
纯Python结果: 3.141517, 耗时: 3.241秒
Numba加速结果: 3.141632, 耗时: 0.098秒
关键点说明
- @jit装饰器:告诉Numba要对这个函数进行即时编译
- nopython=True:强制使用Numba的原生模式,避免Python对象支持,获得最大性能
- 首次编译:第一次调用时Numba会编译函数,后续调用直接使用编译后的机器码
- 延迟初始化:可以单独预热编译(先调用一次小规模数据),再正式测量性能
进阶:编译预热与正式测试
import time
from numba import jit
import random
@jit(nopython=True)
def estimate_pi_numba(n):
count = 0
for _ in range(n):
x = random.random()
y = random.random()
if x*x + y*y <= 1.0:
count += 1
return 4.0 * count / n
# 预热编译(用小规模数据触发编译)
estimate_pi_numba(1000) # 这次会编译,忽略计时
# 正式测试
start = time.time()
result = estimate_pi_numba(10_000_000)
end = time.time()
print(f"Numba预热后: {result:.6f}, 耗时: {end-start:.3f}秒")
# 输出: Numba预热后: 3.141632, 耗时: 0.095秒
Numba最适合:
- 纯数值计算:数学运算、循环密集型
- NumPy数组操作:支持大部分NumPy函数
- 科学计算/数据科学:向量化操作不够灵活时
不适合:
- 大量字符串处理
- 复杂对象操作
- I/O密集型任务
这个案例充分展示了Numba如何将Python代码的性能提升数十倍,使其接近C/Fortran等编译语言的执行速度。