Python:对于优化问题,使用多处理比循环慢得多。我做错了什么?
我必须保证,在发布本文之前,我已经阅读了很多关于这个主题的帖子。我知道多重处理需要固定的成本,但据我所知,这似乎不是问题所在 我基本上有许多单独的优化问题,并希望并行地解决它们。以下代码是一个简单的示例:Python:对于优化问题,使用多处理比循环慢得多。我做错了什么?,python,optimization,parallel-processing,Python,Optimization,Parallel Processing,我必须保证,在发布本文之前,我已经阅读了很多关于这个主题的帖子。我知道多重处理需要固定的成本,但据我所知,这似乎不是问题所在 我基本上有许多单独的优化问题,并希望并行地解决它们。以下代码是一个简单的示例: import psutil import multiprocessing as mp import time from scipy.optimize import minimize import numpy as np pset = np.random.uniform(-10,10,500)
import psutil
import multiprocessing as mp
import time
from scipy.optimize import minimize
import numpy as np
pset = np.random.uniform(-10,10,500)
def func(x,p):
out= (x-p)**2
return out
def object(p):
def func2(x):
return func(x,p)
output = minimize(func2,
x0,
method = 'trust-constr')
xstar = output.x
return xstar
# 1. Loop
tic = time.perf_counter()
out_list = []
x0 = 0
for p in pset:
xstar = object(p)
out_list.append(xstar)
#print(np.vstack(out_list))
toc = time.perf_counter()
print(f'Loop done in {toc - tic:0.4f} seconds')
# 2. Pool
n_cpu = psutil.cpu_count(logical = False)
if __name__ == '__main__':
pool = mp.Pool(n_cpu)
#results = pool.map_async(object, pset).get()
results = pool.map(object,pset)
pool.close()
pool.join()
#print(np.vstack(results))
toc2 = time.perf_counter()
print(f'Pool done in {toc2 - toc:0.4f} seconds')
正如您所看到的,“pool”方法花费的时间更长,而且越来越多,因此需要解决的问题也越来越多(因此我推测这不是一个固定成本问题)。我的优化问题实际上比这复杂得多,虽然循环需要几分钟来解决(比如3个问题),“池”将持续运行很长时间,至少在我决定强制终止之前15分钟
这种低劣表现的原因是什么?使用并行计算解决优化问题是否存在问题?我还可以尝试其他哪些技巧来加快速度
Q:“性能不佳的原因是什么?”
欢迎来到真实的世界
你的代码会造成一些性能问题,让我们把它们挖出来,好吗
代码对对象()
函数进行了500次.map()
-ed调用,并支付了将整个python会话的n个副本生成到复制进程中的巨大前期成本(以避免垄断性GIL锁所有权的重新分配[SERIAL]
-isation,否则会出现这种情况-如果不了解GIL的详细信息,请阅读关于这一主题的其他精彩文章),但委托给这种昂贵的“远程”进程的实际工作是运行一个.minimize()
-方法,由一个平方差驱动。换句话说,所有的.cpu\u count()
-次被复制(分配的内存IO(因此交换,如果头撞到任何物理RAM大小和O/S内存管理器上限)+复制了主python解释器进程内存映像的完整范围…包括的所有数据结构…-是的,win O/S就是这样,linux O/S-es的破坏性成本最低)
所以,只打电话给一家公司是相当昂贵的产品,不是吗
下一个问题是,在来回传递参数时,SER/DES+通信成本。这意味着一些
在“那里”的过程中产生有效负载,一些B
在结果“返回”的过程中产生有效负载,因此您很高兴不会感觉到太多这种痛苦,这可能会在其他一些不太愉快的用例中伤害您的代码,但您仍然会支付它。。。是的,每次调用500个.map()
-ed时,都需要额外的附加开销时间
接下来是更糟糕的部分。已请求.cpu\u count()
-许多进程位于O/S进程调度程序队列中,以便有时间获取cpu内核并执行(对于某些O/S进程调度器授予的时间,在被强制移出CPU核心之前,以便让其他O/S分配的进程移入并执行-这就是进程调度的工作方式),您可能已经感觉到了烟雾,这是以额外的附加开销时间为代价的,由(繁重的)进程消耗提升,一旦许多进程停留在队列中等待各自的轮次
接下来是最严重的惩罚-您很容易,几乎可以肯定,开始支付更高的内存访问成本,因为在您计划的进程重新进入CPU核心时,已经失去了重用最快的计算资源的机会,即核心缓存上的L1_数据
,首先是通过将任何此类快速重复使用的数据存储在内存中由另一个进程编写,存储在那里,该进程在您的进程获得下一个CPU核心共享继续之前使用了该CPU核心,在O/S调度程序强制删除了前一个进程之后,该进程使用了该CPU核心,但留下的LRU缓存数据永远不需要重复使用&因此,您的进程将支付额外的费用重新获取数据(如果内存I/O通道允许在无需进一步等待空闲通道的情况下使用,则从主RAM支付成本…)。这很可能会在每个O/S-process-scheduler进程移出/移入周期中发生,因为在大多数情况下,您甚至无法获得与上次露营时相同的CPU核心(因此,即使没有机会推测某些残差,这些残差也可能“保留”在您的“上一轮”中的L1_数据
内核缓存中。该进程被分配给一些可用的CPU内核
最后但并非最不重要的一点——现代CPU执行另一个级别的进程2核“优化”策略,以降低达到热上限的风险。因此,进程更频繁地“移动”,以便允许它们在较冷的CPU核上工作,并保持“热”-内核冷却。该策略适用于轻负载,其中一些计算密集型进程可能会享受从热CPU内核到另一个较冷CPU内核的热驱动跳跃,因为否则,如果它留在热CPU内核上,热硅结构将降低此类CPU内核的频率,从而允许热上限n我们无法克服——是的,你明白了——随着CPU频率的降低,你的处理速度会变慢,CPU时钟会减少,计算也会减少,直到这个热CPU核心变冷(这对于繁重的计算工作来说是一种矛盾的说法,不是吗?)。如果这是针对多核CPU上的几个进程,那么这种热策略可能看起来很吸引人,可以显示从热CPU内核到冷CPU内核的高GHz时钟和热跳跃,但是,由于明显的原因…-如果您使用.map()
-进程“覆盖”所有CPU内核,则会停止工作(没有提到队列中所有其他O/S协调的进程),因此唯一的结果是,所有热CPU内核将降低CPU频率,并以较慢的速度工作-