Pregunta

Estoy escribiendo un multi threading aplicación java que se ejecuta en el procesador Nehalem.Sin embargo tengo un problema que a partir de 4 hilos yo casi no veo la aceleración en mi aplicación.

He hecho alguna prueba sencilla.He creado un hilo que solo asigna una gran variedad y haciendo que el acceso aleatorio entradas de la matriz.Así que cuando ejecuto el número de subprocesos que el tiempo de funcionamiento no se debe cambiar (suponiendo que yo no soy superior a número de núcleos de CPU).Pero lo que he observado es que la ejecución de 1 o 2 hilos toma casi el mismo tiempo, pero la ejecución de 4 o 8 hilos es significativamente más lento.Así que antes de tratar para resolver y algoritmos de sincronización problema en mi aplicación que quiero averiguar qué es máxima posible de paralelización que puedo lograr.

He usado -XX:+UseNUMA JVM opción, por lo que las matrices debe ser asignada en la memoria cerca de los correspondientes hilos.

P. S.Si los hilos estaban haciendo un simple cálculo matemático no había tiempo de caída para 4 y hasta 8 hilos, así que llegué a la conclusión de que cuando los hilos están para acceder a la memoria que tengo algunos problemas.

Cualquier ayuda o ideas son apreciados, gracias.


EDITAR

Gracias a todos por las respuestas.Veo que no me han explicado a mí mismo lo suficientemente bueno.

Antes de tratar de eliminar los problemas de sincronización en mi aplicación me hizo una prueba simple que comprobar mejor posible paralelización que podría alcanzarse.El código es el siguiente:

public class TestMultiThreadingArrayAccess {
    private final static int arrSize = 40000000;

    private class SimpleLoop extends Thread {
        public void run() {
            int array[] = new int[arrSize];
            for (long i = 0; i < arrSize * 10; i++) {
                array[(int) ((i * i) % arrSize)]++; // randomize a bit the access to the array
            }
            long sum = 0;
            for (int i = 0; i < arrSize; i++)
                sum += array[i];
        }
    }

    public static void main(String[] args) {
        TestMultiThreadingArrayAccess test = new TestMultiThreadingArrayAccess();
        for (int threadsNumber : new int[] { 1, 2, 4, 8 }) {
            Statistics timer = new Statistics("Executing " + threadsNumber+ " threads"); // Statistics is a simple helper class that measures the times
            timer.start();
            test.doTest(threadsNumber);
            timer.stop();
            System.out.println(timer.toString());
        }
    }

    public void doTest(int threadsNumber) {
        Thread threads[] = new Thread[threadsNumber];
        for (int i = 0; i < threads.length; i++) {
            threads[i] = new SimpleLoop();
            threads[i].start();
        }

        for (int i = 0; i < threads.length; i++)
            try {
                threads[i].join();
            } catch (InterruptedException e) {
            };
    }
}

Así que como puedes ver no hay ninguna sincronización del todo en este minitest y también la asignación de la matriz está en el interior de la rosca, así que debe ser colocado en la parte de la memoria que se puede acceder rápidamente.También no hay ninguna memoria contiendas en este código.Todavía para 4 hilos hay una caída de 30% en el tiempo de ejecución, y 8 hilos se ejecuta dos veces más lento.Como usted desde el código que sólo tiene que esperar hasta que todos los hilos de terminar el suyo trabajo, y desde el suyo trabajo es independiente del número de hilos no deben afectar el tiempo total de la ejecución de la toma.

En la máquina instalada 2 quad-core hyperthreaded procesadores Nehalem (por un total de 16 CPUs), así que con 8 hilos cada uno puede coger la CPU exclusivamente.

Cuando traté de correr esta prueba con los más pequeños de la matriz (20 entradas) el descenso de los tiempos de ejecución de 4 hilos fue de 7% y 8 hilos - 14%, lo cual es satisfactorio.Pero cuando se trata de operar al azar acceder a large array (40M entradas) los tiempos de ejecución aumentar de manera considerable, así que yo creo que hay un problema que grandes trozos de la memoria (porque no caben en la memoria caché?) se accede en un no-de una manera eficiente.

¿Hay alguna idea de cómo solucionar este problema?

Espero que esto aclara la cuestión de una mejor manera, gracias de nuevo.

¿Fue útil?

Solución

El cuello de botella en la prueba es la CPU a la banda de memoria.Incluso cuando está disponible la memoria local, se va a compartir por algún número de hilos.(La memoria es local en un nodo, no a un núcleo específico). Una vez que la CPU puede exceder fácilmente el ancho de banda disponible para un simple bucle como su prueba anterior, y así aumentar los hilos en dicha prueba no mejorará el rendimiento, y puede empeorar el rendimientoDebido a la coherencia en caché empeorada.

Solo una prueba de cordura, ¿también estás usando el colector paralelo?-XX:+UseParallelGC.USENUMA solo tiene efecto entonces.

Otros consejos

Sin saber qué estás haciendo exactamente y cuál es el problema que intenta resolver. Parece que tiene una sincronización pesada en torno a su código, ya que podría ser la razón principal por no ser lo suficientemente escalable. Sobre la causa de la sincronización para reducir la velocidad de cualquier velocidad, una vez que haga su solicitud casi en serie. Así que mi sugerencia para usted es inspeccionar su implementación y tratar de resolver esto.

agregar.

después de haber agregado su implementación de lo que está haciendo. La rebaja de rendimiento podría explicarse por un gran y masivo acceso a la memoria. Una vez que ejecute todos los hilo y necesitan acceder a un controlador de memoria para no los datos en caché, ya que se ejecutan en diferentes CPU, el controlador de memoria evita que las CPU lo hagan simultáneamente, lo que significa que existe una sincronización en el nivel de hardware en cada error de caché. En su caso es casi igual que si estuviera ejecutando 10 programas independientes diferentes. Supongo que si lanzará 10 (puede reemplazar 10 por cualquier número grande) copia su navegador web, por ejemplo, verá el mismo efecto, pero esto no significa que la implementación del navegador sea ineficaz, simplemente creará una gran carga en memoria de computadora.

Como señala Artem, es posible que tenga una sincronización innecesaria. Pero empezaría por establecer los hechos. ¿Su aplicación está realmente corriendo más lenta al describir?

Aquí hay un artículo perspicaz sobre el tema: http://codeidol.com/java/java-concurrency/testing-concurrent-programs/avoiding-performance-testing-pitfalls/

En realidad, es bastante difícil escribir un micro compartimento útiles, especialmente cuando está tratando con un código concurrente. Por ejemplo, puede tener "eliminación de código muerto" en el que el compilador optimiza el código que cree que se está ejecutando. También es difícil adivinar cuando se ejecuta la recolección de basura. La optimización de tiempo de ejecución de Hotspot hace también la medición más difícil. En caso de hilos, debe tener en cuenta el tiempo que se usa para crearlos. Por lo tanto, es posible que tenga que usar un "cíclicbarrier", etc. para tener una medición precisa. Cosas así ...

Dicho esto, le resulta difícil que tenga problemas para acceder a la memoria si todo lo que está haciendo es leer. Podríamos ser capaces de ayudarlo mejor si puede publicar el código ...

Hay dos problemas posibles obvios que resortan para la mente.

  • usando más hilo asigna más matrices que estallan el caché.Los accesos a la memoria principal o los niveles más bajos de caché son mucho más lentos.
  • Si está utilizando la misma fuente de instancia del generador de números aleatorios, entonces los hilos lucharán por acceder a ella.Puede que no sea la sincronización completa, sino también las barreras de memoria con un algoritmo sin cerradura.Generalmente algoritmos sin bloqueo, aunque generalmente rápido, obtienen mucho más lento en alta contención.

Aparte de los problemas de concurrencia La causa más probable de su lento es la contención de la memoria caché de memoria.

Si todos los hilos están accediendo a la misma pieza de almacenamiento, las posibilidades son las de la memoria caché de la memoria de otros procesadores cuando desee acceder a ella.

Si el almacenamiento es "solo lectura", podría dar a cada subproceso su propia copia, lo que permitiría que el JVM & Processor optimice los Accuestes de memoria.

He modificado la prueba con el asesoramiento del artículo que he publicado.En mi 2 núcleo de la máquina (que es todo lo que tengo ahora mismo) resultado parece razonable (nota que me encontré con 2 pruebas para cada número de hilo):

Tal vez usted puede probar esto?(Por favor, tenga en cuenta que he tenido que modificar su prueba un poco (ver el comentario) porque era muy largo para que se ejecute en mi pobre hardware)

También tenga en cuenta que puedo ejecutar esta prueba mediante la -server opción.

Test with threadNum 1 took 2095717473 ns
Test with threadNum 1 took 2121744523 ns
Test with threadNum 2 took 2489853040 ns
Test with threadNum 2 took 2465152974 ns
Test with threadNum 4 took 5044335803 ns
Test with threadNum 4 took 5041235688 ns
Test with threadNum 8 took 10279012556 ns
Test with threadNum 8 took 10347970483 ns

código:

import java.util.concurrent.*;

public class Test{
    private final static int arrSize = 20000000;

    public static void main(String[] args) throws Exception {
        int[] nums = {1,1,2,2,4,4,8,8};//allow hotspot optimization
        for (int threadNum : nums) {
            final CyclicBarrier gate = new CyclicBarrier(threadNum+1);
            final CountDownLatch latch = new CountDownLatch(threadNum);
            ExecutorService exec = Executors.newFixedThreadPool(threadNum);
            for(int i=0; i<threadNum; i++){
                Runnable test = 
                  new Runnable(){
                     public void run() {
                         try{
                             gate.await();
                         }catch(Exception e){
                             throw new RuntimeException(e);
                         }
                         int array[] = new int[arrSize];
                         //arrSize * 10 took very long to run so made it
                         // just arrSize.
                         for (long i = 0; i < arrSize; i++) {
                             array[(int) ((i * i) % arrSize)]++;
                         }//for
                         long sum = 0;
                         for (int i = 0; i < arrSize; i++){
                              sum += array[i]; 
                         }
                         if(new Object().hashCode()==sum){
                              System.out.println("oh");
                         }//if
                         latch.countDown();
                      }//run
                   };//test
                exec.execute(test);
             }//for
             gate.await();
             long start = System.nanoTime();
             latch.await();
             long finish = System.nanoTime();
             System.out.println("Test with threadNum " +
                 threadNum +" took " + (finish-start) + " ns ");
             exec.shutdown();
             exec.awaitTermination(Long.MAX_VALUE,TimeUnit.SECONDS);           
        }//for
    }//main

}//Test
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top