Бенчмаркинг (python против С++ с использованием BLAS) и (numpy)
Я хотел бы написать программу, которая широко использует функциональные возможности линейной алгебры BLAS и LAPACK. Поскольку производительность является проблемой, я сделал некоторый бенчмаркинг и хотел бы знать, если подход, который я принял, является законным.
У меня есть, так сказать, три участника и хочу проверить их производительность с помощью простого матрично-матричного умножения. Конкурсанты:
- Numpy, используя только функциональные возможности
dot
.
- Python, вызывающий функции BLAS через общий объект.
- С++, вызывающий функции BLAS через общий объект.
Сценарий
Я реализовал матрично-матричное умножение для разных измерений i
. i
работает от 5 до 500 с шагом 5, а матрицы m1
и m2
настраиваются следующим образом:
m1 = numpy.random.rand(i,i).astype(numpy.float32)
m2 = numpy.random.rand(i,i).astype(numpy.float32)
1. Numpy
Используемый код выглядит так:
tNumpy = timeit.Timer("numpy.dot(m1, m2)", "import numpy; from __main__ import m1, m2")
rNumpy.append((i, tNumpy.repeat(20, 1)))
2. Python, вызывающий BLAS через общий объект
С помощью функции
_blaslib = ctypes.cdll.LoadLibrary("libblas.so")
def Mul(m1, m2, i, r):
no_trans = c_char("n")
n = c_int(i)
one = c_float(1.0)
zero = c_float(0.0)
_blaslib.sgemm_(byref(no_trans), byref(no_trans), byref(n), byref(n), byref(n),
byref(one), m1.ctypes.data_as(ctypes.c_void_p), byref(n),
m2.ctypes.data_as(ctypes.c_void_p), byref(n), byref(zero),
r.ctypes.data_as(ctypes.c_void_p), byref(n))
тестовый код выглядит следующим образом:
r = numpy.zeros((i,i), numpy.float32)
tBlas = timeit.Timer("Mul(m1, m2, i, r)", "import numpy; from __main__ import i, m1, m2, r, Mul")
rBlas.append((i, tBlas.repeat(20, 1)))
3. С++, вызов BLAS через общий объект
Теперь код С++, естественно, немного длиннее, поэтому я свожу информацию к минимуму.
Я загружаю функцию с помощью
void* handle = dlopen("libblas.so", RTLD_LAZY);
void* Func = dlsym(handle, "sgemm_");
Я измеряю время с помощью gettimeofday
следующим образом:
gettimeofday(&start, NULL);
f(&no_trans, &no_trans, &dim, &dim, &dim, &one, A, &dim, B, &dim, &zero, Return, &dim);
gettimeofday(&end, NULL);
dTimes[j] = CalcTime(start, end);
где j
- это цикл, выполняющийся 20 раз. Я вычисляю время, прошедшее с помощью
double CalcTime(timeval start, timeval end)
{
double factor = 1000000;
return (((double)end.tv_sec) * factor + ((double)end.tv_usec) - (((double)start.tv_sec) * factor + ((double)start.tv_usec))) / factor;
}
Результаты
Результат показан на следующем рисунке:
![enter image description here]()
Вопросы
- Как вы думаете, мой подход справедлив или есть некоторые ненужные накладные расходы, которых я могу избежать?
- Ожидаете ли вы, что результат будет показывать такое огромное несоответствие между подходом С++ и python? Оба используют общие объекты для своих вычислений.
- Поскольку я предпочитаю использовать python для своей программы, что я могу сделать, чтобы повысить производительность при вызове процедур BLAS или LAPACK?
Скачать
Полный эталонный тест можно скачать здесь. (Дж. Ф. Себастьян сделал эту ссылку возможной ^^)
Ответы
Ответ 1
Я запустил ваш тест. Нет разницы между С++ и numpy на моей машине:
![woltan's benchmark]()
Как вы думаете, мой подход справедлив, или есть некоторые ненужные накладные расходы, которых я могу избежать?
Это кажется справедливым из-за отсутствия различий в результатах.
Ожидаете ли вы, что результат будет показывать такое огромное несоответствие между подходом С++ и python? Оба используют общие объекты для своих вычислений.
Нет.
Так как я предпочитаю использовать python для своей программы, что я могу сделать для повышения производительности при вызове процедур BLAS или LAPACK?
Убедитесь, что numpy использует оптимизированную версию библиотек BLAS/LAPACK в вашей системе.
Ответ 2
ОБНОВЛЕНИЕ (30.07.2014):
Я снова запускаю тест на нашем новом HPC.
Как аппаратное обеспечение, так и программный стек изменились с установки в исходном ответе.
Я помещал результаты в таблицу google (также содержит результаты исходного ответа).
Оборудование
Наш HPC имеет два разных узла с процессорами Intel Sandy Bridge и один с новыми процессорами Ivy Bridge:
Sandy (MKL, OpenBLAS, ATLAS):
- CPU: 2 x 16 Intel (R) Xeon (R) E2560 Sandy Bridge @2,00 ГГц (16 ядер)
- ОЗУ: 64 ГБ
Ivy (MKL, OpenBLAS, ATLAS):
- CPU: 2 x 20 Intel (R) Xeon (R) E2680 V2 Ivy Bridge @2,80 ГГц (20 ядер, с HT = 40 ядер)
- ОЗУ: 256 ГБ
Программное обеспечение
Программный стек предназначен для обоих узлов sam. Вместо GotoBLAS2 используется OpenBLAS, а также есть многопоточный ATLAS BLAS, который настроен на 8 потоков (hardcoded).
- ОС: Suse
- Компилятор Intel: ictce-5.3.0
- Numpy: 1.8.0
- OpenBLAS: 0.2.6
- ATLAS:: 3.8.4
Контрольная точка Dot-Product
Контрольный код такой же, как и ниже. Однако для новых машин я также использовал контрольный показатель для размеров матрицы 5000 и 8000.
В приведенной ниже таблице приведены результаты тестов исходного ответа (переименованные: MKL → Nehalem MKL, Netlib Blas → Nehalem Netlib BLAS и т.д.)
![Matrix multiplication (sizes=[1000,2000,3000,5000,8000])]()
Производительность с одной резьбой:
![single threaded performance]()
Многопоточная производительность (8 потоков):
![multi-threaded (8 threads) performance]()
Потоки против размера матрицы (Ivy Bridge MKL):
![Matrix-size vs threads]()
Benchmark Suite
![benchmark suite]()
Производительность с одной резьбой:
![enter image description here]()
Производительность с несколькими потоками (8 потоков):
![enter image description here]()
Заключение
Новые результаты тестов аналогичны тем, что были в исходном ответе. OpenBLAS и MKL выполняются на одном уровне, за исключением теста Собственное значение.
Тест Собственные значения работает достаточно хорошо на OpenBLAS в режиме с одним потоком.
В многопоточном режиме производительность хуже.
Таблица "Матрица по сравнению с диаграммой потоков" также показывает, что хотя MKL, а также OpenBLAS обычно хорошо масштабируются с количеством ядер/потоков, это зависит от размера матрицы. Для небольших матриц добавление большего количества ядер не улучшит производительность.
Также наблюдается увеличение производительности примерно на 30% от Sandy Bridge до Ivy Bridge, что может быть связано с более высокой тактовой частотой (+ 0,8 ГГц) и/или лучшей архитектурой,
Оригинальный ответ (04.10.2011):
Некоторое время назад мне приходилось оптимизировать вычисления/алгоритмы линейной алгебры, написанные на питоне с использованием numpy и BLAS, поэтому я тестировал/тестировал различные конфигурации numpy/BLAS.
В частности, я тестировал:
- Numpy с ATLAS
- Numpy с GotoBlas2 (1.13)
- Numpy с MKL (11.1/073)
- Numpy with Accelerate Framework (Mac OS X)
Я выполнил два разных теста:
- простой точечный продукт матриц разного размера
- Набор тестов, который можно найти здесь.
Вот мои результаты:
Машины
Linux (MKL, ATLAS, No-MKL, GotoBlas2):
- ОС: Ubuntu Lucid 10.4 64 бит.
- CPU: 2 x 4 Intel (R) Xeon (R) E5504 @2,00 ГГц (8 ядер)
- ОЗУ: 24 ГБ
- Компилятор Intel: 11.1/073
- Scipy: 0.8
- Numpy: 1.5
Mac Book Pro (ускорение):
- ОС: Mac OS X Snow Leopard (10.6)
- CPU: 1 Intel Core 2 Duo 2.93 Ghz (2 ядра)
- ОЗУ: 4 ГБ
- Scipy: 0.7
- Numpy: 1.3
Mac Server (ускорение):
- OS: Mac OS X Snow Leopard Server (10.6)
- CPU: 4 X Intel (R) Xeon (R) E5520 @2.26 Ghz (8 ядер)
- ОЗУ: 4 ГБ
- Scipy: 0.8
- Numpy: 1.5.1
Тест производительности продукта
Код:
import numpy as np
a = np.random.random_sample((size,size))
b = np.random.random_sample((size,size))
%timeit np.dot(a,b)
Результаты
System | size = 1000 | size = 2000 | size = 3000 |
netlib BLAS | 1350 ms | 10900 ms | 39200 ms |
ATLAS (1 CPU) | 314 ms | 2560 ms | 8700 ms |
MKL (1 CPUs) | 268 ms | 2110 ms | 7120 ms |
MKL (2 CPUs) | - | - | 3660 ms |
MKL (8 CPUs) | 39 ms | 319 ms | 1000 ms |
GotoBlas2 (1 CPU) | 266 ms | 2100 ms | 7280 ms |
GotoBlas2 (2 CPUs)| 139 ms | 1009 ms | 3690 ms |
GotoBlas2 (8 CPUs)| 54 ms | 389 ms | 1250 ms |
Mac OS X (1 CPU) | 143 ms | 1060 ms | 3605 ms |
Mac Server (1 CPU)| 92 ms | 714 ms | 2130 ms |
![Dot product benchmark - chart]()
Benchmark Suite
Код:
Дополнительную информацию о наборе тестов см. В здесь.
Результаты:
System | eigenvalues | svd | det | inv | dot |
netlib BLAS | 1688 ms | 13102 ms | 438 ms | 2155 ms | 3522 ms |
ATLAS (1 CPU) | 1210 ms | 5897 ms | 170 ms | 560 ms | 893 ms |
MKL (1 CPUs) | 691 ms | 4475 ms | 141 ms | 450 ms | 736 ms |
MKL (2 CPUs) | 552 ms | 2718 ms | 96 ms | 267 ms | 423 ms |
MKL (8 CPUs) | 525 ms | 1679 ms | 60 ms | 137 ms | 197 ms |
GotoBlas2 (1 CPU) | 2124 ms | 4636 ms | 147 ms | 456 ms | 743 ms |
GotoBlas2 (2 CPUs)| 1560 ms | 3278 ms | 116 ms | 295 ms | 460 ms |
GotoBlas2 (8 CPUs)| 741 ms | 2914 ms | 82 ms | 262 ms | 192 ms |
Mac OS X (1 CPU) | 948 ms | 4339 ms | 151 ms | 318 ms | 566 ms |
Mac Server (1 CPU)| 1033 ms | 3645 ms | 99 ms | 232 ms | 342 ms |
![Benchmark suite - chart]()
Установка
Установка MKL включала установку полного набора компиляторов Intel, который довольно прост. Однако из-за некоторых ошибок/проблем настройка и компиляция numpy с поддержкой MKL была немного сложной.
GotoBlas2 - это небольшой пакет, который можно легко скомпилировать как общую библиотеку. Однако из-за ошибки вам необходимо заново создать общую библиотеку после ее создания, чтобы использовать ее с numpy.
В дополнение к этому зданию он для нескольких целевых платформ не работал по какой-то причине. Поэтому мне пришлось создать файл .so для каждой платформы, для которой я хочу иметь оптимизированный файл libgoto2.so.
Если вы установите numpy из репозитория Ubuntu, он автоматически установит и настроит numpy для использования ATLAS. Установка ATLAS из источника может занять некоторое время и потребует дополнительных шагов (fortran и т.д.).
Если вы установите numpy на машине Mac OS X с Fink или Mac-портами, он либо настроит numpy на использование ATLAS, либо Apple Accelerate Framework.
Вы можете проверить, запустив ldd в файле numpy.core._dotblas или вызвав numpy.show_config().
Выводы
MKL лучше всего отслеживается GotoBlas2.
В тесте собственное значение GotoBlas2 работает на удивление хуже ожидаемого. Не знаю, почему это так.
Apple Accelerate Framework работает очень хорошо, особенно в однопоточном режиме (по сравнению с другими реализациями BLAS).
Оба GotoBlas2 и MKL очень хорошо масштабируются с количеством потоков. Поэтому, если вам приходится иметь дело с большими матрицами, запускающими его на нескольких потоках, это очень поможет.
В любом случае не используйте стандартную реализацию netlib blas, потому что она слишком медленна для любой серьезной вычислительной работы.
В нашем кластере я также установил AMD ACML, а производительность была похожа на MKL и GotoBlas2. У меня нет никаких жестких цифр.
Я лично рекомендовал бы использовать GotoBlas2, потому что он проще установить и бесплатно.
Если вы хотите ввести код на С++/C, также проверьте Eigen3, который должен превосходить MKL/GotoBlas2 в некоторых случаях и также довольно проста в использовании.
Ответ 3
Вот еще один тест (на Linux, просто введите make
): http://dl.dropbox.com/u/5453551/blas_call_benchmark.zip
http://dl.dropbox.com/u/5453551/blas_call_benchmark.png
Я не вижу принципиальной разницы между различными методами для больших матриц, между Numpy, Ctypes и Fortran. (Fortran вместо С++ --- и, если это имеет значение, ваш тест, вероятно, сломан.)
<ы > Ваша функция CalcTime
в С++, похоже, имеет ошибку знака. ... + ((double)start.tv_usec))
должен быть вместо ... - ((double)start.tv_usec))
. Возможно, ваш тест также имеет другие ошибки, например, сравнение между различными библиотеками BLAS или различными параметрами BLAS, такими как количество потоков или между реальным временем и временем процессора?
EDIT: не удалось подсчитать фигурные скобки в функции CalcTime
- это ОК.
В качестве ориентира: если вы делаете ориентир, всегда отправляйте весь код где-нибудь. Комментируя тесты, особенно когда удивляешься, без полного кода, обычно неэффективно.
Чтобы узнать, с каким BLAS Numpy связано, do:
$ python
Python 2.7.2+ (default, Aug 16 2011, 07:24:41)
[GCC 4.6.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import numpy.core._dotblas
>>> numpy.core._dotblas.__file__
'/usr/lib/pymodules/python2.7/numpy/core/_dotblas.so'
>>>
$ ldd /usr/lib/pymodules/python2.7/numpy/core/_dotblas.so
linux-vdso.so.1 => (0x00007fff5ebff000)
libblas.so.3gf => /usr/lib/libblas.so.3gf (0x00007fbe618b3000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007fbe61514000)
UPDATE. Если вы не можете импортировать numpy.core._dotblas, ваш Numpy использует свою внутреннюю резервную копию BLAS, которая медленнее и не предназначена для использования в вычислениях производительности!
Ответ от @Woltan ниже указывает, что это объяснение различий, которые он видит в Numpy против Ctypes + BLAS.
Чтобы исправить ситуацию, вам нужно либо ATLAS, либо MKL - проверьте следующие инструкции: http://scipy.org/Installing_SciPy/Linux Большинство дистрибутивов Linux поставляются с ATLAS, поэтому лучше всего установить их пакет libatlas-dev
(имя может отличаться).
Ответ 4
Учитывая строгость, которую вы показали с анализом, меня удивляют результаты. Я полагаю, что это "ответ", но только потому, что он слишком длинный для комментария и дает возможность (хотя я ожидаю, что вы это рассмотрели).
Я бы подумал, что метод numpy/python не добавит больших накладных расходов для матрицы разумной сложности, поскольку по мере увеличения сложности доля, в которой участвует python, должна быть небольшой. Меня больше интересуют результаты в правой части графика, но показанное здесь отклонение по порядку будет тревожным.
Интересно, используете ли вы самые лучшие алгоритмы, которые могут использовать numpy. Из руководства по сборке для linux:
"Build FFTW (3.1.2): Версии SciPy >= 0.7 и Numpy >= 1.2: Из-за проблем с лицензией, конфигурацией и обслуживанием поддержка FFTW была удалена в версиях SciPy >= 0.7 и NumPy >= 1.2. Вместо этого теперь используется встроенная версия fftpack.
Есть несколько способов использовать скорость FFTW, если это необходимо для вашего анализа.
Перейдите к версии Numpy/Scipy, которая включает поддержку.
Установите или создайте собственную оболочку FFTW. См. http://developer.berlios.de/projects/pyfftw/ как не одобренный пример."
Вы скомпилировали numpy с mkl? (http://software.intel.com/en-us/articles/intel-mkl/). Если вы работаете в Linux, инструкции для компиляции numpy с mkl приведены здесь: http://www.scipy.org/Installing_SciPy/Linux#head-7ce43956a69ec51c6f2cedd894a4715d5bfff974 (несмотря на URL). Ключевая часть:
[mkl]
library_dirs = /opt/intel/composer_xe_2011_sp1.6.233/mkl/lib/intel64
include_dirs = /opt/intel/composer_xe_2011_sp1.6.233/mkl/include
mkl_libs = mkl_intel_lp64,mkl_intel_thread,mkl_core
Если вы находитесь в окнах, вы можете получить скомпилированный двоичный файл с mkl (а также получить pyfftw и многие другие связанные алгоритмы) по адресу: http://www.lfd.uci.edu/~gohlke/pythonlibs/, с благодарностью Кристофу Гольке в Лаборатории флуоресцентной динамики, UC Irvine.
Предостережение. В любом случае, есть много проблем с лицензированием и т.д., о которых нужно знать, но об этом объясняет страница Intel. Опять же, я предполагаю, что вы это рассмотрели, но если вы отвечаете требованиям лицензирования (что в Linux очень легко сделать), это ускорит многозначную часть относительно использования простой автоматической сборки, даже без FFTW. Мне будет интересно следовать этой теме и посмотреть, что думают другие. Независимо от того, отличная строгость и отличный вопрос. Спасибо, что опубликовали его.