Многопроцессорный пул замедляется при вызове внешнего модуля - PullRequest
0 голосов
/ 17 февраля 2019

Мой скрипт вызывает модуль librosa для вычисления кепстральных коэффициентов Mel-частоты (MFCC) для коротких фрагментов звука.После загрузки аудио я хотел бы вычислить их (вместе с некоторыми другими аудио функциями) как можно быстрее - отсюда и многопроцессорность.

Проблема: многопроцессорный вариант намного медленнее, чем последовательный.Профилирование говорит, что мой код тратит более 90% времени на <method 'acquire' of '_thread.lock' objects>.Неудивительно, если бы это было много небольших задач, но в одном тестовом примере я делю свое аудио на 4 блока и затем обрабатываю их в отдельных процессах.Я думал, что накладные расходы должны быть минимальными, но на практике это почти так же плохо, как со многими небольшими задачами.

Насколько я понимаю, многопроцессорный модуль должен форкнуть почти все, и не должно быть никакого сражения за блокировку.Тем не менее, результаты, кажется, показывают что-то другое.Может ли быть так, что librosa модуль внизу хранит какую-то внутреннюю блокировку?

Мое профилирование приводит к простому тексту: https://drive.google.com/open?id=17DHfmwtVOJOZVnwIueeoWClUaWkvhTPc

Как изображение: https://drive.google.com/open?id=1KuZyo0CurHd9GjXge5CYQhdWn2Q6OG8Z

Код для воспроизведения «проблемы»:

import time
import numpy as np
import librosa
from functools import partial
from multiprocessing import Pool

n_proc = 4

y, sr = librosa.load(librosa.util.example_audio_file(), duration=60) # load audio sample
y = np.repeat(y, 10) # repeat signal so that we can get more reliable measurements
sample_len = int(sr * 0.2) # We will compute MFCC for short pieces of audio

def get_mfcc_in_loop(audio, sr, sample_len):
    # We split long array into small ones of lenth sample_len
    y_windowed = np.array_split(audio, np.arange(sample_len, len(audio), sample_len))
    for sample in y_windowed:
        mfcc = librosa.feature.mfcc(y=sample, sr=sr)

start = time.time()
get_mfcc_in_loop(y, sr, sample_len)
print('Time single process:', time.time() - start)

# Let's test now feeding these small arrays to pool of 4 workers. Since computing
# MFCCs for these small arrays is fast, I'd expect this to be not that fast
start = time.time()
y_windowed = np.array_split(y, np.arange(sample_len, len(y), sample_len))
with Pool(n_proc) as pool:
    func = partial(librosa.feature.mfcc, sr=sr)
    result = pool.map(func, y_windowed)
print('Time multiprocessing (many small tasks):', time.time() - start)

# Here we split the audio into 4 chunks and process them separately. This I'd expect
# to be fast and somehow it isn't. What could be the cause? Anything to do about it?
start = time.time()
y_split = np.array_split(y, n_proc)
with Pool(n_proc) as pool:
    func = partial(get_mfcc_in_loop, sr=sr, sample_len=sample_len)
    result = pool.map(func, y_split)
print('Time multiprocessing (a few large tasks):', time.time() - start)

Результаты на моей машине:

  • Время одного процесса: 8,48 с
  • Времямногопроцессорная обработка (много небольших задач): 44,20 с
  • время многопроцессорной обработки (несколько крупных задач): 41,99 с

Есть идеи, что является причиной этого?А еще лучше, как сделать лучше?

1 Ответ

0 голосов
/ 19 февраля 2019

Чтобы выяснить, что происходит, я запустил top -H и заметил, что появилось +60 потоков!Это было это.Оказывается, librosa и зависимости порождают множество дополнительных потоков, которые вместе разрушают параллелизм.

Решение

Проблема переподписки хорошо описана в joblib docs .Давайте использовать это тогда.

import time
import numpy as np
import librosa
from joblib import Parallel, delayed

n_proc = 4

y, sr = librosa.load(librosa.util.example_audio_file(), duration=60) # load audio sample
y = np.repeat(y, 10) # repeat signal so that we can get more reliable measurements
sample_len = int(sr * 0.2) # We will compute MFCC for short pieces of audio

def get_mfcc_in_loop(audio, sr, sample_len):
    # We split long array into small ones of lenth sample_len
    y_windowed = np.array_split(audio, np.arange(sample_len, len(audio), sample_len))
    for sample in y_windowed:
        mfcc = librosa.feature.mfcc(y=sample, sr=sr)

start = time.time()
y_windowed = np.array_split(y, np.arange(sample_len, len(y), sample_len))
Parallel(n_jobs=n_proc, backend='multiprocessing')(delayed(get_mfcc_in_loop)(audio=data, sr=sr, sample_len=sample_len) for data in y_windowed)
print('Time multiprocessing with joblib (many small tasks):', time.time() - start)


y_split = np.array_split(y, n_proc)
start = time.time()
Parallel(n_jobs=n_proc, backend='multiprocessing')(delayed(get_mfcc_in_loop)(audio=data, sr=sr, sample_len=sample_len) for data in y_split)
print('Time multiprocessing with joblib (a few large tasks):', time.time() - start)

Результаты:

  • Время многопроцессорной обработки с JobLib (много небольших задач): 2,66
  • Время многопроцессорной обработки с JobLib (несколько больших задач): 2,65

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

...