所附的脚本针对不同大小的矩阵上的不同数量的并行进程计算numpy.congiate routine ,并记录相应的运行时间. 矩阵形状只在其第一维上变化(从1,64,64到256,64,64).共轭调用总是在1,64,64个子矩阵上进行,以确保正在处理的部分适合我系统上的L2缓存(每个内核256KB,在我的例子中L3缓存是25MB).运行该脚本将生成下图(具有略微不同的AX标签和 colored颜色 ).

enter image description here

正如您所看到的,从大约100、64、64的形状开始,运行时间取决于所使用的并行进程的数量.

What could be the cause of this ?
Or why is the dependence on the number of processes for matrices below (100,64,64) so low?
My main goal is to find a modification to this script such that the runtime becomes as independent as possible from the number of processes for matrices 'a' of arbitrary size.

In case of 20 Processes:
all 'a' matrices take at most: 20 * 16 * 256 * 64 * 64 Byte = 320MB
all 'b' sub matrices take at most: 20 * 16 * 1 * 64 * 64 Byte = 1.25MB
So all sub matrices fit simultaneously in L3 cache as well as individually in the L2 cache per core of my CPU. I did only use physical cores no hyper-threading for these tests.

以下是脚本:

from multiprocessing import Process, Queue
import time
import numpy as np
import os
from matplotlib import pyplot as plt
os.environ['OPENBLAS_NUM_THREADS'] = '1'
os.environ['MKL_NUM_THREADS'] = '1'

def f(q,size):
    a = np.random.rand(size,64,64) + 1.j*np.random.rand(size,64,64)
    start = time.time()
    n=a.shape[0]
    for i in range(20):
        for b in a:
            b.conj()
    duration = time.time()-start
    q.put(duration)

def speed_test(number_of_processes=1,size=1):
    number_of_processes = number_of_processes
    process_list=[]
    queue = Queue()
    #Start processes
    for p_id in range(number_of_processes):
        p = Process(target=f,args=(queue,size))
        process_list.append(p)
        p.start()
    #Wait until all processes are finished
    for p in process_list:
        p.join()

    output = []
    while queue.qsize() != 0:
        output.append(queue.get())
    return np.mean(output)

if __name__ == '__main__':
    processes=np.arange(1,20,3)
    data=[[] for i in processes]
    for p_id,p in enumerate(processes):
        for size_0 in range(1,257):
            data[p_id].append(speed_test(number_of_processes=p,size=size_0))

    fig,ax = plt.subplots()
    for d in data:
        ax.plot(d)
    ax.set_xlabel('Matrix Size: 1-256,64,64')
    ax.set_ylabel('Runtime in seconds')

    fig.savefig('result.png')

推荐答案

这个问题至少是两个复杂效应的组合造成的:cache-thrashingfrequency-scaling.我可以在我的6核i5-9600KF处理器上重现这种效果.


缓存抖动

最大的影响来自缓存颠簸问题.通过查看RAM吞吐量可以很容易地跟踪它.实际上,1个工艺是4GiB/s,6个工艺是20GiB/s.读吞吐量与写吞吐量相似,因此吞吐量是对称的.我的RAM能够达到~40 GiB/s,但通常只有在混合读/写模式下才能达到~32 GiB/s.这意味着RAM压力相当大.此类用例通常出现在两种情况下:

  • 因为高速缓存不够大,所以数组从RAM读/写回RAM;
  • 对存储器中的不同位置进行许多访问,但它们被映射在L3中的相同高速缓存线中.

乍一看,第一种情况在这里发生的可能性要大得多,因为数组是连续的并且非常大(不幸的是,另一种影响也会发生,见下文).事实上,主要问题是a数组太大,无法放入L3.事实上,当规模是128的时候,a128*64*64*8*2 = 8 MiB/process更重要.实际上,a是由两个必须读取的数组构建的,因此缓存中所需的空间是它的3倍:即.>;24 MiB/进程.问题是所有进程都分配相同的内存量,所以bigger the number of processes the bigger the cumulative space taken by 100.当累积空间大于缓存时,处理器需要write data to the RAM and read it back,这是很慢的.

事实上,这更糟糕:进程不是完全同步的,因此一些进程可能会因为填充a而刷新其他进程需要的数据.

此外,b.conj()会创建一个新数组,该数组可能不会每次都以相同的内存分配进行分配,因此处理器还需要写回数据.这种效果取决于所使用的低级分配器.可以使用out参数so来解决此问题.话虽如此,这个问题在我的机器上并不明显(使用out比使用6个进程快2%,用1个进程同样快).

简而言之,更多的进程访问更多的数据,全局数据量无法容纳在CPU缓存中,这会降低性能,因为需要反复重新加载array.


频率定标

现代处理器使用频率zoom (如Turbo-Boost),以使(相当)顺序应用程序更快,但它们在进行计算时不能对所有内核使用相同的频率,因为处理器的频率为limited power budget.这个结果是lower theoretical scalability分.问题是所有进程都在做相同的工作,所以在N个核上运行N个进程不是N次,比在1个核上运行1个进程要花费更多的时间.

当创建一个进程时,两个核心以4550-4600 MHz运行(其他核心以3700 MHz运行),而当6个进程运行时,所有核心以4300 MHz运行.这足以解释我的机器上高达7%的差异.

你很难控制涡轮频率,但你可以完全禁用它,或者控制频率,这样最小频率和最大频率都被设置为基本频率.请注意,处理器在病理情况下可以自由使用低得多的频率(即,达到临界温度时的节流).我确实看到了通过调整频率来改善行为(在实践中提高了7%~10%).


其他影响

当进程数等于核数时,与为其他任务留出一个核时相比,操作系统对进程进行更多的上下文切换.上下文切换会略微降低进程的性能.在分配了所有内核时尤其如此,因为操作系统调度程序更难避免不必要的migrations.这通常发生在运行多个进程的PC上,但在计算机上运行的进程不多.在我的机器上,这个开销大约是5%-10%.

请注意,进程数不应超过核心数(并且不应超过超线程数).超过这个限制,性能很难预测,并且会出现许多复杂的开销(主要是调度问题).

Python相关问答推荐

Python在tuple上操作不会通过整个单词匹配

如何更改分组条形图中条形图的 colored颜色 ?

Python中绕y轴曲线的旋转

Pandas:将多级列名改为一级

海上重叠直方图

给定高度约束的旋转角解析求解

考虑到同一天和前2天的前2个数值,如何估算电力时间序列数据中的缺失值?

可以bcrypts AES—256 GCM加密损坏ZIP文件吗?

找到相对于列表索引的当前最大值列表""

交替字符串位置的正则表达式

python sklearn ValueError:使用序列设置数组元素

Tensorflow tokenizer问题.num_words到底做了什么?

Pandas 数据帧中的枚举,不能在枚举列上执行GROUP BY吗?

如果不使用. to_list()[0],我如何从一个pandas DataFrame中获取一个值?

为什么我只用exec()函数运行了一次文件,而Python却运行了两次?

以极轴表示的行数表达式?

对于标准的原始类型注释,从键入`和`从www.example.com `?

如何将列表从a迭代到z-以抓取数据并将其转换为DataFrame?

如何在不不断遇到ChromeDriver版本错误的情况下使用Selify?

牛郎星直方图中分类列的设置顺序