Executando verificação de segurança...
1

Indo direto ao ponto: o motivo do jit ser mais rápido é por que ele esta alocando memoria de forma muito mais eficiente, do que fazer um malloc/free por elemento. Que são operacões extramamente lentas!!!

Se estiver curioso em entender o que esta acontecendo, execute ambos os executaveis com o dtrace por exemplo ou com outra instrumentação para ver as syscalls sendo feitas. Quase sempre isso é o fator determinante!

Carregando publicação patrocinada...
2

Boa dica! Uma maneira de provar/desprovar isso seria pre-allocar um monte de Entry objects e rodar o benchmark dos puts com os objetos já pre-allocados pelo C++. Concorda?

Agora isso explicaria a vantagem do put, mas não explica a vantagem do get e do remove. Concorda?

E o mais importante: não tem jeito de resolver essa ineficiência de C++ na alocação de memória no heap? A HotSpot JVM vai sempre levar vantagem nisso? E aquela história que o C++ te dá controle total de tudo, etc e tals?

Estou perguntando na boa. Quero entender as vantagens e desvantagens de cada linguagem (C++ e Java) e cada approach (AOT e JIT).

1

Boa dica! Uma maneira de provar/desprovar isso seria pre-allocar um monte de Entry objects e rodar o benchmark dos puts com os objetos já pre-allocados pelo C++. Concorda?

Teste e compartilhe os resultados. Acredito que pre-alocar todos os objetos em memória estática resultaria em uma melhora de desempenho de várias ordens de grandeza.

Agora isso explicaria a vantagem do put, mas não explica a vantagem do get e do remove. Concorda?

A explicação para a vantagem do JIT em todas as operações reside na redução da fragmentação de memória, que leva ao uso eficiente do cache.

E aquela história que o C++ te dá controle total de tudo, etc e tals?

Exatamente. A otimização da alocação de memória é uma responsabilidade do programador. Isso envolve técnicas como alocação em blocos, uso de pools de memória e até mesmo a implementação de alocadores personalizados. O compilador, neste caso, não vai oferecer otimizações significativas.

1

Exatamente. A otimização da alocação de memória é uma responsabilidade do programador. Isso envolve técnicas como alocação em blocos, uso de pools de memória e até mesmo a implementação de alocadores personalizados. O compilador, neste caso, não vai oferecer otimizações significativas.

Entendi. Então possível é, só exige uma eforço e uma complexidade muito maior para conseguir algo que a HotSpot JVM/JIT te dá de mão beijada.

Prealocar tudo no pool (note no código que os Entries ficam numa linked-list que é um pool) antes de começar a executar é inconveniente. Até porque muitas vezes vc não vai saber a quantidade média/máxima com antecedência.

Agora fiquei curioso/interessado em MELHORAR esse código. Em consertá-lo para ele ser tão performático quanto a versão em Java. Isso é possível ou é melhor escrever isso em assembly? :P Vc mencionou alocação em blocos e até mesmo a implementação de alocadores personalizados. Isso é possível de se fazer para tornar esse código mais rápido?

Engraçado que as pessoas abrem a boca para falar que C++ é a coisa mais performática do mundo, blah, blah, blah. Se vc é DEUS talvez ele na sua mão seja bem performático mesmo. Se para implementar uma simples hash table você precisa pensar em 10 complexidades extras que com o Java vc não precisa nem saber que existem, realmente eu me pergunto como grandes empresas mantem projetos de C++ grandes, que envolvem vários programadores, com qualidade. Talvez o KERNEL do Linux tenha dado certo, poque por muito tempo o único que meteu a mão ali foi o Linus.

Abstração é muito importante. E Java te dá isso sobre C++ de uma maneira muito eficiente e performática. O JIT resolve muita coisa. Se vc quiser baixar o nível para "controlar melhor a performance" então vc deve ser um DEUS e nesse caso melhor partir direto para o assembly. A impressão que fica é que se você for apenas um bom/ótimo programador, o resultado final não vai ficar bom se comparado como poderia ter ficado com uma linguagem mais de alto nível (e igualmente performática) como o Java. Não vai ficar bom tanto do ponto de vista da performance como do ponto de vista da organização (clareza do código, ausência de bugs, ausência de vulnerabilidades, etc). Não vamos nem entrar na questão de que C++ é o paraíso das vulnerabilidades e até o governo americano está querendo se livrar dele nos seus sistemas.

Repare que no GitHub a gente também testou GraalVM compilando o Java para nativo ahead-of-time. Mas não fica mais rápido que a HotSpot JIT.

2

Vc mencionou alocação em blocos e até mesmo a implementação de alocadores personalizados. Isso é possível de se fazer para tornar esse código mais rápido?

Uma abordagem muito simples e ingênua seria, em vez de alocar cada entrada individualmente, alocar, digamos, 1000 de uma vez. Ou simplesmente alocar, digamos, 1 KB de memória, seria mais inteligente e alinhar o número de elementos para caber nesse tamanho de bloco predefinido. E se você estiver realmente preocupado com desempenho, poderia ajustar esse tamanho para preencher a cache L1 do seu processador.

Se você quiser ficar mais sofisticado, consulte a documentação do cppreference para o alocador polimórfico e etc. Ou se você quiser ir ainda mais fundo, estude a documentação do Linux para brk e implemente seu próprio malloc com ele. Ou não.

Se vc quiser baixar o nível para "controlar melhor a performance" então vc deve ser um DEUS e nesse caso melhor partir direto para o assembly.

Novamente, entender quais chamadas do sistema acontecem e quando é a primeira coisa a começar a entender e pensar sobre desempenho. Não, não precisa ser um deus. Mas tem que saber de sistemas operacionais e organização de computadores, no mínimo.

Programar em assembly, é uma grande perda de tempo. Compiladores modernos são altamente otimizados e geralmente superam em muito capacidade de micro-otimização humana.

No entanto, quando desempenho é crítico, uma prática essencial é examinar o código assembly gerado pelo compilador para identificar possíveis gargalos e oportunidades de otimização. Algumas vezes, o compilador pode falhar em gerar o código mais eficiente. Em muitos casos, alterar a construção da linguagem de alto nível é suficiente.

Além disso, temos as instruções mágicas (como as vetorias) de processadores atuais. Compiladores são bastante inteligentes e frequentemente otimizam o código para usar essas instruções sem intervenção explícita do programador, mas, em muitos casos, é necessário utilizá-las diretamente em código C/C++ de forma inline.

Um abraço e bons estudos!

1

E se você estiver realmente preocupado com desempenho, poderia ajustar esse tamanho para preencher a cache L1 do seu processador.

Com o Java também dá para fazer isso. Tenho um exemplo usando o Java heap e outro usando memória nativa via sun.misc.Unsafe.

Não é super complexo quanto parece. Tudo que vc tem que fazer é espaçar as coisas para isolá-las dentro de um cache line do CPU, de forma que outras coisas não te expulsem do cache sem que vc precisasse ter saído do cache. Para isso vc pode usar padding.

Acho que o disruptor foi o primeiro que fez isso com Java, mas posso estar errado.