Estou trabalhando em um aplicativo Java para resolver uma classe de problemas de otimização numérica - problemas de programação linear em larga escala para ser mais preciso. Um único problema pode ser dividido em subproblemas menores que podem ser resolvidos em paralelo. Como existem mais subproblemas do que núcleos de CPU, eu uso um ExecutorService e defino cada subproblema como Callable que é enviado ao ExecutorService. A solução de um subproblema requer a chamada de uma biblioteca nativa - neste caso, um solucionador de programação linear.
Problema
Posso executar o aplicativo nos sistemas Unix e Windows com até 44 núcleos físicos e até 256g de memória, mas os tempos de computação no Windows são uma ordem de magnitude maior que no Linux para grandes problemas. O Windows não apenas requer substancialmente mais memória, mas a utilização da CPU ao longo do tempo cai de 25% no início para 5% após algumas horas. Aqui está uma captura de tela do gerenciador de tarefas no Windows:
Observações
- Os tempos de solução para grandes instâncias do problema geral variam de horas a dias e consomem até 32g de memória (no Unix). Os tempos de solução para um subproblema estão na faixa de ms.
- Não encontro esse problema em pequenos problemas que levam apenas alguns minutos para serem resolvidos.
- O Linux usa os dois soquetes prontos para uso, enquanto o Windows exige que eu ative explicitamente a intercalação de memória no BIOS para que o aplicativo utilize os dois núcleos. Independentemente de eu não fazer isso, isso não afeta a deterioração da utilização geral da CPU ao longo do tempo.
- Quando olho para os threads no VisualVM, todos os threads do pool estão em execução, nenhum está em espera ou então.
- De acordo com o VisualVM, 90% do tempo da CPU é gasto em uma chamada de função nativa (resolvendo um pequeno programa linear)
- A Coleta de Lixo não é um problema, pois o aplicativo não cria e não faz referência a muitos objetos. Além disso, a maioria da memória parece estar alocada fora da pilha. 4g de heap são suficientes no Linux e 8g no Windows para a maior instância.
O que eu tentei
- todos os tipos de argumentos da JVM, alto XMS, alto metasspace, sinalizador UseNUMA e outros GCs.
- JVMs diferentes (ponto de acesso 8, 9, 10, 11).
- diferentes bibliotecas nativas de diferentes solucionadores de programação linear (CLP, Xpress, Cplex, Gurobi).
Questões
- O que impulsiona a diferença de desempenho entre o Linux e o Windows de um aplicativo Java multiencadeado grande que faz uso intenso de chamadas nativas?
- Existe algo que eu possa alterar na implementação que ajude o Windows, por exemplo, devo evitar o uso de um ExecutorService que receba milhares de chamadas e faça o que?
ForkJoinPool
é mais eficiente que o agendamento manual.
ForkJoinPool
vez deExecutorService
? A utilização de 25% da CPU é muito baixa se o problema estiver ligado à CPU.