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

Я пишу код, который должен вычислять большое количество проблем с собственными значениями (типичный размер матриц - несколько сотен). Мне было интересно, можно ли ускорить процесс с помощью модуля IPython.parallel. Как бывший пользователь 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)

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

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

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

ОТРЕДАКТИРОВАНО 29 июля 2013 г.; 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 виртуальных ядра.

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


person MKK_    schedule 24.07.2013    source источник
comment
У вас не та же проблема, что и у stackoverflow.com/ вопросы/16323743/ ? Вы уверены, что вычисления действительно проводились в нескольких ядрах?   -  person hivert    schedule 24.07.2013
comment
@hivert Спасибо. Но я думаю, что все ядра используются. При просмотре производительности в диспетчере задач Windows все восемь ядер подскакивают до 100% во время вычислений. Это плохой показатель?   -  person MKK_    schedule 24.07.2013
comment
Вы сказали, что, возможно, scipy.linalg.eig реализовано таким образом, что использует все доступные ядра, но проверяли ли вы это на самом деле? На моем двухъядерном ПК eig использует процессор на 99%.   -  person    schedule 24.07.2013
comment
@moarningsun - Да. В задаче, которую я пытаюсь решить, eig обычно использует 50-52% доступной мощности процессора, когда IPython.parallel не используется.   -  person MKK_    schedule 24.07.2013
comment
Похоже, у вас включена гиперпоточность, т.е. у вас всего 4 физических ядра плюс 4 виртуальных. В этом случае использование ЦП может быть плохим показателем.   -  person    schedule 25.07.2013
comment
лунное солнце - Вы правы. У меня 4 физических ядра плюс 4 виртуальных. Как тогда можно объяснить, что с IPython.parallel enbaled загрузка ЦП подскакивает до 100%, но вычисления выполняются медленнее? Я имею в виду, действительно ли эти виртуальные ядра мешают вычислению eig, которое по своей сути выполняется на 4 ядрах без IPython.parallel? Спасибо   -  person MKK_    schedule 25.07.2013
comment
Я удалил свой ответ, потому что я больше не уверен, что эту «вещь» можно отнести к гиперпоточности. Расчет также может быть ограничен пропускной способностью памяти (или чем-то еще ..). Может быть, вы могли бы выполнить некоторые проверки, запустив eig только для 1 потока и посмотреть, как он масштабируется с количеством движков IPython? Например, мой scipy использует подпрограммы LAPACK из MKL, поэтому я могу делать import mkl; mkl.set_num_threads(1).   -  person    schedule 29.07.2013
comment
Я столкнулся с этим вопросом, который кажется очень похожим. Кажется, что действительно гиперпоточность не очень помогает для тяжелых вычислений. Однако мне все еще немного неясно, как scipy.linalg.eig использует доступные ресурсы ЦП и можно ли это как-то улучшить, используя соответствующие инструменты, такие как IPython.parallel...   -  person MKK_    schedule 29.07.2013
comment
Обратите внимание, что может быть невозможно исправить количество потоков, используемых MKL, только предложение. Основываясь на использовании ЦП, я предполагаю, что в данном конкретном случае используется максимум 4 потока.   -  person    schedule 29.07.2013


Ответы (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

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

person Community    schedule 13.08.2013
comment
Спасибо, утро. Хотя ваш ответ действительно дает мне четкое понимание причин, он дает основанный на диагнозе ответ, основанный на хорошей и последовательной идее исследования эффекта. Из того, что вы написали, и что я заметил, «это зависит» служит лучшим ответом на опубликованный вопрос. Тем не менее, стремление к познанию внутренних причин делает меня все еще немного голодным ;). - person MKK_; 28.08.2013