0

Я использую joblib для повышения эффективности при простой задаче построения плотностей вероятности для дискретных данных. Короче говоря, меня озадачивает тот факт, что мое улучшение производительности насыщается двумя параллельными процессами, и ничего не получается благодаря тому, что у них больше. Мне также интересно узнать о других возможных подходах к оптимизации этой программы. Сначала я рассмотрю особенности проблемы в деталях.Параллелизация с joblib - Насыщенность производительности и общие соображения

Я рассматриваю двоичный массив X формы (n_samples, n_features) и вектор y классификационных меток. Для целей эксперимента, это будет делать:

import numpy as np 
X = np.random.randint(0,2,size=[n_samples,n_features]) 
y = np.random.randint(0,10,size=[n_samples,]) 

joint_probability_binary Функция принимает в качестве входных данных столбца художественного массива X (индивидуальная функция) и вектор y этикетки и выводит их совместное распределение. Ничего особенного.

def joint_probability_binary(x, y): 

    labels = list(set(y)) 
    joint = np.zeros([len(labels), 2]) 

    for i in xrange(y.shape[0]): 
     joint[y[i], x[i]] += 1 

    return joint/float(y.shape[0]) 

Теперь я хотел бы применить joint_probability_binary для каждой функции (каждый столбец) из X. Я понимаю, что эта задача (при достаточно большом значении n_samples) была бы достаточно грубой, достаточно для многопроцессорного параллелизма. Я написал последовательную и параллельную функцию для выполнения этой задачи.

from joblib import Parallel, delayed 

def joints_sequential(X, y): 
    return [joint_probability_binary(X[:,i],y) for i in range(X.shape[1])] 

def joints_parallel(X, y, n_jobs): 
    return Parallel(n_jobs=n_jobs, verbose=0)(
     delayed(joint_probability_binary)(X = X[:,i],y = y) 
     for i in range(X.shape[1])) 

Я приспособил функцию синхронизации, написанную самим Гвидо ван Россум, как представлено here, следующим образом:

import time 

def timing(f, n, **kwargs): 
    r = range(n) 
    t1 = time.clock() 
    for i in r: 
     f(**kwargs); 
     f(**kwargs); 
     f(**kwargs); 
     f(**kwargs); 
     f(**kwargs); 
     f(**kwargs); 
     f(**kwargs); 
     f(**kwargs); 
     f(**kwargs); 
     f(**kwargs); 
    t2 = time.clock() 
    return round(t2 - t1, 3) 

Наконец, чтобы изучить изменения в работе и ее зависимость от количества рабочих мест, Я бегу

tseq = timing(joints_sequential,10, X=X,y=y) 
print('Sequential list comprehension - Finished in %s sec' %tseq) 

for nj in range(1,9): 
    tpar = timing(joints_parallel,10, X=X, y=y, n_jobs=nj) 
    print('Parallel execution - %s jobs - Finished in %s sec' %(nj,tpar)) 

для n_samples = 20000 и n_features = 20, я получаю

Sequential list comprehension - Finished in 60.778 sec 
Parallel execution - 1 jobs - Finished in 61.975 sec 
Parallel execution - 2 jobs - Finished in 6.446 sec 
Parallel execution - 3 jobs - Finished in 7.516 sec 
Parallel execution - 4 jobs - Finished in 8.275 sec 
Parallel execution - 5 jobs - Finished in 8.953 sec 
Parallel execution - 6 jobs - Finished in 9.962 sec 
Parallel execution - 7 jobs - Finished in 10.382 sec 
Parallel execution - 8 jobs - Finished in 11.321 sec 

1.

Этот результат подтверждает, что есть совсем немного, чтобы быть получена от распараллеливания этой задачи (работает это на OS X с 2 ГГц Intel Core i7 с 4 ядрами). То, что я нахожу наиболее впечатляющим, однако, это то, что производительность уже насыщается для n_jobs = 2. Учитывая размер каждой задачи, мне трудно думать, что это может быть вызвано только накладными расходами Joblib, но опять же моя интуиция ограничена. Я повторил эксперимент с большими массивами, n_samples = 200000 и n_features = 40, и это приводит к тому же поведению: Последовательного список понимания - Законченное в 1230.172 сек

Parallel execution - 1 jobs - Finished in 1198.981 sec 
Parallel execution - 2 jobs - Finished in 94.624 sec 
Parallel execution - 3 jobs - Finished in 95.1 sec 
... 

Кто-нибудь есть интуиция о том, почему это могло бы быть в случае (с учетом что мой общий подход достаточно обоснован)?

2.

Наконец, с точки зрения общей оптимизации, что бы другие способы повышения производительности программы такого рода? Я подозреваю, что от написания Cython-реализации функции, которая вычисляет общую вероятность, будет многое, но у меня нет опыта с ней.

ответ

0

Мое впечатление, что это, как правило, потому, что вы переписываете ядра.На моем рабочем столе с i7-3770 я получаю следующее:

Sequential list comprehension - Finished in 25.734 sec 
Parallel execution - 1 jobs - Finished in 25.532 sec 
Parallel execution - 2 jobs - Finished in 4.302 sec 
Parallel execution - 3 jobs - Finished in 4.178 sec 
Parallel execution - 4 jobs - Finished in 4.521 sec 

Не зная больше о вашей системе, я не в состоянии помочь много. Однако часто ноутбуки имеют больше логических ядер, чем физические ядра из-за гиперпотока или других технологий. Это не задача, которая хорошо справляется с гиперпотоками. (например, вы не увидите увеличения производительности, используя дополнительные потоки, так как здесь ничего не блокируется здесь, поэтому у вас не так много шансов).

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

Чтобы повысить производительность, я бы рекомендовал написать вашу функцию joint_probability_binary() как numpy ufunc, используя их из функции pyfunc() для создания версии c. https://docs.scipy.org/doc/numpy/reference/ufuncs.html.

Numba также может помочь, но я никогда не использовал его http://numba.pydata.org/numba-doc/0.35.0/index.html.