Стоит ли использовать IPython параллельно с eig scipy?

Я пишу код, который должен вычислять большое количество задач на собственные значения (типичная размерность матриц - несколько сотен). Мне было интересно, можно ли ускорить процесс с помощью IPython.parallel модуль. Как бывший пользователь MATLAB и новичок в Python, я искал что-то похожее на MATLAB. parfor...

Следуя некоторым учебникам в Интернете, я написал простой код, чтобы проверить, ускоряет ли он вычисления вообще, и обнаружил, что это не так и часто фактически замедляет его (зависит от регистра). Я думаю, я мог бы упустить момент и, возможно, scipy.linalg.eig реализован таким образом, что использует все доступные ядра и, пытаясь распараллелить его, прерывает управление движком.

Вот код "parralel":

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)

Разница во времени процессора для этих двух очень тонкая. Если поверх проблемы собственных значений распараллеленная функция должна выполнить еще несколько операций с матрицами, она начнет длиться вечно, то есть как минимум в 5 раз дольше, чем непараллельный вариант.

Прав ли я в том, что проблемы с собственными значениями не совсем подходят для такого рода распараллеливания, или я упускаю суть?

Большое спасибо!

Отредактировано 29 июля 2013 г.; 12:20 BST

Следуя совету 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 виртуальных ядра.

Спасибо за любой вклад!

1 ответ

Решение

Интересная проблема. Поскольку я думаю, что можно добиться лучшего масштабирования, я исследовал производительность с небольшим "тестом". С помощью этого теста я сравнил производительность однопоточного и многопоточного 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 на direct_view с 4-мя двигателями и 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 стоит на мой взгляд.

Ранее я также провел несколько тестов на двухъядерном ноутбуке Intel, но получил несколько забавных результатов, по-видимому, ноутбук перегрелся. Но в этой системе ускорения были, как правило, немного ниже, около 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

Надеюсь это поможет:)

Другие вопросы по тегам