Pergunta

Eu estou escrevendo um multi-threading aplicação java que é executado no processador Nehalem.No entanto, tenho um problema que, a partir do 4 threads eu quase não ver o speedup em meu aplicativo.

Eu fiz alguns testes simples.Eu criei uma thread que apenas atribui uma grande matriz e tornando o acesso aleatório de entradas na matriz.Então, quando eu executar número de threads que o tempo de execução não deve alterar (supondo que eu não estou excedendo o número de núcleos disponíveis).Mas o que eu observei é que a execução de 1 ou 2 threads leva quase o mesmo tempo, mas executar o 4 ou 8 threads é significativamente mais lenta.Então, antes de tentar para resolver algoritmos e sincronização problema na minha aplicação, eu quero descobrir o que é o máximo possível de paralelização que eu possa atingir.

Eu usei -XX:+UseNUMA JVM opção, de modo que as matrizes devem ser alocados na memória de perto o correspondente threads.

P. S.Se os fios estavam fazendo um simples cálculo matemático não houve tempo de queda para 4 e 8 threads, então eu conclui que quando as threads estão acessando a memória que eu tenho alguns problemas.

Qualquer ajuda ou ideias são apreciados, obrigado.


EDITAR

Obrigado a todos pelas respostas.Eu vejo que eu ainda não expliquei-me bom o suficiente.

Antes de tentar eliminar os problemas de sincronização na minha aplicação eu fiz um teste simples que verifique o melhor possível de paralelização que poderia ser alcançado.O código é o seguinte:

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) {
            };
    }
}

Então, como você pode ver, não há sincronização em todo este minitest e também a alocação da matriz está dentro do thread, então ele deve ser colocado no bloco de memória que pode ser acessado rapidamente.Também não há memória contendas neste código.Ainda para 4 threads há uma queda de 30% no tempo de execução, e 8 threads é executado duas vezes mais lento.Como você a partir do código-eu só aguardar até que todas as threads acabamento deles trabalho, e a partir deles o trabalho é independente do número de linhas não devem afetar o tempo total de execução leva.

A máquina instalada 2 quad-core hyperthreaded processadores Nehalem (totalmente de 16 CPUs), então com 8 threads cada um pode pegá-lo de CPU em modo exclusivo.

Quando eu tentei executar este teste com a menor matriz (20K entradas) a redução do tempo de execução de 4 segmentos foi de 7% e 8 threads - 14%, o que é satisfatório.Mas quando eu tento operar aleatório acessado em grande matriz (40M entradas) e tempos de execução aumentar drasticamente, então eu acho que não há problema que grandes blocos de memória (porque eles não cabem na memória cache?) são acessados em um não-de forma eficiente.

Há alguma idéia de como consertar isso?

Espero que isso esclarece a questão de uma forma melhor, obrigado novamente.

Foi útil?

Solução

O gargalo do teste é a cpu para a memória largura de banda.Mesmo quando a memória estiver disponível, ele vai ser compartilhado por um número de threads.(A memória é o local para um nó, não para um núcleo específico.) Uma vez que a CPU pode facilmente exceder a largura de banda disponível para um loop simples como o teste acima, e assim aumentando a threads no tal teste não irá melhorar o desempenho, e que pode piorar o desempenho piorou a coerência do cache.

Apenas um teste de sanidade, você também utilizando o coletor paralelo? -XX:+UseParallelGC.UseNUMA só tem efeito depois.

Outras dicas

Sem saber o que exatamente você está fazendo e qual é o problema que pretende resolver.Parece que você tem pesado a sincronização em torno de seu código, pois pode ser a principal razão para não ser escalável o suficiente.Sobre a sincronização de causar a abrandar speedup, depois de tornar seu aplicativo quase de série.Assim, minha sugestão para você é para inspecionar sua implementação e tentando descobrir isso.

ADICIONE.

Depois de adicionar a sua implementação do que você está fazendo.O rebaixamento da classificação de desempenho pode ser explicado pelo grande e maciça de acesso de memória.Uma vez que você executar tudo o que você thread e que eles precisam para acessar o controlador de memória para não cache de dados, desde que executados em diferentes CPU, controlador de memória impede CPU de fazê-lo simultaneamente, ou seja, há uma sincronização em nível de hardware em cada um cache miss.No caso é quase igual como se você estivesse executando 10 diferentes programas independentes.Eu acho que se você vai iniciar 10 (você pode substituir 10 por qualquer grande número de cópias de seu navegador da web, por exemplo, você vai ver o mesmo efeito, mas isso não significa que o navegador execução é ineficaz, você acabou de criar um grande peso na memória do computador.

Como Artem notas, é possível que você tenha desnecessária a sincronização.Mas eu gostaria de começar por estabelecer os fatos.Seu aplicativo é REALMENTE rodando mais lento, como você descreveria?

Aqui é um valioso artigo sobre o assunto: http://codeidol.com/java/java-concurrency/Testing-Concurrent-Programs/Avoiding-Performance-Testing-Pitfalls/

É realmente muito difícil escrever útil micro benchmarks, especialmente quando você está lidando com códigos simultâneos.Por exemplo, você pode ter "eliminação de código Morto", em que o compilador otimiza o código de distância você acha que está sendo executado.Também é difícil adivinhar quando a coleta de lixo é executado.Hotspot otimização de tempo de execução faz também a medição mais difícil.Em caso de threads, você precisa levar em conta o tempo que é usado para criá-los.Assim, você pode precisar usar um CyclicBarrier` etc.para ter uma medição precisa.Coisas assim..

Tendo dito isso, acho difícil que você vai ter problemas no acesso a memória se tudo o que você está fazendo está lendo.Nós podemos ser capazes de ajudá-lo melhor se você pode postar o código...

Há duas óbvias possíveis problemas que vêm à mente.

  • Usando mais thread aloca mais matrizes de burst cache.Acessos à memória principal ou baixos níveis de cache são muito mais lentos.
  • Se você estiver usando a mesma fonte de instância do gerador de número aleatório e, em seguida, threads serão de luta por acesso a ele.Ele pode não estar completa sincronização, mas em vez disso, barreiras de memória com um lock-free algoritmo.Geralmente lock-free algoritmos, embora geralmente rápido, fica muito lento em alta contenção.

Além de problemas de simultaneidade a causa mais provável de sua slowup é a memória cache de contenção.

Se todos os threads estão acessando o mesmo pedaço de armazenamento, as chances são de sua em outros processadores de cache de memória quando você desejar acessá-lo.

Se o armazenamento é "somente leitura", você poderia dar a cada segmento de sua própria cópia, o que permitiria a JVM e processador para optimizar a memória acccesses.

Eu modifiquei o teste com a assessoria do artigo que eu postei.No meu core 2 máquina (que é tudo que eu tenho direito agora) resultado parece razoável (note que eu corri 2 testes para cada thread número):

Talvez você pode tentar este?(Por favor note que eu tinha para modificar seu teste ligeiramente (ver comentário) porque levou muito tempo para executar no meu pobre hardware)

Observe também que eu executar esse teste usando o -server opção.

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 em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top