Бенчмаркинг (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. Мне будет интересно следовать этой теме и посмотреть, что думают другие. Независимо от того, отличная строгость и отличный вопрос. Спасибо, что опубликовали его.