Eu tenho o seguinte código Java com vários grandes arrays que nunca mudam de tamanho. Ele roda em 1100 ms no meu computador.
Implementei o mesmo código em C ++ e usei std::vector
.
O tempo de implementação do C ++ que executa exatamente o mesmo código é 8.800 ms em meu computador. O que eu fiz de errado, para que funcione tão lentamente?
Basicamente, o código faz o seguinte:
for (int i = 0; i < numberOfCells; ++i) {
h[i] = h[i] + 1;
floodedCells[i] = !floodedCells[i];
floodedCellsTimeInterval[i] = !floodedCellsTimeInterval[i];
qInflow[i] = qInflow[i] + 1;
}
Ele itera por meio de diferentes matrizes com um tamanho de cerca de 20.000.
Você pode encontrar ambas as implementações nos seguintes links:
- Java: https://ideone.com/R8KqjT
- C ++: https://ideone.com/Lu7RpE
(No ideone eu só pude executar o loop 400 vezes em vez de 2.000 vezes por causa da limitação de tempo. Mas mesmo aqui há uma diferença de três vezes)
std::vector<bool>
usa um bit por elemento para economizar espaço, o que leva a muitas mudanças de bits. Se você quer velocidade, deve ficar longe dela. Use em seustd::vector<int>
lugar.h[i] += 1;
ou (melhor ainda)++h[i]
seja mais legível do queh[i] = h[i] + 1;
, ficaria um pouco surpreso se ver qualquer diferença significativa na velocidade entre eles. Um compilador pode "descobrir" que ambos estão fazendo a mesma coisa e gerar o mesmo código de qualquer maneira (pelo menos na maioria dos casos comuns).Respostas:
Aqui está a versão C ++ com os dados por nó reunidos em uma estrutura e um único vetor dessa estrutura usado:
exemplo vivo
O tempo agora é 2x a velocidade da versão Java. (846 vs 1631).
As probabilidades são de que o JIT percebeu a queima do cache de dados de acesso em todo o lugar e transformou seu código em uma ordem logicamente semelhante, mas mais eficiente.
Também desativei a sincronização do stdio, pois ela só é necessária se você misturar
printf
/scanf
com C ++std::cout
estd::cin
. Acontece que você imprime apenas alguns valores, mas o comportamento padrão do C ++ para impressão é excessivamente paranóico e ineficiente.Se
nEdges
não for um valor constante real, os 3 valores da "matriz" terão que ser retirados dostruct
. Isso não deve causar um grande impacto no desempenho.Você pode conseguir outro aumento de desempenho classificando os valores
struct
diminuindo o tamanho, reduzindo assim o consumo de memória (e classificando o acesso também quando não importa). Mas não tenho certeza.Uma regra prática é que uma única falha de cache é 100 vezes mais cara do que uma instrução. Organizar seus dados para ter coerência de cache tem muito valor.
Se reorganizar os dados em um
struct
for inviável, você pode alterar sua iteração para ser sobre cada contêiner por vez.Como um aparte, observe que as versões Java e C ++ tinham algumas diferenças sutis. O que descobri foi que a versão Java tem 3 variáveis no loop "for each edge", enquanto a C ++ tinha apenas 2. Fiz a minha corresponder ao Java. Não sei se existem outros.
fonte
Sim, o cache na versão c ++ leva um martelar. Parece que o JIT está melhor equipado para lidar com isso.
Se você alterar o outer
for
in isUpdateNeeded () para snippets mais curtos. A diferença vai embora.O exemplo abaixo produz um aumento de 4x.
Isso mostra, em um grau razoável, que falhas de cache são o motivo da lentidão. Também é importante observar que as variáveis não são dependentes, portanto, uma solução encadeada é facilmente criada.
Ordem restaurada
De acordo com o comentário de Stefans, tentei agrupá-los em uma estrutura usando os tamanhos originais. Isso remove a pressão imediata do cache de maneira semelhante. O resultado é que a versão c ++ (CCFLAG -O3) é cerca de 15% mais rápida do que a versão java.
Varning nem baixo nem bonito.
Meu resultado difere ligeiramente de Jerry Coffins para os tamanhos originais. Para mim, as diferenças permanecem. Pode muito bem ser minha versão java, 1.7.0_75.
fonte
++
ajuda de alguma forma?x = x + 1
parece terrivelmente desajeitado em comparação com++x
.Como @Stefan adivinhou em um comentário sobre a resposta de @CapitãoGiraffe, você ganha bastante usando um vetor de estruturas em vez de uma estrutura de vetores. O código corrigido fica assim:
Compilado com o compilador de VC ++ 2015 CTP, usando
-EHsc -O2b2 -GL -Qpar
, obtenho resultados como:Compilar com g ++ produz um resultado um pouco mais lento:
No mesmo hardware, usando o compilador / JVM do Java 8u45, obtenho resultados como:
Isso é cerca de 35% mais lento do que a versão do VC ++ e cerca de 16% mais lento do que a versão do g ++.
Se aumentarmos o número de iterações para as 2.000 desejadas, a diferença cai para apenas 3%, sugerindo que parte da vantagem do C ++ neste caso é simplesmente um carregamento mais rápido (um problema perene com Java), não realmente na execução em si. Isso não me parece surpreendente neste caso - o cálculo que está sendo medido (no código postado) é tão trivial que duvido que a maioria dos compiladores possa fazer muito para otimizá-lo.
fonte
#pragma omp
e (talvez) um pouco de trabalho para garantir que cada iteração de loop seja independente. Isso exigiria um trabalho mínimo para obter um aumento de velocidade ~ Nx, onde N é o número de núcleos de processador disponíveis.Suspeito que se trate de alocação de memória.
Estou pensando que
Java
pega um grande bloco contíguo na inicialização do programa, ao passo queC++
pede ao sistema operacional bits e peças à medida que avança.Para colocar essa teoria em teste, fiz uma modificação na
C++
versão e, de repente, ela começou a funcionar um pouco mais rápido do que aJava
versão:Tempo de execução sem o vetor de pré-alocação:
Tempo de execução com o vetor de pré-alocação:
Tempo de execução para a
Java
versão:fonte
FloodIsolation
ainda podem ser alocados em outro lugar.