Сейчас уже многие используют библиотеку numpy в своих python-программах, поскольку она заметно ускоряет работу с данными и выполнение математических операций. Однако во многих случаях numpy работает в разы медленнее, чем она может… потому что использует только один процессор, хотя могла бы использовать все, что у вас есть.

Дело в том, что для выполнения многих операций numpy вызывает функции из библиотеки линейной алгебры. Вот в ней-то обычно и кроется проблема. К счастью, все довольно легко исправимо.

Итак, возможно три ситуации:

  • у вас не установлены никакие библиотеки линейной алгебры и тогда numpy использует встроенную библиотеку, и она, надо сказать, весьма медленная;
  • у вас уже установлены классические библиотеки типа ATLAS и BLAS, и они умеют использовать только один процессор;
  • у вас установлена современная библиотека OpenBLAS, MKL и им подобные.

Проведем простой тест. Запустите вот эту программу:

import numpy as np
size = 10000
a = np.random.random_sample((size, size))
b = np.random.random_sample((size, size))
n = np.dot(a,b)

После чего, если работаете в Linux, то запустите top, а если вы работаете в Windows, зайдите на вкладку в “Быстродействие” в диспетчере задач (вызывается по Ctrl+Shift+Esc)… Если top показывает загруженность на уровне 100%, а индикатор “Загрузка ЦП” на вкладке “Быстродействие”, наоборот, показывает значение многократно ниже 100%, значит вычислениями занято лишь одно ядро — и эта статья для вас. Те, у кого задействованы все процессоры, могут радоваться — у них все в порядке — и дальше можно не читать.

Решение для Windows

Теоретически, можно, конечно, найти исходники библиотек, перекомпилировать их и пересобрать numpy. Я даже слышал, что кто-то писал, что он видел людей, которые говорили, что им это удалось… В общем, самый простой способ — это установить научный дистрибутив Python, например, Anaconda или Canopy. В дистрибутив входит не только python и numpy, но и целая куча полезных библиотек для расчетов и визуализации.

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

Решение для Linux

На самом деле вы также можете установить готовый дистрибутив Anaconda, Canopy или что-то другое сразу со всеми библиотеками. Но если предпочитаете собирать своими руками, то читайте дальше — там есть все рецепты.

Проверка библиотек


Как вы помните, возможны два варианта:

  • у вас установлены “олдскульные” (или “устаревшие”, кому как нравится) библиотеки (например, ATLAS);
  • у вас не установлены библиотеки, и numpy использует встроенную библиотеку (которая еще медленнее)

Если у вас стоит свежая версия numpy (>1.10), то, зайдя в каталог, куда установлен numpy (обычно это /usr/local/lib/python2.7/dist-packages/numpy, но в зависимости от версии Linux и Python путь может меняться) и выполните следующие команды в консоли:

cd core
ldd multiarray.so

В более ранних версиях numpy библиотеки multiarray.so нет, зато есть _dotblas.so:

ldd _dotblas.so

Вывод команды ldd покажет вам, использует ли numpy сторонние библиотеки линейной алгебры.

linux-vdso.so.1 =>  (0x00007fffe58a2000)
libm.so.6 => /lib/x86_64-linux-gnu/libm.so.6 (0x00007f8adbff4000)
libpthread.so.0 => /lib/x86_64-linux-gnu/libpthread.so.0 (0x00007f8adbdd6000)
libc.so.6 => /lib/x86_64-linux-gnu/libc.so.6 (0x00007f8adba10000)
/lib64/ld-linux-x86-64.so.2 (0x00007f8adc68c000)

Если в листинге вы не видите libblas.so, значит ваша numpy использует свою внутреннюю библиотеку. Если видите, значит у вас стоит ATLAS или BLAS.

В любом случае сначала вам нужна правильна библиотека линейной алгебры.

Установка OpenBLAS


OpenBLAS — хорошая библиотека алгоритмов и функций линейной алгебры, которые лежат в основе современных методов анализа данных и машинного обучения.

Прежде всего вам потребуется компилятор Фортрана, поскольку OpenBLAS не совместим со стандартным компилятором g77.

sudo apt-get install gfortran

Загрузите OpenBLAS с github’а (предварительно вернувшись в подходящий для установки каталог):

git clone https://github.com/xianyi/OpenBLAS.git

Теперь зайдите в каталог и запустите сборку:

cd OpenBLAS
make FC=gfortran

Когда компиляция и сборка успешно завершатся, установите библиотеку.

sudo make install

По умолчанию, библиотека будет установлена в /opt/OpenBLAS. Если вы хотите установить ее в другое место, запустите make install с ключом PREFIX:

sudo make install PREFIX=/your/preferred/location

Переназначение библиотек


Если ранее вы выяснили, что у вас уже установлена какая-то библиотека линейной алгебры, то вам достаточно запустить команду переназначения библиотек:

sudo update-alternatives --install /usr/lib/libblas.so.3 libblas.so.3 	/opt/OpenBLAS/lib/libopenblas.so 50

После этого OpenBLAS по умолчанию станет библиотекой линейной алегбры не только для numpy, а вообще для всех ваших программ и библиотек.

И снова запустите тест, чтобы увидеть, как у вас при вычислениях теперь задействованы все процессоры.

Собираем правильный numpy


Если у вас numpy работал на встроенной библиотеке, то вам придется его пересобрать, чтобы он подхватил только что установленный OpenBLAS.

Сначала избавьтесь от дефектной библиотеки:

sudo pip uninstall numpy

После чего создайте в домашнем каталоге файл .numpy-site.cfg следующего содержания:

[default]
include_dirs = /opt/OpenBLAS/include
library_dirs = /opt/OpenBLAS/lib

[openblas]
openblas_libs = openblas
include_dirs = /opt/OpenBLAS/include
library_dirs = /opt/OpenBLAS/lib

[lapack]
lapack_libs = openblas

[atlas]
atlas_libs = openblas
libraries = openblas

Если вы ранее выбрали нестандартное расположение для OpenBLAS, то измените пути в файле. А теперь установите numpy заново:

sudo pip install numpy

Когда компиляция и установка завершатся, запустите начальный тест, чтобы убедиться, что теперь процессоры не простаивают. Вот и все.

Комментарии (14)


  1. barabashka
    30.12.2015 15:32

    Есть альтернативный способ добавить многопоточность к numpy: библиотека numexpr


    1. chersanya
      30.12.2015 19:14

      Там же вроде нет линейной алгебры?


  1. masai
    30.12.2015 19:54
    +2

    Теоретически, можно, конечно, найти исходники библиотек, перекомпилировать их и пересобрать numpy. Я даже слышал, что кто-то писал, что он видел людей, которые говорили, что им это удалось… В общем, самый простой способ — это установить научный дистрибутив Python, например, Anaconda или Canopy.


    Если не хочется ставить анаконду ради одной библиотеки, можно просто скачать Wheel-пакет для 32- или 64-битной версии Windows, собранный с MKL. Устанавливается одной командой. Например,
    pip install numpy-1.10.2+mkl-cp35-none-win_amd64.whl
    

    установит Numpy 1.10.2, собранный с MKL, для 64-битного python 3.5.

    На сайте полно скомпилированных пакетов для научных вычислений, пожалуйста, не положите его ненароком. ;)


    1. Roman_Kh
      30.12.2015 22:29

      Спасибо за отличное дополнение.
      Раньше бесплатный MKL тоже был однопоточным. Но Intel в итоге сделала community лицензию для своих библиотек без ограничений по потокам — software.intel.com/sites/campaigns/nest


  1. masai
    30.12.2015 23:45
    +2

    Забыл добавить. Посмотреть информацию о используемых библиотеках можно проще:

    import numpy as np
    np.show_config()
    

    Если установить dev-пакеты для OpenBLAS, то numpy при сборке сам подхватит библиотеку. Например, в Ubuntu нужно установить libopenblas-dev и libopenblas-base. Если достаточно версии из репозитория, то ничего самому компилировать не нужно. Достаточно установить пакеты и переустановить numpy через pip.

    Использовать pip с sudo тоже не стоит, по-моему. Лучше, когда есть только один общесистемный менеджер пакетов. Я свежие математические библиотеки устанавливаю в виртуальное окружение. Довольно удобно, легко обновлять и доустанавливать что-то, а главное — не нужны права root.


  1. Psychopompe
    31.12.2015 15:03

    А совет для тех, у кого не deb-based?


    1. masai
      31.12.2015 17:40

      Если кратко, суть статьи — перекомпилировать Numpy. Это отлично делает pip, но надо в своём дистрибутиве сперва установить OpenBLAS или MKL.


      1. Psychopompe
        04.01.2016 00:34

        Имею следующий выхлоп:

        $ dnf list installed | grep openblas
        openblas.x86_64                        0.2.15-2.fc23                    @updates
        openblas-devel.x86_64                  0.2.15-2.fc23                    @updates
        openblas-openmp.x86_64                 0.2.15-2.fc23                    @updates
        openblas-openmp64.x86_64               0.2.15-2.fc23                    @updates
        openblas-openmp64_.x86_64              0.2.15-2.fc23                    @updates
        openblas-serial64.x86_64               0.2.15-2.fc23                    @updates
        openblas-serial64_.x86_64              0.2.15-2.fc23                    @updates
        openblas-threads.x86_64                0.2.15-2.fc23                    @updates
        openblas-threads64.x86_64              0.2.15-2.fc23                    @updates
        openblas-threads64_.x86_64             0.2.15-2.fc23                    @updates
        

        $ ldd multiarray.so
        	linux-vdso.so.1 (0x00007ffc94ee8000)
        	libopenblas.so.0 => /lib64/libopenblas.so.0 (0x00007f9316707000)
        	libm.so.6 => /lib64/libm.so.6 (0x00007f9316405000)
        	libpython2.7.so.1.0 => /lib64/libpython2.7.so.1.0 (0x00007f931603b000)
        	libpthread.so.0 => /lib64/libpthread.so.0 (0x00007f9315e1e000)
        	libc.so.6 => /lib64/libc.so.6 (0x00007f9315a5d000)
        	/lib64/ld-linux-x86-64.so.2 (0x0000555786b82000)
        	libgfortran.so.3 => /lib64/libgfortran.so.3 (0x00007f9315730000)
        	libdl.so.2 => /lib64/libdl.so.2 (0x00007f931552c000)
        	libutil.so.1 => /lib64/libutil.so.1 (0x00007f9315329000)
        	libquadmath.so.0 => /lib64/libquadmath.so.0 (0x00007f93150e9000)
        	libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007f9314ed2000)
        

        Что-то не могу разобраться в значениях top, на которое из двух смотреть?
        %Cpu(s): 79.1 us, 10.4 sy,  0.0 ni,  9.3 id,  1.2 wa,  0.0 hi,  0.0 si,  0.0 st
        KiB Mem :  8010052 total,   141700 free,  4756624 used,  3111728 buff/cache
        KiB Swap:  2095100 total,  2060716 free,    34384 used.  2810328 avail Mem 
        
          PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                             
        10185 USER      20   0 2642864 2.276g  11056 R 100.0 29.8   3:54.40 python 
        

        Не пинайте сильно
        я только начал разбираться в проге основательно


        1. Roman_Kh
          04.01.2016 13:18

          После запуска top нажмите «1», тогда вместо строки "%Cpu(s)" отобразятся данные отдельно по каждому процессору.


          1. Psychopompe
            04.01.2016 19:30

            Ага, спасибо. Одно ядро загружено полностью, второе — процентов на 20. Так и должно быть?


            1. Roman_Kh
              04.01.2016 21:56

              Если вы запускаете пример из статьи, то он должен грузить на 100% все имеющиеся ядра, а у вас загружено только одно.


              1. Psychopompe
                04.01.2016 22:28

                Странно, именно его запускаю. Что может быть не так?


                1. Roman_Kh
                  05.01.2016 00:24

                  Причин может быть много.
                  У вас Linux установлен прямо на «железо» или на виртуальную машину?

                  Попробуйте запустить тест вот так:

                  $ OMP_NUM_THREADS=4 python test.py
                  


                  1. Psychopompe
                    05.01.2016 23:08

                    На железе стоит. Попробовал, результат не изменился.