Como parte da minha rotina de avaliação de dados, estou realizando 1.000.000 de simulações de Monte-Carlo em meu MacBook Pro M1 Pro (10 Core, 32 GB de RAM) usando vários processos em Python 3.10.5 (. Os tempos de execução são concurrent. futures.ProcessPoolExecutor)
:
Apple MacBook Pro, M1 Pro 10 Core CPU
macOS Monterey 12.6.2
Python 3.10.5 | packaged by conda-forge | (main, Jun 14 2022, 07:07:06) [Clang 13.0.1 ] on darwin
without setting CPU affinity, single run
Cores Simulations Execution Time
10 1000 00:00:03.114602
10 10000 00:00:16.658438
10 100000 00:02:39.969048
10 1000000 00:26:23.064365
Tentando diminuir o tempo de cálculo e carregar o trabalho da minha máquina principal, decidi realizar o cálculo em uma estação de trabalho Dual Xeon E5-2687Wv4 mais antiga utilizando 20 núcleos (hyper-threading desativado):
DELL Precision T7810, 2x Xeon E5-2687W v4
Ubuntu 22.04.3 LTS
Python 3.10.5 | packaged by conda-forge | (main, Jun 14 2022, 07:06:46) [GCC 10.3.0] on linux
without setting CPU affinity, single run
Cores Simulations Execution Time
20 1000 00:00:03.913254
20 10000 00:00:16.684702
20 100000 00:02:31.481626
20 1000000 00:27:44.841615
De acordo com os números acima, não consegui ver nenhum aumento perceptível no desempenho. No entanto, usar apenas 20 dos 24 núcleos disponíveis pode produzir alguma sobrecarga, pois o escalonador tende a trocar os núcleos do processador. Para investigar esse efeito potencial, configurei manualmente a afinidade da CPU de cada processo e obtive os seguintes resultados:
DELL Precision T7810, 2x Xeon E5-2687W v4
Ubuntu 22.04.3 LTS
Python 3.10.5 | packaged by conda-forge | (main, Jun 14 2022, 07:06:46) [GCC 10.3.0] on linux
with setting CPU affinity, single run
Cores Simulations Execution Time
20 1000 00:00:03.855061
20 10000 00:00:17.721105
20 100000 00:02:39.870485
20 1000000 00:26:22.462597
Novamente, nenhuma diferença no desempenho foi perceptível. Para garantir que o código seja dimensionado em geral, testei a execução com 10, 16 e 20 núcleos na estação de trabalho:
DELL Precision T7810, 2x Xeon E5-2687W v4
Ubuntu 22.04.3 LTS
Python 3.10.5 | packaged by conda-forge | (main, Jun 14 2022, 07:06:46) [GCC 10.3.0] on linux
with setting CPU affinity, single run
Cores Simulations Execution Time
10 1000 00:00:04.274913
10 10000 00:00:30.311358
10 100000 00:04:57.086862
10 1000000 00:50:58.328345
Cores Simulations Execution Time
16 1000 00:00:03.605890
16 10000 00:00:21.139773
16 100000 00:03:25.156981
16 1000000 00:35:11.151080
Cores Simulations Execution Time
20 1000 00:00:03.855061
20 10000 00:00:17.721105
20 100000 00:02:39.870485
20 1000000 00:26:22.462597
Os tempos de execução parecem escalar linearmente com o número de núcleos (exceto por alguma sobrecarga devido à geração dos processos em um número menor de simulações).
Com base em números de benchmark comuns entre o M1 Pro da Apple e o Dual Xeon E5-2687Wv4 como
- PassMark para M1 Pro 10 Core
- PassMark para Dual Xeon E5-2687Wv4
- Benchmark OpenFOAM com M1 Pro e Dual Xeon E5-2987Wv4
Eu esperava um aumento de desempenho de cerca de 25...30% (ou pelo menos 15% se considerarmos estes benchmarks como incertos). No entanto, minhas simulações de Monte-Carlo apresentam desempenho aproximadamente equivalente em ambos os sistemas.
Com base nas descobertas acima, minhas perguntas são:
- Isso se deve apenas à arquitetura mais moderna do M1 Pro da Apple?
- O que estou perdendo aqui (apesar de o próprio Python ser bastante lento)?
- Como eu poderia investigar esse problema de dimensionamento com mais detalhes?
Juntamente com
Não funcionam necessariamente bem juntos, especialmente junto com outro problema que também destaco abaixo.
Um único Xeon deve superar o M1 Pro quando o hyper threading estiver habilitado.
Muitos benchmarks multi-core mostram que o hyper threading pode aumentar o desempenho de 15 a 30%. . Existem alguns casos extremos em que não há benefício para o Hyper Threading, mas na maioria das vezes o Hyper Threading permite que várias partes do núcleo da CPU sejam melhor carregadas, permitindo que threads independentes usem partes subutilizadas da CPU.
Ao desativar o hyperthreading, você basicamente reduziu o desempenho do Xeon em algo entre 0 e 30%.
Do benchmark de CPU comparando Apple M1 Pro 10 Core 3200 MHz vs Intel Xeon E5-2687W v4 @ 3,00 GHz
Em um único thread, o Apple M1 Pro potencialmente tem uma vantagem sobre o Xeon, mas em um benchmark multi-core (CPU Mark), o Xeon deveria vencer por 25%, mas ao desabilitar o Hyper Threading você roubou a vantagem do Xeon.
Isso, por si só, não deveria colocar um sistema Xeon duplo em desvantagem, mas um sistema com processadores Xeon duplos também tem outros problemas potenciais.
A arquitetura dual Xeon resulta em um problema de sincronização. Para que cada thread seja executado na segunda CPU, os dados devem ser copiados através do link QPI entre os dois processadores para a memória do segundo processador, veja a imagem abaixo para a arquitetura. Os resultados devem então ser copiados de volta através do link QPI e para o thread mestre para serem manipulados. Isso representa um gargalo no sistema, especialmente se seus threads tiverem conjuntos de dados pequenos.
Combinar esse gargalo com o hyper threading desabilitado significa que sua carga de trabalho precisa estar ajustada e ciente do sistema. Pode ser que um sistema antigo de CPU dupla não seja mais rápido do que um processador moderno que já possui uma vantagem significativa (observe o benchmark de núcleo único do M1 Pro) e não apresenta as desvantagens de uma arquitetura de memória NUMA . Observo também que existem diferentes modos de cluster para Xeons modernos que podem alterar o desempenho , mas exigem ajuste do sistema.
Um sistema de CPU única com desempenho de núcleo único significativamente maior pode potencialmente superar um sistema "mais poderoso", dependendo da tarefa e da arquitetura do sistema.
Tanto a Intel quanto a AMD vêm empacotando cada vez mais núcleos em um único pacote há algum tempo, o sistema resultante é mais previsível e possui uma interface de memória mais consistente.