在python中多次调用函数的计算时间有什么办法可以加快吗?

Is there any way to speed up the computation time of calling a function with multiple time in python?

    import numpy as np
    import matplotlib.pyplot as plt
    from numpy import random
    import time
    from collections import Counter
   
    def simulation(N):
        I = 10
        success = 0
        M = 100
        for i in range(I):
            s = allocate(N,M)
            M -= s
            success += s
        return success

    def allocate(N,M):
        count = Counter(random.randint(N,size = M))
        success = sum(j for v,j in count.items() if j == 1)
        return success

    if __name__ == "__main__":
        start = time.perf_counter() 
        SAMPLE_SIZE = 100000
        N = np.linspace(5,45,41).astype(int)
        Ps = []
        for n in N:
            ps = []
            for _ in range(SAMPLE_SIZE):
                ps.append(simulation(n)/100)
            result = np.average(np.array(ps))
            Ps.append(result)

        elapsed = (time.perf_counter() - start)
        print("Time used:",elapsed)
        plt.scatter(N,Ps)
        plt.show()

这是我的情况。最终目标是将 SAMPLE_SIZE 设置为 10^7。但是,当我将它设置为 10^5 时,它已经需要大约 1000 秒才能 运行 它。有什么办法可以让它更高效更快?谢谢你给我建议。

首先,allocate 的实现效率不是很高:您可以使用矢量化 Numpy 函数 来做到这一点:

def allocate(N, M):
    success = np.count_nonzero(np.bincount(random.randint(N, size=M)) == 1)
    return success

大多数情况下,Numpy 函数执行一些检查和创建一些临时数组的开销。您可以使用 Numba 来解决这个问题:

import numba as nb

@nb.njit('int_(int_, int_)')
def allocate(N,M):
    tmp = np.zeros(N, np.int_)
    for i in range(M):
        rnd = np.random.randint(0, N)
        tmp[rnd] += 1
    count = 0
    for i in range(N):
        count += tmp[i] == 1
    return count

然后,您可以通过对 simulation 函数使用 Numba 装饰器 @nb.njit('int_(int_)') 来进一步加快代码速度,从而避免从 C[=43= 调用 Numba 函数的开销] 口译员。

最后,您可以通过 运行 与 Numba 并行 来加速主循环(同时避免使用 慢速列表).您还可以回收 tmp 数组,以免造成太多分配(这很昂贵并且不随内核数量扩展)。这是生成的最终代码:

import numpy as np
import matplotlib.pyplot as plt
import time
import numba as nb

# Recycle the `tmp` buffer not to do many allocations
@nb.njit('int_(int_, int_, int_[::1])')
def allocate(N, M, tmp):
    tmp.fill(0)
    for i in range(M):
        rnd = np.random.randint(0, N)
        tmp[rnd] += 1
    count = 0
    for i in range(N):
        count += tmp[i] == 1
    return count

@nb.njit('int_(int_)')
def simulation(N):
    I = 10
    success = 0
    M = 100
    tmp = np.zeros(N, np.int_) # Preallocated buffer
    for i in range(I):
        s = allocate(N, M, tmp)
        M -= s
        success += s
    return success

@nb.njit('float64(int_, int_)', parallel=True)
def compute_ps_avg(n, sample_size):
    ps = np.zeros(sample_size, dtype=np.float64)
    for i in nb.prange(sample_size):
        ps[i] = simulation(n) / 100.0
    # Note that np.average is not yet supported by Numba
    return np.mean(ps)

if __name__ == "__main__":
    start = time.perf_counter() 
    SAMPLE_SIZE = 100_000
    N = np.linspace(5,45,41).astype(int)
    Ps = [compute_ps_avg(n, SAMPLE_SIZE) for n in N]
    elapsed = (time.perf_counter() - start)
    print("Time used:",elapsed)
    plt.scatter(N,Ps)
    plt.show()

以下是我的 10 核机器上的性能结果:

Initial code:          670.6 s
Optimized Numba code:    3.9 s

生成的代码快 172 倍

80%以上的时间花在了随机数的生成上。因此,如果您希望代码更快,一种解决方案是使用 SIMD-optimized 随机数生成器来加快随机数的生成。不幸的是,AFAIK,这不可能(有效地)在 Python 中实现。你当然需要使用像 C 或 C++ 这样的本地语言来做到这一点。

我可能漏掉了重点,但您似乎可以用封闭式计算代替您的模拟。

我相信您要解决的问题是在给定 M 个球随机分布在 N 个盒子中的情况下,找到其中恰好有 1 个球的盒子的预期数量。

按照这里的答案https://math.stackexchange.com/a/66094得到封闭形式的表达式(André Nicolas 用 10 个球和 5 个盒子解决了这个问题,但你应该可以推断)

(作为旁注,我通常也会编写代码来确认我的概率计算是正确的,如果这是你正在做的事情,我很抱歉说出显而易见的事情:P)