matlab optimization parallel-processing parfor

matlab - ¿Ahorrando tiempo y memoria usando parfor?



optimization parallel-processing (2)

Considere prova.mat en MATLAB obtenido de la siguiente manera

for w=1:100 for p=1:9 A{p}=randn(100,1); end baseA_.A=A; eval([''baseA.A'' num2str(w) ''= baseA_;'']) end save(sprintf(''prova.mat''),''-v7.3'', ''baseA'')

Para tener una idea de las dimensiones reales en mis datos, la 1x9 cell en A1 está compuesta por las siguientes 9 matrices: 904x5, 913x5, 1722x5, 4136x5, 9180x5, 3174x5, 5970x5, 4455x5, 340068x5 . Los otros Aj tienen una composición similar.

Considere el siguiente código

clear all load prova tic parfor w=1:100 indA=sprintf(''A%d'', w); Aarr=baseA.(indA).A; Boot=[]; for p=1:9 C=randn(100,1).*Aarr{p}; Boot=[Boot; C]; end D{w}=Boot; end toc

Si ejecuto el bucle parfor con 4 trabajadores locales en mi Macbook Pro, me llevará 1,2 segundos. Reemplazar parfor con for toma 0.01 seg.

Con mis datos reales, la diferencia de tiempo es de 31 segundos frente a 7 segundos [la creación de la matriz C también es más complicada].

Si lo he entendido correctamente, el problema es que la computadora tiene que enviar la baseA a cada trabajador local y esto requiere tiempo y memoria.

¿Podría sugerir una solución que pueda hacer que parfor más conveniente que for ? Pensé que guardar todas las celdas en la baseA era una forma de ahorrar tiempo al cargar una vez al principio, pero tal vez me equivoque.


Corte los datos transmitidos en una matriz de celdas

El siguiente enfoque funciona para los datos que se agrupan en bucle . No importa cuál sea la variable de agrupación, siempre que se determine antes del ciclo. La ventaja de la velocidad es enorme.

Un ejemplo simplificado de tales data es el siguiente, con la primera columna que contiene una variable de agrupación:

ngroups = 1000; nrows = 1e6; data = [randi(ngroups,[nrows,1]), randn(nrows,1)]; data(1:5,:) ans = 620 -0.10696 586 -1.1771 625 2.2021 858 0.86064 78 1.7456

Ahora, suponga por simplicidad que estoy interesado en la sum() por grupo de los valores en la segunda columna. Puedo recorrer en grupo, indexar los elementos de interés y resumirlos. Realizaré esta tarea con un bucle for , un parfor simple y un parfor con datos parfor , y compararé los tiempos.

Tenga en cuenta que este es un ejemplo de juguete y no estoy interesado en soluciones alternativas como bsxfun() , este no es el punto del análisis.

Resultados

Tomando prestado el mismo tipo de trama de , primero confirmo los mismos hallazgos sobre parfor simple vs for . En segundo lugar, el método parfor supera por completo a ambos métodos en datos parfor , lo que lleva un poco más de 2 segundos en completarse en un conjunto de datos con 10 millones de filas (la operación de división se incluye en el tiempo). El parfor simple tarda 24s en completarse y casi el doble de esa cantidad de tiempo (estoy en Win7 64, R2016a e i5-3570 con 4 núcleos).

El punto principal de cortar los datos antes de comenzar el parfor es evitar:

  • la sobrecarga de toda la información que se transmite a los trabajadores,
  • operaciones de indexación en conjuntos de datos cada vez mayores.

El código

ngroups = 1000; nrows = 1e7; data = [randi(ngroups,[nrows,1]), randn(nrows,1)]; % Simple for [out,t] = deal(NaN(ngroups,1)); overall = tic; for ii = 1:ngroups tic idx = data(:,1) == ii; out(ii) = sum(data(idx,2)); t(ii) = toc; end s.OverallFor = toc(overall); s.TimeFor = t; s.OutFor = out; % Parfor try parpool(4); catch, end [out,t] = deal(NaN(ngroups,1)); overall = tic; parfor ii = 1:ngroups tic idx = data(:,1) == ii; out(ii) = sum(data(idx,2)); t(ii) = toc; end s.OverallParfor = toc(overall); s.TimeParfor = t; s.OutParfor = out; % Sliced parfor [out,t] = deal(NaN(ngroups,1)); overall = tic; c = cache2cell(data,data(:,1)); s.TimeDataSlicing = toc(overall); parfor ii = 1:ngroups tic out(ii) = sum(c{ii}(:,2)); t(ii) = toc; end s.OverallParforSliced = toc(overall); s.TimeParforSliced = t; s.OutParforSliced = out; x = 1:ngroups; h = plot(x, s.TimeFor,''xb'',x,s.TimeParfor,''+r'',x,s.TimeParforSliced,''.g''); set(h,''MarkerSize'',1) title ''Time per iteration'' ylabel ''Time [s]'' xlabel ''Iteration number[-]''; legend({sprintf(''for : %5.2fs'',s.OverallFor),... sprintf(''parfor : %5.2fs'',s.OverallParfor),... sprintf(''parfor_sliced: %5.2fs'',s.OverallParforSliced)},... ''interpreter'', ''none'',''fontname'',''courier'')

Puede encontrar cache2cell() en mi repositorio de github .

Simple para datos en rodajas

¿Se preguntará qué sucede si ejecutamos el simple for los datos cortados? Para este simple ejemplo de juguete, si eliminamos la operación de indexación cortando los datos, eliminamos el único cuello de botella del código, y el for realidad será un poco más rápido que el parfor .

Sin embargo, este es un ejemplo de juguete donde la operación de indexación toma completamente el costo del bucle interno. Por lo tanto, para que el parfor valga la pena, el bucle interno debe ser más complejo y / o extendido.

Ahorro de memoria con parfor en rodajas

Ahora, suponiendo que su ciclo interno sea más complejo y que el ciclo simple sea más lento, veamos cuánta memoria ahorramos evitando los datos transmitidos en parfor con 4 trabajadores y un conjunto de datos con 50 millones de filas (para aproximadamente 760 MB en RAM )

Como puede ver, se envían casi 3 GB de memoria adicional a los trabajadores. La operación de división necesita algo de memoria para completarse, pero aún mucho menos que la operación de transmisión y, en principio, puede sobrescribir el conjunto de datos inicial, por lo tanto, tiene un costo de RAM insignificante una vez completado. Finalmente, el parfor en los datos parfor solo usará una pequeña fracción de memoria, es decir, la cantidad que corresponde a los segmentos que se están utilizando.

Cortado en una celda

Los datos sin procesar se dividen en grupos y cada sección se almacena en una celda. Dado que una matriz de celdas es una matriz de referencias, básicamente dividimos los data contiguos en la memoria en bloques independientes.

Mientras que nuestros data muestra data veían así

data(1:5,:) ans = 620 -0.10696 586 -1.1771 625 2.2021 858 0.86064 78 1.7456

c rodajas parece

c(1:5) ans = [ 969x2 double] [ 970x2 double] [ 949x2 double] [ 986x2 double] [1013x2 double]

donde c{1} es

c{1}(1:5,:) ans = 1 0.58205 1 0.80183 1 -0.73783 1 0.79723 1 1.0414


Información general

Muchas funciones tienen múltiples subprocesos implícitos integrados , lo que hace que un bucle parfor no sea más eficiente, cuando se usan estas funciones, que un bucle for serie, ya que todos los núcleos ya se están utilizando. parfor realidad será un detrimento en este caso, ya que tiene la sobrecarga de asignación, mientras que es tan paralela como la función que está tratando de usar.

Cuando no se utiliza una de las funciones implícitamente multiproceso, parfor se recomienda básicamente en dos casos: muchas iteraciones en su ciclo (es decir, como 1e10 ), o si cada iteración lleva mucho tiempo (por ejemplo, eig(magic(1e4)) ) . En el segundo caso, es posible que desee considerar el uso de spmd (más lento que parfor en mi experiencia). La razón por la que parfor es más lento que un bucle for para rangos cortos o iteraciones rápidas es la sobrecarga necesaria para administrar a todos los trabajadores correctamente, en lugar de simplemente hacer el cálculo.

Consulte esta pregunta para obtener información sobre la división de datos entre trabajadores separados.

Benchmarking

Código

Considere el siguiente ejemplo para ver el comportamiento de for en oposición al de parfor . Primero abra el grupo paralelo si aún no lo ha hecho:

gcp; % Opens a parallel pool using your current settings

Luego ejecute un par de bucles grandes:

n = 1000; % Iteration number EigenValues = cell(n,1); % Prepare to store the data Time = zeros(n,1); for ii = 1:n tic EigenValues{ii,1} = eig(magic(1e3)); % Might want to lower the magic if it takes too long Time(ii,1) = toc; % Collect time after each iteration end figure; % Create a plot of results plot(1:n,t) title ''Time per iteration'' ylabel ''Time [s]'' xlabel ''Iteration number[-]'';

Luego haga lo mismo con parfor lugar de for . Notará que el tiempo promedio por iteración sube (0.27s a 0.39s para mi caso). Sin embargo, parfor cuenta que el parfor utilizó a todos los trabajadores disponibles, por lo tanto, el tiempo total ( sum(Time) ) debe dividirse por el número de núcleos en su computadora. Entonces, para mi caso, el tiempo total bajó de alrededor de 270 segundos a 49 segundos, ya que tengo un procesador octacore.

Entonces, aunque el tiempo para hacer cada iteración por separado aumenta usando parfor con respecto al uso de for , el tiempo total disminuye considerablemente.

Resultados

Esta imagen muestra los resultados de la prueba tal como la ejecuté en la PC de mi casa. eig(500) n=1000 y eig(500) ; mi computadora tiene un procesador I5-750 de 2.66 GHz con cuatro núcleos y ejecuta MATLAB R2012a. Como puede ver, el promedio de la prueba paralela oscila alrededor de 0.29s con mucha dispersión, mientras que el código de serie es bastante estable alrededor de 0.24s. El tiempo total, sin embargo, bajó de 234s a 72s, que es una velocidad de 3.25 veces. La razón por la que esto no es exactamente 4 es la sobrecarga de memoria, como se expresa en el tiempo extra que toma cada iteración. La sobrecarga de memoria se debe a que MATLAB tiene que verificar lo que está haciendo cada núcleo y asegurarse de que cada iteración de bucle se realice solo una vez y que los datos se coloquen en la ubicación de almacenamiento correcta.