Python 如何有效地利用并行性?

Python 如何有效地利用并行性?,python,performance,numpy,parallel-processing,numba,Python,Performance,Numpy,Parallel Processing,Numba,我一直在尝试利用Numba来加速大型阵列计算。我一直在以GFLOPS为单位测量计算速度,它始终远远低于我对CPU的期望 我的处理器是i9-9900k,根据float32基准测试,它应该能够超过200gflops。在我的测试中,我从未超过过50千兆次。这在所有8个内核上运行 在单个内核上,我实现了大约17千兆次的吞吐量,这(我相信)是理论性能的50%。我不确定这是否可以改进,但它不能很好地扩展到多核是一个问题 我正在努力学习这一点,因为我正计划编写一些图像处理代码,这些代码迫切需要尽可能提高速度。

我一直在尝试利用Numba来加速大型阵列计算。我一直在以GFLOPS为单位测量计算速度,它始终远远低于我对CPU的期望

我的处理器是i9-9900k,根据float32基准测试,它应该能够超过200gflops。在我的测试中,我从未超过过50千兆次。这在所有8个内核上运行

在单个内核上,我实现了大约17千兆次的吞吐量,这(我相信)是理论性能的50%。我不确定这是否可以改进,但它不能很好地扩展到多核是一个问题

我正在努力学习这一点,因为我正计划编写一些图像处理代码,这些代码迫切需要尽可能提高速度。我也觉得我应该先了解这一点,然后再涉足GPU计算

下面是一些示例代码,其中包含我编写快速函数的一些尝试。我正在测试的操作是将一个数组乘以一个float32,然后对整个数组求和,即MAC操作

我怎样才能得到更好的结果

import os
# os.environ["NUMBA_ENABLE_AVX"] = "1"
import numpy as np
import timeit
from timeit import default_timer as timer
import numba
# numba.config.NUMBA_ENABLE_AVX = 1
# numba.config.LOOP_VECTORIZE = 1
# numba.config.DUMP_ASSEMBLY = 1
from numba import float32, float64
from numba import jit, njit, prange
from numba import vectorize
from numba import cuda

lengthY = 16 # 2D array Y axis
lengthX = 2**16 # X axis
totalops = lengthY * lengthX * 2 # MAC operation has 2 operations
iters = 100
doParallel = True


@njit(fastmath=True, parallel=doParallel)
def MAC_numpy(testarray):
    output = (float)(0.0)
    multconst = (float)(.99)
    output = np.sum(np.multiply(testarray, multconst))
    return output


@njit(fastmath=True, parallel=doParallel)
def MAC_01(testarray):
    lengthX = testarray.shape[1]
    lengthY = testarray.shape[0]
    output = (float)(0.0)
    multconst = (float)(.99)
    for y in prange(lengthY):
        for x in prange(lengthX):
            output += multconst*testarray[y,x]
    return output


@njit(fastmath=True, parallel=doParallel)
def MAC_04(testarray):
    lengthX = testarray.shape[1]
    lengthY = testarray.shape[0]
    output = (float)(0.0)
    multconst = (float)(.99)
    for y in prange(lengthY):
        for x in prange(int(lengthX/4)):
            xn = x*4
            output += multconst*testarray[y,xn] + multconst*testarray[y,xn+1] + multconst*testarray[y,xn+2] + multconst*testarray[y,xn+3]
    return output



# ======================================= TESTS =======================================

testarray = np.random.rand(lengthY, lengthX)

# ==== MAC_numpy ====
time = 1000
for n in range(iters):
    start = timer()
    output = MAC_numpy(testarray)
    end = timer()
    if((end-start) < time): #get shortest time
        time = end-start
print("\nMAC_numpy")
print("output = %f" % (output))
print(type(output))
print("fastest time = %16.10f us" % (time*10**6))
print("Compute Rate = %f GFLOPS" % ((totalops/time)/10**9))

# ==== MAC_01 ====
time = 1000
lengthX = testarray.shape[1]
lengthY = testarray.shape[0]
for n in range(iters):
    start = timer()
    output = MAC_01(testarray)
    end = timer()
    if((end-start) < time): #get shortest time
        time = end-start
print("\nMAC_01")
print("output = %f" % (output))
print(type(output))
print("fastest time = %16.10f us" % (time*10**6))
print("Compute Rate = %f GFLOPS" % ((totalops/time)/10**9))

# ==== MAC_04 ====
time = 1000
for n in range(iters):
    start = timer()
    output = MAC_04(testarray)
    end = timer()
    if((end-start) < time): #get shortest time
        time = end-start
print("\nMAC_04")
print("output = %f" % (output))
print(type(output))
print("fastest time = %16.10f us" % (time*10**6))
print("Compute Rate = %f GFLOPS" % ((totalops/time)/10**9))
导入操作系统
#操作系统环境[“NUMBA_ENABLE_AVX”]=“1”
将numpy作为np导入
导入时间信息
从timeit导入默认\u计时器作为计时器
进口麻木
#numba.config.numba_ENABLE_AVX=1
#numba.config.LOOP\u VECTORIZE=1
#numba.config.DUMP_程序集=1
从numba导入float32,float64
来自numba进口jit、njit、prange
从numba导入矢量化
来自numba import cuda
长=16#2D阵列Y轴
长度X=2**16#X轴
totalops=长*长*长*2#MAC操作有2个操作
iters=100
双平行=真
@njit(fastmath=True,parallel=doParallel)
def MAC_numpy(测试阵列):
输出=(浮动)(0.0)
multconst=(浮动)(.99)
输出=np.和(np.乘(testarray,multconst))
返回输出
@njit(fastmath=True,parallel=doParallel)
def MAC_01(测试阵列):
lengthX=测试阵列形状[1]
longing=testarray.shape[0]
输出=(浮动)(0.0)
multconst=(浮动)(.99)
对于prange中的y(冗长):
对于prange中的x(长度x):
输出+=multconst*testarray[y,x]
返回输出
@njit(fastmath=True,parallel=doParallel)
def MAC_04(测试阵列):
lengthX=测试阵列形状[1]
longing=testarray.shape[0]
输出=(浮动)(0.0)
multconst=(浮动)(.99)
对于prange中的y(冗长):
对于prange中的x(整数(长度x/4)):
xn=x*4
输出+=multconst*testarray[y,xn]+multconst*testarray[y,xn+1]+multconst*testarray[y,xn+2]+multconst*testarray[y,xn+3]
返回输出
#============================================================测试=======================================
testarray=np.random.rand(长,长)
#===MAC\u numpy====
时间=1000
对于范围内的n(iters):
开始=计时器()
输出=MAC_numpy(测试阵列)
结束=计时器()
如果((结束-开始)<时间):#获得最短时间
时间=结束-开始
打印(“\nMAC\u numpy”)
打印(“输出=%f”%(输出))
打印(类型(输出))
打印(“最快时间=%16.10f us”%(时间*10**6))
打印(“计算速率=%f GFLOPS”%((总运算/时间)/10**9))
#===MAC\U 01====
时间=1000
lengthX=测试阵列形状[1]
longing=testarray.shape[0]
对于范围内的n(iters):
开始=计时器()
输出=MAC_01(测试阵列)
结束=计时器()
如果((结束-开始)<时间):#获得最短时间
时间=结束-开始
打印(“\nMAC\u 01”)
打印(“输出=%f”%(输出))
打印(类型(输出))
打印(“最快时间=%16.10f us”%(时间*10**6))
打印(“计算速率=%f GFLOPS”%((总运算/时间)/10**9))
#==MAC\U 04====
时间=1000
对于范围内的n(iters):
开始=计时器()
输出=MAC_04(测试阵列)
结束=计时器()
如果((结束-开始)<时间):#获得最短时间
时间=结束-开始
打印(“\nMAC\u 04”)
打印(“输出=%f”%(输出))
打印(类型(输出))
打印(“最快时间=%16.10f us”%(时间*10**6))
打印(“计算速率=%f GFLOPS”%((总运算/时间)/10**9))
Q:我如何才能获得更好的结果

1st:学习如何避免做无用的工作-你可以直接消除一半的FLOP-s而不是说所有被避免的RAM-I/O-s的一半,每一个都要以每次写回
+100~350[ns]
为代价

由于MUL的分布性质,加上
(a.C+b.C)==(a+b.C
,最好先
np.sum(a)
,然后再
MUL
通过(浮点)常数求和

#utput = np.sum(np.multiply(testarray, multconst)) # AWFULLY INEFFICIENT
output = np.sum(            testarray)*multconst #######################
2nd:学习如何按照处理顺序最佳地对齐数据(缓存线重用使您更快地重新使用预取数据。不沿着这些已预取数据对齐矢量化代码的副作用只是让您的代码支付RAM访问延迟的许多倍,而不是智能地重新使用已付费的数据块。根据此原则设计对齐的工作单元ciple意味着更多的SLOC,但回报是值得的-谁现在免费获得更快的CPU+RAM,或者免费获得大约100x的加速,仅仅是因为没有编写一个设计糟糕或幼稚的循环迭代器

3rd:了解如何在
numpy
numba
代码块内有效地利用矢量化(块定向)操作,避免按numba花时间自动分析呼叫签名(您为每次调用的自动分析支付了额外的时间,而您已经设计了代码,并且确切地知道将使用哪些数据类型,那么为什么每次调用numba块时都要为自动分析支付额外的时间呢?)

4th:了解将所有相关附加成本和处理原子性投入到游戏中的扩展功能在哪里支持您获得加速的愿望,永远不要支付超过您将获得的回报的费用(至少证明附加成本是合理的…)-支付未获得任何奖励的额外费用是可能的,但没有任何有益的im