2010-09-29 5 views
6

Estoy experimentando con algunas construcciones de subprocesos múltiples, pero de alguna manera parece que el multithreading no es más rápido que un solo hilo. Lo reduje a una prueba muy simple con un ciclo anidado (1000x1000) en el que el sistema solo cuenta.
A continuación publiqué el código para el subprocesamiento único y el subprocesamiento múltiple y cómo se ejecutan.
El resultado es que el hilo único completa el ciclo en aproximadamente 110 ms, mientras que los dos hilos también toman aproximadamente 112 ms.
No creo que el problema sea la sobrecarga de multihilo. Si solo envío uno de los Runnables al ThreadPoolExecutor, se ejecutará en la mitad del tiempo del único hilo, lo cual tiene sentido. Pero agregar ese segundo Runnable lo hace 10 veces más lento. Ambos núcleos de 3.00Ghz funcionan al 100%.
Creo que puede ser específico de la PC, ya que la PC de otra persona mostró resultados de doble velocidad en el multihilo. Pero entonces, ¿qué puedo hacer al respecto? Tengo un Intel Pentium 4 3.00GHz (2 CPUs) y Java jre6. código

prueba:Multithreading no más rápido que un solo hilo (prueba simple de bucle)

// Single thread: 
long start = System.nanoTime(); // Start timer 
final int[] i = new int[1];  // This is to keep the test fair (see below) 
int i = 0; 
for(int x=0; x<10000; x++) 
{ 
    for(int y=0; y<10000; y++) 
    { 
     i++; // Just counting... 
    } 
} 
int i0[0] = i; 
long end = System.nanoTime(); // Stop timer 

Este código se ejecuta en aproximadamente 110 ms .

// Two threads: 

start = System.nanoTime(); // Start timer 

// Two of the same kind of variables to count with as in the single thread. 
final int[] i1 = new int [1]; 
final int[] i2 = new int [1]; 

// First partial task (0-5000) 
Thread t1 = new Thread() { 
    @Override 
    public void run() 
    { 
     int i = 0; 
     for(int x=0; x<5000; x++) 
      for(int y=0; y<10000; y++) 
       i++; 
     i1[0] = i; 
    } 
}; 

// Second partial task (5000-10000) 
Thread t2 = new Thread() { 
    @Override 
    public void run() 
    { 
     int i = 0; 
     for(int x=5000; x<10000; x++) 
      for(int y=0; y<10000; y++) 
       i++; 
     int i2[0] = i; 
    } 
}; 

// Start threads 
t1.start(); 
t2.start(); 

// Wait for completion 
try{ 
    t1.join(); 
    t2.join(); 
}catch(Exception e){ 
    e.printStackTrace(); 
} 

end = System.nanoTime(); // Stop timer 

Este código se ejecuta en aproximadamente 112 ms .

Editar: Cambié los Ejecutables a Hilos y eliminé el ExecutorService (por la simplicidad del problema).

Editar: intentó algunas sugerencias

+0

¿Has probado las sugerencias? –

+0

Ah, Pentium4 - ver mi respuesta actualizada :) – snemarch

Respuesta

11

Definitivamente no quiere seguir sondeando Thread.isAlive() - esto quema muchos ciclos de CPU sin una buena razón. Use Thread.join() en su lugar.

Además, probablemente no sea una buena idea que los subprocesos incrementen las matrices de resultados directamente, las líneas de caché y todo. Actualice las variables locales y realice una sola tienda cuando finalicen los cálculos.

EDIT:

totalmente por alto que usted está utilizando un Pentium 4. Por lo que yo sé, no hay versiones múltiples núcleos de la P4 - para dar la ilusión de múltiples núcleos, tiene Hyper-Threading: dos los núcleos lógicos comparten las unidades de ejecución de un núcleo físico. Si sus subprocesos dependen de las mismas unidades de ejecución, su rendimiento será el mismo (o peor) rendimiento de subproceso único. Necesitaría, por ejemplo, cálculos de punto flotante en un subproceso y cálculos enteros en otro para obtener mejoras de rendimiento.

La implementación de P4 HT ha sido criticada mucho, las implementaciones más recientes (core2 recientes) deberían ser mejores.

+0

+1 - El primer párrafo es probablemente donde está la mayor parte de la diferencia. –

+0

+1 - En realidad, ambas sugerencias aceleran el proceso significativamente, gracias. Pero hay algo extraño: usar Thread.isAlive() en combinación con el incremento de matrices directamente, es más rápido (800 ms) que usar Thread.join() (2200 ms), pero usar isAlive() en combinación con su segunda sugerencia, es más lento (190 ms) que unirse() (114 ms). De todos modos, usar ambas sugerencias acelera el sistema de 2200 ms a 114: D. Sin embargo, su segunda sugerencia también acelera el hilo individual a aproximadamente 110 ms, por lo que ahora no hay diferencia todavía. – RemiX

+0

Una diferencia de menos de 10 ms realmente no dice nada cuando se ejecuta en un sistema operativo multitarea. Necesitará aumentar las iteraciones para medir la diferencia de velocidad de manera más confiable :) – snemarch

1

Eso no se hace nada con i, por lo que su loop está probablemente sólo optimizado de distancia.

+0

En realidad, imprimí el valor de i en la parte inferior (pero no se muestra en el código). – RemiX

+0

Los tiempos son consistentes con su optimización, pero no optimizados. Me gustaría ver la prueba repetida (sin reiniciar el proceso). Una cuestión que pueden tener los hilos en este contexto es que HotSpot se ejecuta en un hilo diferente, y el hilo adicional puede terminar ejecutando el código no optimizado durante algún tiempo. –

+0

Otro subproceso que hace exactamente lo mismo que t2 (solo entonces 10000x10000) se completa en 107 ms (más rápido que t1 y t2 juntos), ¿o no es eso lo que quiso decir? – RemiX

2

No me sorprende en absoluto la diferencia. Está utilizando el marco de simultaneidad de Java para crear sus hilos (aunque no veo ninguna garantía de que se creen incluso dos hilos ya que el primer trabajo puede completarse antes de que el segundo empiece.

Probablemente haya todo tipo de bloqueo y sincronización en marcha detrás de las escenas que en realidad no necesita para su prueba sencilla. en pocas palabras, hago que el problema es la sobrecarga de multihilo.

+0

También lo probé con solo dos subprocesos y el uso de thread1.start(), que muestra el mismo resultado. Además, un ejecutable en el ExecutorService funciona muy rápido y, por último, otra máquina funciona bien con este código. – RemiX

4

intente aumentar el tamaño de la matriz algo. no, en serio.

Los objetos pequeños asignados secuencialmente en el mismo subproceso tenderán a asignarse inicialmente de forma secuencial. hábilmente en la misma línea de caché. Si tiene dos núcleos que acceden a la misma línea de caché (y luego micro-benhcmark básicamente está haciendo una secuencia de escrituras en la misma dirección) entonces tendrán que pelear por el acceso.

Hay una clase en java.util.concurrent que tiene un montón de campos no utilizados long. Su propósito es separar los objetos que pueden ser utilizados con frecuencia por diferentes subprocesos en diferentes líneas de caché.

+0

Estoy usando una matriz diferente para cada subproceso, por lo que no creo que tengan que luchar por el acceso ... ¿o no entendí bien? – RemiX

+4

@RemiX: ambos están asignados en el montón, i2 se asigna justo después de i1. Hay una gran probabilidad de que terminen en la misma línea de caché. – snemarch

+0

+1 - 2200 ms a 280 ms simplemente aumentando el tamaño de las matrices a 10. Desafortunadamente, al usar sus otras sugerencias, el efecto ya no es tan bueno. Bueno para recordar, sin embargo. – RemiX

1

¿Ha comprobado la cantidad de núcleos disponibles en su PC con Runtime.getRuntime(). AvailableProcessors()?

+0

Acabo de hacerlo, y dice 2 procesadores. Además, puedo verlos trabajando en el Administrador de tareas. – RemiX

0

Su código simplemente incrementa una variable - esta es una operación muy rápida de todos modos. No está ganando mucho con el uso de múltiples hilos aquí. Las ganancias de rendimiento son más pronunciadas cuando thread-1 tiene que esperar una respuesta externa o hacer algunos cálculos más complejos, mientras tanto, el hilo principal o algún otro hilo puede continuar procesándose y no se puede esperar. Puede parecer que tiene más ganancias si cuenta más alto o si usa más hilos (probablemente un número seguro sea la cantidad de CPU/núcleos en su máquina).

Cuestiones relacionadas