¿Por qué los hilos aumentan el tiempo de procesamiento?

Estaba trabajando en la multitarea de una simulación básica de DLA 2-D. La Agregación Limitada por Difusión (DLA) es cuando hay partículas que realizan un recorrido aleatorio y un agregado cuando tocan el agregado actual.

En la simulación, tengo 10.000 partículas caminando en una dirección aleatoria en cada paso. Yo uso un grupo de trabajadores y una cola para alimentarlos. Los alimento con una lista de partículas y el trabajador realiza el método .updatePositionAndggregate() en cada partícula.

Si tengo un trabajador, lo alimento con una lista de 10,000 partículas, si tengo dos trabajadores, los alimento con una lista de 5,000 partículas cada uno, si tengo 3 trabajadores, los alimento con una lista de 3,333 partículas cada uno, etc. y etc.

Te muestro un código para el trabajador ahora.

 class Worker(Thread): """ The worker class is here to process a list of particles and try to aggregate them. """ def __init__(self, name, particles): """ Initialize the worker and its events. """ Thread.__init__(self, name = name) self.daemon = True self.particles = particles self.start() def run(self): """ The worker is started just after its creation and wait to be feed with a list of particles in order to process them. """ while True: particles = self.particles.get() # print self.name + ': wake up with ' + str(len(self.particles)) + ' particles' + '\n' # Processing the particles that has been feed. for particle in particles: particle.updatePositionAndAggregate() self.particles.task_done() # print self.name + ': is done' + '\n' 

Y en el hilo principal:

 # Create the workers. workerQueue = Queue(num_threads) for i in range(0, num_threads): Worker("worker_" + str(i), workerQueue) # We run the simulation until all the particle has been created while some_condition(): # Feed all the workers. startWorker = datetime.datetime.now() for i in range(0, num_threads): j = i * len(particles) / num_threads k = (i + 1) * len(particles) / num_threads # Feeding the worker thread. # print "main: feeding " + worker.name + ' ' + str(len(worker.particles)) + ' particles\n' workerQueue.put(particles[j:k]) # Wait for all the workers workerQueue.join() workerDurations.append((datetime.datetime.now() - startWorker).total_seconds()) print sum(workerDurations) / len(workerDurations) 

Entonces, imprimo el tiempo promedio en espera de que los trabajadores terminen sus tareas. Hice algunos experimentos con diferentes números de hilos.

 | num threads | average workers duration (s.) | |-------------|-------------------------------| | 1 | 0.147835636364 | | 2 | 0.228585818182 | | 3 | 0.258296454545 | | 10 | 0.294294636364 | 

Realmente me pregunto por qué agregar trabajadores aumenta el tiempo de procesamiento, pensé que al menos tener 2 trabajadores disminuiría el tiempo de procesamiento, pero aumenta dramáticamente desde .14s. a 0.23s. ¿Me puedes explicar por qué?

EDIT: Entonces, la explicación es la implementación de subprocesos de Python, ¿hay alguna manera de que pueda tener multitarea real?

Esto sucede porque los subprocesos no se ejecutan al mismo tiempo, ya que Python puede ejecutar solo un subproceso a la vez debido a GIL (locking de intérprete global).

Cuando se genera un nuevo hilo, todo se congela excepto este hilo. Cuando se detiene se ejecuta el otro. Los hilos de desove necesitan mucho tiempo.

Hablando con amabilidad, el código no importa en absoluto, ya que cualquier código que use 100 subprocesos es más lento que el código que usa 10 subprocesos en Python (si hay más subprocesos significa más eficiencia y más velocidad, lo que no siempre es cierto).

Aquí hay una cita exacta de los documentos de Python :

Detalle de implementación de CPython :

En CPython, debido al locking de intérprete global, solo un hilo puede ejecutar el código de Python a la vez (aunque ciertas bibliotecas orientadas al rendimiento pueden superar esta limitación). Si desea que su aplicación haga un mejor uso de los recursos computacionales de las máquinas de múltiples núcleos, se recomienda utilizar multiprocessing o concurrent.futures.ProcessPoolExecutor . Sin embargo, el subproceso sigue siendo un modelo adecuado si desea ejecutar varias tareas enlazadas a E / S simultáneamente.

Wikipedia sobre GIL

StackOverflow sobre GIL

Los subprocesos en python (al menos en 2.7) NO se ejecutan simultáneamente debido a GIL: https://wiki.python.org/moin/GlobalInterpreterLock : se ejecutan en un solo proceso y comparten la CPU, por lo tanto, no puede usar subprocesos para acelerar su computación hacia arriba.

Si desea usar computación paralela para acelerar su cálculo (al menos en python2.7), use procesos – multiprocessing paquetes.

Esto se debe al locking global de intérprete de Python. Desafortunadamente, con GIL en Python, los subprocesos bloquearán la E / S y, como tal, nunca excederán el uso de 1 núcleo de CPU. Eche un vistazo aquí para comenzar a entender GIL: https://wiki.python.org/moin/GlobalInterpreterLock

Verifique sus procesos en ejecución (Administrador de tareas en Windows, por ejemplo) y notará que su aplicación Python solo utiliza un núcleo.

Sugeriría mirar el multiprocesamiento en Python, que no está obstaculizado por la GIL: https://docs.python.org/2/library/multiprocessing.html

Lleva tiempo crear el otro hilo y comenzar a procesarlo. Como no tenemos el control del progtwigdor, estoy dispuesto a apostar que estos dos subprocesos se progtwign en el mismo núcleo (ya que el trabajo es muy pequeño), por lo tanto, está agregando el tiempo necesario para crear el subproceso y no se realiza parallel processing