4

我正在编写一个必须计算大量特征值问题的代码(典型的矩阵维度是几百个)。我想知道是否可以通过使用IPython.parallelmodule. 作为前 MATLAB 用户和 Python 新手,我一直在寻找类似于 MATLAB 的parfor...

在网上的一些教程之后,我编写了一个简单的代码来检查它是否可以加快计算速度,我发现它并没有而且实际上通常会减慢它的速度(视情况而定)。我认为,我可能遗漏了一点,并且可能scipy.linalg.eig以这样一种方式实现,即它使用所有可用的内核并通过尝试并行化它来中断引擎管理。

这是“并行”代码:

import numpy as np
from scipy.linalg import eig
from IPython import parallel

#create the matrices
matrix_size = 300
matrices = {}

for i in range(100):
    matrices[i] = np.random.rand(matrix_size, matrix_size)    

rc = parallel.Client()
lview = rc.load_balanced_view()
results = {}

#compute the eigenvalues
for i in range(len(matrices)):
    asyncresult = lview.apply(eig, matrices[i], right=False)
    results[i] = asyncresult

for i, asyncresult in results.iteritems():
    results[i] = asyncresult.get()

非并行变体:

#no parallel
for i in range(len(matrices)):
    results[i] = eig(matrices[i], right=False)

两者的 CPU 时间差异非常微妙。如果在特征值问题之上,并行化函数必须进行更多矩阵运算,它开始永远持续下去,即至少比非并行化变体长 5 倍。

我对特征值问题并不真正适合这种并行化是对的,还是我错过了重点?

非常感谢!

2013 年 7 月 29 日编辑;英国夏令时 12:20

按照 moarningsun 的建议,我尝试在eig使用mkl.set_num_threads. 对于 500×500 矩阵,最少 50 次重复设置如下:

No of. threads    minimum time(timeit)    CPU usage(Task Manager) 
=================================================================
1                  0.4513775764796151                 12-13%
2                  0.36869288559927327                25-27%
3                  0.34014644287680085                38-41%
4                  0.3380558903450037                 49-53%
5                  0.33508234276183657                49-53%
6                  0.3379019065051807                 49-53%
7                  0.33858615048501406                49-53%
8                  0.34488405094054997                49-53%
9                  0.33380300334101776                49-53%
10                 0.3288481198342197                 49-53%
11                 0.3512653110685733                 49-53%

除了一个线程情况之外,没有实质性差异(也许 50 个样本有点小......)。我仍然认为我没有抓住重点,可以做很多事情来提高性能,但不确定如何做。这些是在启用了超线程的 4 核机器上运行的,提供 4 个虚拟内核。

感谢您的任何意见!

4

1 回答 1

3

有趣的问题。因为我认为应该可以实现更好的扩展,所以我用一个小的“基准”调查了性能。通过这个测试,我比较了单线程和多线程eig(通过 MKL LAPACK/BLAS 例程提供的多线程)与 IPython 并行化的性能eig。为了看看它会有什么不同,我改变了视图类型、引擎和 MKL 线程的数量以及在引擎上分布矩阵的方法。

以下是旧 AMD 双核系统上的结果:

 m_size=300, n_mat=64, repeat=3
+------------------------------------+----------------------+
| settings                           | speedup factor       |
+--------+------+------+-------------+-----------+----------+
| func   | neng | nmkl | view type   | vs single | vs multi |
+--------+------+------+-------------+-----------+----------+
| ip_map |    2 |    1 | direct_view |      1.67 |     1.62 |
| ip_map |    2 |    1 |  loadb_view |      1.60 |     1.55 |
| ip_map |    2 |    2 | direct_view |      1.59 |     1.54 |
| ip_map |    2 |    2 |  loadb_view |      0.94 |     0.91 |
| ip_map |    4 |    1 | direct_view |      1.69 |     1.64 |
| ip_map |    4 |    1 |  loadb_view |      1.61 |     1.57 |
| ip_map |    4 |    2 | direct_view |      1.15 |     1.12 |
| ip_map |    4 |    2 |  loadb_view |      0.88 |     0.85 |
| parfor |    2 |    1 | direct_view |      0.81 |     0.79 |
| parfor |    2 |    1 |  loadb_view |      1.61 |     1.56 |
| parfor |    2 |    2 | direct_view |      0.71 |     0.69 |
| parfor |    2 |    2 |  loadb_view |      0.94 |     0.92 |
| parfor |    4 |    1 | direct_view |      0.41 |     0.40 |
| parfor |    4 |    1 |  loadb_view |      1.62 |     1.58 |
| parfor |    4 |    2 | direct_view |      0.34 |     0.33 |
| parfor |    4 |    2 |  loadb_view |      0.90 |     0.88 |
+--------+------+------+-------------+-----------+----------+

如您所见,性能增益在所使用的不同设置下差异很大,最大为常规多线程的 1.64 倍eig。在这些结果中,parfor除非在引擎上禁用 MKL 线程(使用view.apply_sync(mkl.set_num_threads, 1)),否则您使用的功能表现不佳。

改变矩阵大小也会产生显着的差异。ip_map使用 4个direct_view引擎和禁用 MKL 线程与常规多线程相比的加速eig

 n_mat=32, repeat=3
+--------+----------+
| m_size | vs multi |
+--------+----------+
|     50 |     0.78 |
|    100 |     1.44 |
|    150 |     1.71 |
|    200 |     1.75 |
|    300 |     1.68 |
|    400 |     1.60 |
|    500 |     1.57 |
+--------+----------+

显然对于相对较小的矩阵存在性能损失,对于中等大小的加速比最大,而对于较大的矩阵,加速比再次降低。我可以实现 1.75 的性能增益,这IPython.parallel在我看来是值得使用的。

我之前也在英特尔双核笔记本电脑上做了一些测试,但我得到了一些有趣的结果,显然笔记本电脑过热了。但是在那个系统上,加速比一般要低一些,最大大约 1.5-1.6。

现在我认为你的问题的答案应该是:这取决于。性能提升取决于硬件、BLAS/LAPACK 库、问题大小和IPython.parallel部署方式,以及我可能不知道的其他因素。最后但并非最不重要的一点是,它是否值得还取决于认为值得多少性能提升。

我使用的代码:

from __future__ import print_function
from numpy.random import rand
from IPython.parallel import Client
from mkl import set_num_threads
from timeit import default_timer as clock
from scipy.linalg import eig
from functools import partial
from itertools import product

eig = partial(eig, right=False)  # desired keyword arg as standard

class Bench(object):
    def __init__(self, m_size, n_mat, repeat=3):
        self.n_mat = n_mat
        self.matrix = rand(n_mat, m_size, m_size)
        self.repeat = repeat
        self.rc = Client()

    def map(self):
        results = map(eig, self.matrix)

    def ip_map(self):
        results = self.view.map_sync(eig, self.matrix)

    def parfor(self):
        results = {}
        for i in range(self.n_mat):
            results[i] = self.view.apply_async(eig, self.matrix[i,:,:])
        for i in range(self.n_mat):
            results[i] = results[i].get()

    def timer(self, func):
        t = clock()
        func()
        return clock() - t

    def run(self, func, n_engines, n_mkl, view_method):
        self.view = view_method(range(n_engines))
        self.view.apply_sync(set_num_threads, n_mkl)
        set_num_threads(n_mkl)
        return min(self.timer(func) for _ in range(self.repeat))

    def run_all(self):
        funcs = self.ip_map, self.parfor
        n_engines = 2, 4
        n_mkls = 1, 2
        views = self.rc.direct_view, self.rc.load_balanced_view
        times = []
        for n_mkl in n_mkls:
            args = self.map, 0, n_mkl, views[0]
            times.append(self.run(*args))
        for args in product(funcs, n_engines, n_mkls, views):
            times.append(self.run(*args))
        return times

不知道是否重要,但要启动我在命令行中键入的 4 个 IPython 并行引擎:

ipcluster start -n 4

希望这可以帮助 :)

于 2013-08-13T15:17:14.697 回答