Suponha que eu tenha uma matriz numpy grande de memória, que tenha uma função func
que aceite essa matriz gigante como entrada (junto com alguns outros parâmetros). func
com parâmetros diferentes pode ser executado em paralelo. Por exemplo:
def func(arr, param):
# do stuff to arr, param
# build array arr
pool = Pool(processes = 6)
results = [pool.apply_async(func, [arr, param]) for param in all_params]
output = [res.get() for res in results]
Se eu usar a biblioteca de multiprocessamento, essa matriz gigante será copiada várias vezes em diferentes processos.
Existe uma maneira de permitir que diferentes processos compartilhem a mesma matriz? Este objeto de matriz é somente leitura e nunca será modificado.
O que é mais complicado, se arr não é uma matriz, mas um objeto python arbitrário, existe uma maneira de compartilhá-la?
[EDITADO]
Eu li a resposta, mas ainda estou um pouco confuso. Como fork () é copiado na gravação, não devemos invocar nenhum custo adicional ao gerar novos processos na biblioteca de multiprocessamento python. Mas o código a seguir sugere uma enorme sobrecarga:
from multiprocessing import Pool, Manager
import numpy as np;
import time
def f(arr):
return len(arr)
t = time.time()
arr = np.arange(10000000)
print "construct array = ", time.time() - t;
pool = Pool(processes = 6)
t = time.time()
res = pool.apply_async(f, [arr,])
res.get()
print "multiprocessing overhead = ", time.time() - t;
saída (e, a propósito, o custo aumenta à medida que o tamanho da matriz aumenta, então eu suspeito que ainda haja sobrecarga relacionada à cópia de memória):
construct array = 0.0178790092468
multiprocessing overhead = 0.252444982529
Por que existe uma sobrecarga tão grande, se não copiamos a matriz? E que parte a memória compartilhada me salva?
Respostas:
Se você usar um sistema operacional que usa
fork()
semântica de cópia na gravação (como qualquer unix comum), desde que você nunca altere sua estrutura de dados, ela estará disponível para todos os processos filhos sem ocupar memória adicional. Você não precisará fazer nada de especial (exceto tenha certeza absoluta de não alterar o objeto).A coisa mais eficiente que você pode fazer pelo seu problema seria compactar sua matriz em uma estrutura de matriz eficiente (usando
numpy
ouarray
), coloque-a na memória compartilhada, envolva-amultiprocessing.Array
e passe-a para suas funções. Esta resposta mostra como fazer isso .Se você deseja um objeto compartilhado gravável , precisará envolvê-lo com algum tipo de sincronização ou bloqueio.
multiprocessing
fornece dois métodos para fazer isso : um usando memória compartilhada (adequado para valores simples, matrizes ou ctypes) ou umManager
proxy, em que um processo retém a memória e um gerente arbitra o acesso a ela de outros processos (mesmo em uma rede).o
Manager
abordagem pode ser usada com objetos Python arbitrários, mas será mais lenta que o equivalente usando memória compartilhada porque os objetos precisam ser serializados / desserializados e enviados entre processos.Existem inúmeras bibliotecas e abordagens de processamento paralelo disponíveis no Python .
multiprocessing
é uma biblioteca excelente e bem arredondada, mas se você tiver necessidades especiais, talvez uma das outras abordagens seja melhor.fonte
apply_async
deve fazer referência ao objeto compartilhado no escopo diretamente, e não através de seus argumentos.Encontrei o mesmo problema e escrevi uma pequena classe de utilitário de memória compartilhada para contornar isso.
Estou usando
multiprocessing.RawArray
(sem bloqueio), e também o acesso às matrizes não é sincronizado (sem bloqueio), tenha cuidado para não atirar nos próprios pés.Com a solução, recebo acelerações por um fator de aproximadamente 3 em um i7 quad-core.
Aqui está o código: sinta-se à vontade para usá-lo e melhorá-lo, e informe quaisquer bugs.
fonte
Este é o caso de uso pretendido para Ray , que é uma biblioteca para Python paralelo e distribuído. Sob o capô, ele serializa objetos usando o layout de dados Apache Arrow (que é um formato de cópia zero) e os armazena em um armazenamento de objetos de memória compartilhada para que possam ser acessados por vários processos sem criar cópias.
O código seria semelhante ao seguinte.
Se você não ligar
ray.put
, o array ainda será armazenado na memória compartilhada, mas isso será feito uma vez por chamadafunc
, o que não é o que você deseja.Observe que isso funcionará não apenas para matrizes, mas também para objetos que contêm matrizes , por exemplo, dicionários que mapeiam entradas para matrizes como abaixo.
Você pode comparar o desempenho da serialização no Ray versus pickle executando o seguinte no IPython.
A serialização com Ray é apenas um pouco mais rápida que a pickle, mas a desserialização é 1000x mais rápida por causa do uso de memória compartilhada (esse número, é claro, depende do objeto).
Veja a documentação do Ray . Você pode ler mais sobre serialização rápida usando Ray e Arrow . Note que eu sou um dos desenvolvedores Ray.
fonte
Como Robert Nishihara mencionou, o Apache Arrow facilita isso, especificamente com o armazenamento de objetos na memória do Plasma, no qual Ray é construído.
Fiz cérebro de plasma especificamente para esta razão - carregamento rápido e recarga de grandes objetos em um aplicativo Flask. É um namespace de objeto de memória compartilhada para objetos serializáveis do Apache Arrow, incluindo
pickle
'd bytestrings geradas porpickle.dumps(...)
.A principal diferença com o Apache Ray e o Plasma é que ele controla os IDs de objetos para você. Quaisquer processos, threads ou programas em execução localmente podem compartilhar os valores das variáveis chamando o nome de qualquer
Brain
objeto.fonte