问:如何使用更多 CPU 来运行我的 python 脚本?
先说几句,关于“游戏的因素”,更多的 CPU 可能会被计入处理任务执行的流程中:(
详细示例如下)
- 从按原样状态重新组织流程流到可行的并行代码执行方式来实现合理加速的成本
- 执行任何并行计算密集型策略的已知python限制了解
- python 脚本本身,即代码看起来会大不相同,大多数情况下如果试图利用 MPI 分布式内存并行性,“跨”一组 {cluster|grid}-connected-machines
主要消歧:
标准python
总是作为一个纯粹的[SERIAL]
解释器,总是。
[并行] 不是 [并发]
[PARALLEL]
流程是流程组织中最复杂的形式:并行流程必须同时开始、执行和完成,通常在时间限制内,因此应该避免任何不确定性阻塞或其他不确定性来源(不仅仅是“ " 即时缓解、避免、主要预防——这很难)
[CONCURRENT]
流程更容易实现,因为有更多的空闲资源,基于并发策略的进程调度器可以引导一些工作流(线程)开始在这样的空闲资源(磁盘 I/O,CPU-执行等),并且还可以“强制”此类工作在某些调度程序方决定时间量后被软信号或强制完全中断,并暂时从使用“临时借出”的设备/资源中驱逐,因此,在调度器的并发调度策略队列中发生了不确定的长时间或优先级驱动的等待之后,轮到另一个工作流(线程)候选者了。
[SERIAL]
流程是最简单的形式 - 一个接一个地,没有任何实时传递的压力 - “mañana (maˈɲana; 英语 məˈnjɑːnə) n, adv .. b. some other and later time ”
Python 解释器从那时起就被诅咒了[SERIAL]
,即使语法构造函数已经为 {基于轻量级THREAD
的 | 重量级-full-copy- PROCESS
}-based 形式的“concurrent”-code-invocations
已知轻量级形式仍然依赖于 python-GIL-lock,它通过以循环方式临时借出中央解释器的 GIL-lock 来重新[SERIAL]
调整实际执行,由恒定的时间驱动到任何大群的线程。结果终于[SERIAL]
又来了,这对于“外部”延迟屏蔽(示例)很有用,但对 HPC 级计算没有用...
即使是 GIL 转义尝试支付所有成本并利用PROCESS
基于完整副本的[CONCURRENT]
代码执行的重量级形式也并非免于头痛 - 只需仔细阅读有关崩溃的警告并在泄漏后挂起少数非常稀有的资源,直到下一次平台重启(!):
在 3.8 版更改:在 macOS 上,spawn
启动方法现在是默认值。fork
start 方法应该被认为是不安全的,因为它可能导致子进程崩溃。请参阅bpo-33725。
在 3.4 版更改: spawn
在所有 unix 平台上forkserver
添加,并为一些 unix 平台添加。子进程不再继承 Windows 上的所有父进程可继承句柄。
在 Unix 上,使用spawn
orforkserver
start 方法也将启动一个资源跟踪进程,该进程跟踪未链接的命名系统资源(例如命名信号量或SharedMemory
对象)由程序的进程创建。当所有进程都退出时,资源跟踪器会取消链接任何剩余的跟踪对象。通常应该没有,但如果一个进程被信号杀死,可能会有一些“泄露”的资源。(泄漏的信号量和共享内存段都不会在下次重新启动之前自动取消链接。这对两个对象都是有问题的,因为系统只允许有限数量的命名信号量,而共享内存段会占用主内存中的一些空间。)
我们大部分时间都会对良好的代码设计感到满意,为python进行了优化,并增加了某种智能矢量化和[CONCURRENT]
处理组织。
真正的[PARALLEL]
代码执行很可能是没有人会尝试在确定性 GIL 中断的 python[SERIAL]
代码解释器中实现的事情(截至 2019-3Q,这个游戏显然已经先验地丢失了)。
成本总是存在的。
较小的基于线程的尝试,较大的基于进程的尝试,最大的用于将代码重构为分布式内存并行(使用 MPI 进程间通信中介工具或其他形式的分布式)
每个语法技巧都有一些附加成本,即在“内部部分”(有用的代码)开始为我们工作之前,它需要多长时间[TIME]
以及需要多大的附加内存分配([SPACE]
并希望加快整体运行时间)。如果这些附加成本(处理设置成本 + 参数传输成本 + 协调和通信成本 + 结果收集成本 + 处理终止成本)是相同的,则比寻求的成本更高为了加速,你突然发现自己付出的比得到的多。
当没有用于测试热点的最终工作代码时,可能会注入类似这样的 crash-test-dummy 代码,CPU 和 RAM 将获得压力测试工作负载:
##########################################################################
#-EXTERNAL-zmq.Stopwatch()'d-.start()-.stop()-clocked-EXECUTION-----------
#
def aFATpieceOfRAMallocationAndNUMPYcrunching( aRAM_size_to_allocate = 1E9,
aCPU_load_to_generate = 20
):
#-XTRN-processing-instantiation-COSTs
#---------------------------------------------------------------------
#-ZERO-call-params-transfer-COSTs
#---------------------------------------------------------------------
#-HERE---------------------------------RAM-size'd-STRESS-TEST-WORKLOAD
_ = numpy.random.randint( -127,
127,
size = int( aRAM_size_to_allocate ),
dtype = numpy.int8
)
#---------------------------------------------------------------------
#-HERE-----------------------------------CPU-work-STRESS-TEST-WORKLOAD
# >>> aClk.start();_ = numpy.math.factorial( 2**f );aClk.stop()
# 30 [us] for f = 8
# 190 [us] for f = 10
# 1 660 [us] for f = 12
# 20 850 [us] for f = 14
# 256 200 [us] for f = 16
# 2 625 728 [us] for f = 18
# 27 775 600 [us] for f = 20
# 309 533 629 [us] for f = 22
# +3 ... ... ... [us] for f = 24+ & cluster-scheduler may kill job
# +30 ... ... ... [us] for f = 26+ & cluster-manager may block you
# ... ... ... ... [us] for f = 28+ & cluster-owner will hunt you!
#
return len( str( [ numpy.math.factorial( 2**f )
for f in range( min( 22,
aCPU_load_to_generate
)
)
][-1]
)
) #---- MAY TRY TO return( _.astype( numpy.int64 )
#------ + len( str( [numpy.math.factorial(...)...] ) )
#------ )
#------ TO TEST also the results-transfer COSTs *
#------ yet, be careful +RAM COSTs *
#------ get explode ~8+ times HERE *
#
#-EXTERNAL-ZERO-results-transfer-and-collection-COSTs
#########################################################################
如何避免面对最后的讽刺“糟糕的坏交易,不是吗?”
在您花费时间和预算之前,进行公平的分析、对热点进行基准测试并扩展超出教科书示例的数据大小。“只是编码”在这里不起作用。
为什么?
单个“错误”的SLOC 可能会将最终性能破坏为超过大约 +37% 的时间,或者可能会提高性能以花费不到 -57% 的基线处理时间。
过早的优化是非常危险的。
成本/收益分析在花费您的费用之前会告诉事实。阿姆达尔定律可以帮助您确定一个盈亏平衡点并给出一个主要限制,之后任意数量的免费资源(甚至无限多的资源(观看此完全交互式分析并尝试移动p
-slider,用于处理的[PARALLEL]
-fraction , 低于不现实的 100% 并行代码的任何地方,以便闻到现实生活中的火焰 ) )不会为您的代码处理流程带来一点加速。
隐藏的宝石永远喜欢:
numpy
等性能抛光库中的智能矢量化技巧scipy
可以并且将在内部使用多个 CPU 内核,而 python 不知道或关心这一点。学习矢量化代码技巧,您的代码将受益匪浅。
LLVM 编译器也numba
可以在以下情况下提供帮助:最终性能应该从您的 CPU 引擎中榨取,而代码不能依赖于使用智能numpy
性能技巧。
然而更难的是进入其他 {pre|jit}-compiled-fashions of python-code,以便摆脱 GIL-lock still- [SERIAL]
stepping code-execution 的陷阱。
包起来 :
拥有尽可能多的 CPU 内核总是好的。利用多处理器芯片中本地可用的所有此类 CPU 内核,在 NUMA 架构结构中更糟糕,在由至少连接的计算节点(MPI 和其他形式的基于消息的形式)的独立、松散耦合的分布式生态系统中最糟糕其他自治计算节点的协调)。
尽管“让他们确实为你工作”的实际成本可能高于实际这样做的好处(重构 +调试+ 正确性证明 + 实际工作 + 结果收集)。
帕金森定律很明确——如果出现问题,它会在可能造成最大伤害的时刻出现。
:o) 所以在前进的道路上保持乐观 - 这将是一段疯狂的旅程,我可以向你保证