2015-12-24 2 views
1

Я просто написал тривиальную программу, чтобы проверить, как cython «s prange выполняет, а вот код:Cython Прейндж не так быстро, чем один поток

from cython.parallel import prange 
import numpy as np 

def func(int r, int c): 
    cdef: 
    double[:,:] a = np.arange(r*c, dtype=np.double).reshape(r,c) 
    double total = 0 
    int i, j 

    for i in prange(r, nogil=True, schedule='static', chunksize=1): 
    for j in range(c): 
     total += a[i,j] 

    return total 

В Mac Book Pro с OMP_NUM_THREADS=3, приведенным выше кодом занимает почти 18 секунд для (r,c) = (10000, 100000), а с одной нитью занимает около 21 секунды.

Почему так мало повышения производительности? Я правильно использую этот prange?

+0

Сначала отключите 'boundschecking', поэкспериментируйте с разными типами расписаний и chunksizes и посмотрите, что вы получаете. Посмотрите на [this] (http://stackoverflow.com/questions/33193851/cython-prange-slower-for-4-threads-then-with-range/33204449#33204449) вопрос. Вы выполняете 'sum-reduction', а компиляторы отлично справляются с оптимизацией таких циклов в последовательном режиме и с помощью« OpenMP »pragmas, приведенный код сборки может быть не таким оптимальным в этом случае. – romeric

+0

@romeric, попробовал, результаты не лучше. – avocado

ответ

3

Вы рассчитали, сколько времени требуется, чтобы выделить a? В массив 10000 x 100000 float64 входит 8 ГБ памяти.

a = np.ones((10000, 100000), np.double) 

занимает 6 секунд на моем ноутбуке с 16 ГБ оперативной памяти. Если у вас нет 8 ГБ бесплатно, вы попадете на своп, и дольше будет лот. Так как func тратит почти все свое время, просто выделяя a, поэтому параллельная внешняя петля for может получить лишь небольшое дробное улучшение в общей продолжительности выполнения.

Чтобы продемонстрировать это, я изменил вашу функцию, чтобы принять a в качестве входа. В tmp.pyx:

#cython: boundscheck=False, wraparound=False, initializedcheck=False 

from cython.parallel cimport prange 

def serial(double[:, :] a): 
    cdef: 
     double total = 0 
     int i, j 
    for i in range(a.shape[0]): 
     for j in range(a.shape[1]): 
      total += a[i, j] 
    return total 

def parallel(double[:, :] a): 
    cdef: 
     double total = 0 
     int i, j 
    for i in prange(a.shape[0], nogil=True, schedule='static', chunksize=1): 
     for j in range(a.shape[1]): 
      total += a[i, j] 
    return total 

Например:

In [1]: import tmp 

In [2]: r, c = 10000, 100000 

In [3]: a = np.random.randn(r, c) # this takes ~6.75 sec 

In [4]: %timeit tmp.serial(a) 
1 loops, best of 3: 1.25 s per loop 

In [5]: %timeit tmp.parallel(a) 
1 loops, best of 3: 450 ms per loop 

Параллелизация функция дала о 2.8x скорости вверх * на моем ноутбуке с 4 ядрами, но это лишь малая часть времени, необходимого выделить a.

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


* Вы могли бы сделать немного лучше, пропуская большие куски a для каждого рабочего процесса, например, путем увеличения chunksize или с использованием schedule='guided'

+0

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

Смежные вопросы