Cómo hacer progtwigción paralela en Python.

Para C ++, podemos usar OpenMP para hacer progtwigción paralela; sin embargo, OpenMP no funcionará para Python. ¿Qué debo hacer si quiero poner en paralelo algunas partes de mi progtwig de python?

La estructura del código puede ser considerada como:

solve1(A) solve2(B) 

Donde solve1 y solve2 son dos funciones independientes. ¿Cómo ejecutar este tipo de código en paralelo en lugar de en secuencia para reducir el tiempo de ejecución? Espero que alguien pueda ayudarme. Muchas gracias de antemano. El código es:

 def solve(Q, G, n): i = 0 tol = 10 ** -4 while i < 1000: inneropt, partition, x = setinner(Q, G, n) outeropt = setouter(Q, G, n) if (outeropt - inneropt) / (1 + abs(outeropt) + abs(inneropt)) < tol: break node1 = partition[0] node2 = partition[1] G = updateGraph(G, node1, node2) if i == 999: print "Maximum iteration reaches" print inneropt 

Donde setinner y setouter son dos funciones independientes. Ahí es donde quiero paralelo …

Puede utilizar el módulo de multiprocesamiento . Para este caso podría usar un grupo de procesamiento:

 from multiprocessing import Pool pool = Pool() result1 = pool.apply_async(solve1, [A]) # evaluate "solve1(A)" asynchronously result2 = pool.apply_async(solve2, [B]) # evaluate "solve2(B)" asynchronously answer1 = result1.get(timeout=10) answer2 = result2.get(timeout=10) 

Esto generará procesos que pueden hacer trabajo genérico por ti. Como no pasamos los processes , generará un proceso para cada núcleo de CPU en su máquina. Cada núcleo de CPU puede ejecutar un proceso simultáneamente.

Si desea asignar una lista a una sola función, haría esto:

 args = [A, B] results = pool.map(solve1, args) 

No use hilos porque GIL bloquea cualquier operación en objetos de Python.

Esto se puede hacer muy elegantemente con Ray .

Para paralelizar su ejemplo, tendría que definir sus funciones con el decorador @ray.remote y luego invocarlas con .remote .

 import ray ray.init() # Define the functions. @ray.remote def solve1(a): return 1 @ray.remote def solve2(b): return 2 # Start two tasks in the background. x_id = solve1.remote(0) y_id = solve2.remote(1) # Block until the tasks are done and get the results. x, y = ray.get([x_id, y_id]) 

Hay varias ventajas de esto sobre el módulo de multiprocesamiento .

  1. El mismo código se ejecutará en una máquina multinúcleo, así como en un grupo de máquinas.
  2. Los procesos comparten datos de manera eficiente a través de la memoria compartida y la serialización de copia cero .
  3. Los mensajes de error se propagan muy bien.
  4. Estas llamadas de función se pueden componer juntas, por ejemplo,

     @ray.remote def f(x): return x + 1 x_id = f.remote(1) y_id = f.remote(x_id) z_id = f.remote(y_id) ray.get(z_id) # returns 4 
  5. Además de invocar funciones de forma remota, las clases se pueden crear instancias remotas como actores .

Tenga en cuenta que Ray es un marco que he estado ayudando a desarrollar.

CPython utiliza el locking global de intérpretes, lo que hace que la progtwigción en paralelo sea un poco más interesante que C ++

Este tema tiene varios ejemplos y descripciones útiles del desafío:

¿La solución alternativa de locking de intérprete global (GIL) de Python en sistemas de múltiples núcleos con el conjunto de tareas en Linux?

La solución, como han dicho otros, es utilizar múltiples procesos. Qué marco es más apropiado, sin embargo, depende de muchos factores. Además de los ya mencionados, también hay charm4py y mpi4py (soy el desarrollador de charm4py).

Existe una forma más eficiente de implementar el ejemplo anterior que utilizando la abstracción del grupo de trabajo. El bucle principal envía los mismos parámetros (incluido el gráfico completo G ) una y otra vez a los trabajadores en cada una de las 1000 iteraciones. Dado que al menos un trabajador residirá en un proceso diferente, esto implica copiar y enviar los argumentos a los otros procesos. Esto podría ser muy costoso dependiendo del tamaño de los objetos. En su lugar, tiene sentido que los trabajadores almacenen el estado y simplemente envíen la información actualizada.

Por ejemplo, en charm4py esto se puede hacer así:

 class Worker(Chare): def __init__(self, Q, G, n): self.G = G ... def setinner(self, node1, node2): self.updateGraph(node1, node2) ... def solve(Q, G, n): # create 2 workers, each on a different process, passing the initial state worker_a = Chare(Worker, onPE=0, args=[Q, G, n]) worker_b = Chare(Worker, onPE=1, args=[Q, G, n]) while i < 1000: result_a = worker_a.setinner(node1, node2, ret=True) # execute setinner on worker A result_b = worker_b.setouter(node1, node2, ret=True) # execute setouter on worker B inneropt, partition, x = result_a.get() # wait for result from worker A outeropt = result_b.get() # wait for result from worker B ... 

Tenga en cuenta que para este ejemplo solo necesitamos un trabajador. El bucle principal podría ejecutar una de las funciones y hacer que el trabajador ejecute la otra. Pero mi código ayuda a ilustrar un par de cosas:

  1. El trabajador A se ejecuta en el proceso 0 (igual que el bucle principal). Mientras result_a.get() está bloqueado esperando el resultado, el trabajador A realiza el cálculo en el mismo proceso.
  2. Los argumentos se pasan automáticamente por referencia al trabajador A, ya que está en el mismo proceso (no hay copia involucrada).