高性能的Python扩展(3)

简介

本文是这个系列的第三篇,我们关注于使用NumPy API为Python编写高性能的C扩展模块。在本文中,我们将使用OpenMP来并行第二部分中的实现。

回顾

Wrold是存储N体状态的一个类。我们的模拟将演化一系列时间步长下的状态。

在开始模拟时,N体被随机分配质量m,位置r和速度v。对于每个时间步长,接下来的计算有:

  1. 合力F,每个体上的合力根据所有其他体的计算。
  2. 速度v,由于力的作用每个体的速度被改变。
  3. 位置r,由于速度每个体的位置被改变。

计算力:串行代码

下面是之前文章实现中(全部的源代码在这里)的compute_F函数。这个函数计算模拟中每对体之间的相互作用力,其复杂度为O(N^2)。

性能

使用这一系列的实现,我们的代码在i5台式机上能每秒执行26427个时间步长。

计算力:并行代码

下面是重新实现的compute_F函数,它使用OpenMP来并行循环。完整的源文件src/simdomp1.c可以在github上获得。

线程局部存储

串行版本和并行版本之间最明显的不同在于Ft数组的出现。Ft数组通过每个同时运行的线程为力的计算提供线程局部存储。这些局部的数组然后在一个单独的循环中相加,从而得到一个力F的数组。 局部存储对于避免竞争条件是有必要的。可以想象一下,如果一个线程正在执行i=0和j=1,而此时另一个线程正在使用i=1和j=2。在我们的算法中,它们都试图修改F[1],这导致了一种竞争。 你可以在维基百科上阅读有关竞争条件(race conditions)的内容。

OpenMP

OpenMP API通过使用如omp_get_thread_num这样的函数和如#pragma omp parallel这样的指令来执行。 在上面的代码中,我们创建了许多有#pragma omp parallel指令的线程。private指令列出了每个线程的私有变量。所有其他变量是公有的。 #pragma omp指令允许for循环并行进行,每个线程处理不同的指数。我们使用schedule(dynamic, 8)指令来告诉编译器使用8块大小的动态调度。当每个循环花费的时间不同时,动态调度是有用的,正如这里的这种情况。 需要注意的是,在每个并行for循环的末尾有一个隐式屏障。所有的线程在任何可以提前之前都必须完成循环。 在OpenMP 网站上参见有关该API的更多信息。

性能

因为我们增加了一个额外的for循环来相加局部线程的力的数组,我们可以预期单核性能会稍受损失。在我们的测试中,在使用单线程时,OpenMP版本的运行速度比没有OpenMP的版本慢约3%。 在Intel i5台式机上,使用四核及四线程,OpenMP使用SIMD指令执行,每秒80342个时间步长。这比我们原来使用NumPy的Python实现块312倍,比我们最快的串行实现快3倍。

扩展性

上图显示OpenMP代码是如何在四核i5台式机上扩展线程数量的。标有”OMP+SIMD”的点对应的是本文中的实现。一个类似的版本可以在这里获得,没有明确的向量指令标有”OMP”。使用Python和NumPy的原始版本被用于比较(左下)。 性能可以很好的达到物理核的数量,并大幅减少额外线程的增加。请注意,这不是一般的事实,所以使用不同数量的线程来衡量你的代码总是一个好主意。

环境变量

这里有许多环境变量可以改变使用OpenMp程序的行为。这些都对性能有重大的影响,特别是对有多个物理处理器的计算机。 请参阅GNU libgomp 文档了解详细信息。

性能总结:所有实现

这里是这个系列文章中所有实现版本的基准。这个运行在一个四核Intel i5台式机上,在Debian Wheezy系统下使用gcc 4.7.2。

实现 时间步长/每秒 加速
Python+Numpy 257 1
Simple C 1 17974 70
Simple C 2 26136 102
SIMD 26423 103
OMP 4 cores 76657 298
OMP+SIMD 4 cores 80342 313

附加测试

DigitalOcean20核虚拟机

DigitalOcean20核虚拟机实例上运行上面相同的最快的实现。3个因素的最好的性能比在四核四线程的i5台式机上运行时的性能差。我不知道这个差异的原因是否是因为虚拟化和共享内核。 

RunAbove176线程虚拟机

RunAbove对他们IBM Power 8架构免费一个月的使用进行推广。为了好玩,我在他们176线程的实例上运行了相同的实现。之前我从来没有使用过PowerPC架构,我很高兴代码运行不需要任何修改。 

Cython

Matthew Honnibal提交了一个Cythond的实现,可以在这里获得。这个实现和我们当初在第一部分中的C语言实现有些类似。

结论

当性能至关重要时,一个C语言扩展可以显著提升性能。使用SIMD指令可以使性能受益并简化代码,但有移植的成本。OpenMP的支持可以轻松地增加现有代码,并能提高在多核系统中的性能。 如果您有任何疑问,意见,建议或更正,请通过联系链接告诉我。

1 收藏 评论

关于作者:Janzou

做一直倦懒的小猫咪,享受午后阳光(新浪微博:@Jan_zou) 个人主页 · 我的文章 · 13

相关文章

可能感兴趣的话题



直接登录
跳到底部
返回顶部