最近在写一个迭代产生句子vector的程序,会为每个sentence和word产生对应的vector。因此,需要更新的全局数据是比较大的。
由于对于sentence 的 vector 只在处理到对应的句子的时候才会更新,因此需要更多的迭代来拟合,因此,初步觉得迭代的次数会比较多。
实验的机器有16个虚拟线程,无GPU,因此我从刚开始就考虑到采用多核并行。
刚开始使用python写了一个单进程的程序验证了一下算法。 之后,有了两个选择,一个是用python的多进程加速,也就是multiprocessing这个库;另外一个选择是用C++实现,并行方面,pthread或者MPI等很多选择。
首先是不太成功的multiprocessing(问题在后面找到了),出现的困难如下:
1、共享数据:
multiprocessing库基础上的共享数据,有自带的Array, Value等共享数据类型是比较好的选择。
当然,如果数据量比较大,单进程内是用numpy.array 。 从numpy.array 转成各进程共享的 Array等数据格式的方法如下:
ori_vecs = vecs.reshape( n_rows * n_cols )
shared_array = multiprocessing.Array('d', ori_vecs)
这里第一行是将多维的numpy.array 转化为 一维,之后直接转化。不同库中的两种数据类型直接转化,可以认为具体数据的存储方式是相同的。
之后,在单个线程内,如果要将multiprocessing.Array转化为numpy.array,可以用如下代码:
vecs = numpy.frombuffer(shared_array.get_obj())
当然,原有的维度的信息肯定是丢失了,可以写一个封装的类来实现二维向量:
class Arr(object):
def __init__(self, arr, len_vec):
self.arr = arr
self.len_vec = len_vec
def __getitem__(self, index, *args):
if isinstance(index, tuple):
index = index[0]
start = index * self.len_vec
end = start + self.len_vec
return self.arr[start : end ]
在并行模型方面,我是在更新每个元素的vector 时会对全局数据加锁,这个带来了很大的性能问题。
体现在python版本上,就是用十个进程依旧只有200% 的CPU占用,200%这个数据比较精确。 在网上查,有说是进程之间通信,导致整体CPU占用过低。
当然,这个是加锁的问题导致的。
python版本的性能问题一直无解,所以我又实现了C++的版本,当然,单进程下,C++跑的更快。
手上有4台服务器,所以最初看了MPI。 类似pLDA,如果训练的句子数据拆分到4台机器上,那相应的 sentence vector 也可以拆分,通过MPI就行本地更新便可。
最终用pthread实现,是由于我并没有那么大的数据需要发布去处理,单机跑,pthread应该要简单而且快速。
直接照搬python代码里面的,只是向量运算需要自己写了一些工具类。 写完之后,多进程下CPU的占用依旧少的可怜。
用gprof 作性能分析,之后用了 gprof2dot 结合 sfdp 画了一张巨大的结构图,显示问题出在 updateVec 这个函数上,Vec是封装了C++ STL 中的 vector,加了一些向量运算的方法,更新和norm都是在加锁的情况进行的。 算法会同时更新 word vector 和 sentence vector ,对全局数据加锁,这就导致及时只是更新其中一个vector,也会堵塞所有尝试更新其他 vector 的进程, 而我的 sentence vector 有600k,呵呵...
狠狠心,把锁都去掉了,把vector 的更新从整体变为基于单个item的更新,(更新操作是一个累加梯度的过程),在10进程下,CPU终于上800%了。
把之前的python代码也类似改了一下,果然CPU占用上去了。
总结:
1、全局数据,如果更新操作非常稀疏,更新操作是一个累加累减的过程,那加锁的意义有限,而且非常影响并行的性能。
2、Python的并行程序。 由于python的设计天生就不适合多核并行,尽管有众多的库的支持,但是往往文档各方面并不成熟,如果应用稍微复杂一点,就扑街了。 纯计算的还是选择C或者C++,用的模型都比较成熟,而且文档众多,开发风险小很多。