使用prange进行Numba并行化,当使用更多线程时速度会变慢。

6

我尝试使用numba和prange并行化循环的简单代码。但是出现了一个问题,当我使用更多线程时,速度反而变慢。为什么会发生这种情况?(CPU Ryzen 7 2700x, 8个核心,16个线程,3.7GHz)

from numba import njit, prange,set_num_threads,get_num_threads
@njit(parallel=True,fastmath=True)
def test1():
    x=np.empty((10,10))
    for i in prange(10):
        for j in range(10):
            x[i,j]=i+j

Number of threads : 1
897 ns ± 18.3 ns per loop (mean ± std. dev. of 10 runs, 100000 loops each)
Number of threads : 2
1.68 µs ± 262 ns per loop (mean ± std. dev. of 10 runs, 100000 loops each)
Number of threads : 3
2.4 µs ± 163 ns per loop (mean ± std. dev. of 10 runs, 100000 loops each)
Number of threads : 4
4.12 µs ± 294 ns per loop (mean ± std. dev. of 10 runs, 100000 loops each)
Number of threads : 5
4.62 µs ± 283 ns per loop (mean ± std. dev. of 10 runs, 100000 loops each)
Number of threads : 6
5.01 µs ± 145 ns per loop (mean ± std. dev. of 10 runs, 100000 loops each)
Number of threads : 7
5.52 µs ± 194 ns per loop (mean ± std. dev. of 10 runs, 100000 loops each)
Number of threads : 8
4.85 µs ± 140 ns per loop (mean ± std. dev. of 10 runs, 100000 loops each)
Number of threads : 9
6.47 µs ± 348 ns per loop (mean ± std. dev. of 10 runs, 100000 loops each)
Number of threads : 10
6.88 µs ± 120 ns per loop (mean ± std. dev. of 10 runs, 100000 loops each)
Number of threads : 11
7.1 µs ± 154 ns per loop (mean ± std. dev. of 10 runs, 100000 loops each)
Number of threads : 12
7.47 µs ± 159 ns per loop (mean ± std. dev. of 10 runs, 100000 loops each)
Number of threads : 13
7.91 µs ± 160 ns per loop (mean ± std. dev. of 10 runs, 100000 loops each)
Number of threads : 14
9.04 µs ± 472 ns per loop (mean ± std. dev. of 10 runs, 100000 loops each)
Number of threads : 15
9.74 µs ± 581 ns per loop (mean ± std. dev. of 10 runs, 100000 loops each)
Number of threads : 16
11 µs ± 967 ns per loop (mean ± std. dev. of 10 runs, 100000 loops each)
1个回答

6
这是非常正常的。Numba需要创建线程并在它们之间分配工作,以便可以并行执行计算。Numba可以使用不同的线程后端。默认情况下通常是OpenMP,并且默认的OpenMP实现应该是IOMP(ICC / Clang的OpenMP运行时),它尝试"仅创建线程一次"。但是,将工作在线程之间共享比迭代100个值要慢得多。现代主流处理器应该能够在小于0.1-0.2微秒的时间内顺序执行这两个嵌套循环。 Numba还应该能够展开这两个循环。 Numba函数的开销通常约为几百纳秒。与实际循环相比,Numpy数组的分配速度应该要慢得多。此外,即使以前的开销可以忽略不计,还有其他开销会导致这段代码在多个线程上明显变慢。例如,“虚假共享”会导致写操作大多被串行化,因此比如果在1个唯一线程上完成它们要慢(因为在x86-64平台上LLC上的缓存行弹跳效应)。请注意,创建线程的时间通常显着超过1微秒,因为需要系统调用。
简而言之:当要处理的工作足够大且可以有效地并行化时,请使用线程。

1
使用串行代码需要时间 x,而并行化需要时间 x/10+L,其中 L 是由于多线程引起的延迟,假定你至少有 10 个核心(1 个核心上的 10 个线程在计算任务上不会比 1 个线程更快)。多线程延迟是指将工作分配给线程并等待它们完成的延迟。 - Jérôme Richard
1
以下是一个类比:你需要在附近的商店购买10个小/轻物品。你可以自己(串行)购买,或者叫9个朋友来为你购买其他9个物品,然后给你这些物品。假设你的朋友不在附近,那么你独自完成所有工作所需的时间可能远远小于打电话告诉他们该怎么做,等待他们到来,最后检索物品的时间;)。如果物品很大,那么叫几个朋友来帮忙可能是个好主意。这就是多线程工作方式:通信较慢 - Jérôme Richard
1
在这种情况下,最好不要使用多线程。随着每个线程的工作量增加,线程开始变得有用。阈值非常依赖于您特定的机器,但是您可以考虑在计算持续时间> 1毫秒时使用多线程。 - Jérôme Richard
你能给我举一个多线程会有用的情况的例子吗? - ABZANMASTER
例如,矩阵乘法(虽然Numpy使用的BLAS库已经很好地完成了这项工作),或者在大型数组上进行任何数学密集型计算,如计算Mandelbrot集,快速傅里叶变换,大多数图像分析(假设大小合理,如480p / 720p / 1080p)等。 - Jérôme Richard
显示剩余2条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接