A % de uso de CPU é uma mentira
(brendanlong.com)- Em geral, o limite de desempenho de um servidor é avaliado pela % de uso de CPU em ferramentas de monitoramento como
top, mas na prática esse indicador não reflete o desempenho de forma linear - Em testes com stress-ng em um ambiente com Ryzen 9 5900X, quando o uso estava em 50%, a carga de trabalho real chegava a 60~100%, mostrando uma grande discrepância em relação ao indicador
- As principais causas são hyper-threading e turbo boost, já que o compartilhamento de recursos entre núcleos lógicos e a variação da frequência de clock distorcem a métrica
- Por isso, em vez de usar apenas o uso de CPU, um indicador mais preciso é comparar benchmarks da carga de trabalho realmente processável com a vazão atual
- Interpretar o uso de CPU de forma linear gera grandes erros na estimativa de desempenho, então é necessário um método baseado em benchmark no planejamento do sistema
Incompatibilidade entre o valor de uso de CPU do servidor e a vazão real
- Ao operar um servidor, muita gente quer verificar se ele está perto da utilização máxima
- Em geral, consulta-se o maior valor entre uso de rede, memória e CPU em ferramentas de monitoramento como
top - Mas, na prática, existe o problema de o valor de uso de CPU e a quantidade de trabalho que pode ser processada não crescerem de forma linear
Ambiente e método de teste
- Experimento baseado em Ubuntu Desktop + Ryzen 9 5900X (12 núcleos/24 threads)
- Precision Boost Overdrive (Turbo) ativado
- Simulação de várias cargas com stress-ng (1~24 workers, 1~100% de uso)
- Métricas medidas: uso de CPU reportado pelo sistema e volume real de cálculo (Bogo ops)
Resumo dos resultados
- Carga geral de CPU: com 50% de uso, vazão real de 60~65%
- Operações inteiras de 64 bits: com 50% de uso, vazão de 65~85%
- Operações de matriz (Matrix math): com 50% de uso, vazão de 80~100%
- Na prática, mesmo quando workers adicionais não contribuem para o desempenho, o uso de CPU continua subindo
Análise das causas
-
Hyper-threading
- Estrutura com 12 núcleos físicos + 12 núcleos lógicos
- Até 12 workers são alocados de forma ideal nos núcleos físicos, mas acima disso há compartilhamento de núcleos lógicos, o que reduz o desempenho
- Especialmente em operações SIMD (operações de matriz), não há ganho de desempenho por falta de recursos compartilháveis
-
Turbo boost
- Em baixa carga, 4.9GHz → em carga total, 4.3GHz, ou seja, queda de 15% no clock
- Isso causa distorção na fórmula de cálculo do uso de CPU (= busy cycles / total cycles)
- Como o denominador (número total de ciclos) diminui, o aumento do uso é superestimado em relação ao trabalho real
Implicações
- Uso de CPU não é um indicador absoluto de desempenho
- Ao dimensionar capacidade de servidor e prever desempenho:
- 1. Medir a vazão máxima com benchmark
- 2. Monitorar a vazão em tempo real
- 3. Comparar os dois valores para avaliar se está perto do limite de desempenho
- Como há grande variação conforme a arquitetura da CPU (AMD vs Intel), a eficiência do hyper-threading e o funcionamento do turbo, é necessária análise por processador
Conclusão
- O uso de CPU é apenas a proporção de ciclos ocupados, e não reflete com precisão o desempenho real de processamento
- Com uso eficiente, mesmo "50% de uso" pode já representar 80~100% do desempenho máximo
- Portanto, monitoramento de desempenho e planejamento de sistema devem se concentrar em vazão de trabalho baseada em benchmark, e não no uso de CPU
3 comentários
Em ambientes reais de configuração de HPC, o cluster normalmente é montado com hyper-threading desativado por padrão.
Comentários no Hacker News
Não acho que a utilização de CPU seja uma mentira, no sentido de que ela mede uma quantidade claramente definida, mas as pessoas acabam vendo a diferença entre expectativa e realidade quando tentam extrapolar isso para criar um modelo de capacidade
Hyperthreading (SMT) e Turbo (escala de clock) são apenas algumas das várias variáveis que causam não linearidade; largura de banda de memória compartilhada entre os núcleos, capacidade de interconexão e cache do processador também são recursos que se “esgotam” conforme a carga aumenta
No software também há fenômenos, como spinlocks, que podem ter impacto não linear na utilização
A maioria das métricas de utilização de CPU faz médias longas, de alguns segundos até 1 minuto, mas o que importa para o desempenho de servidores sensíveis à latência acontece em dezenas a centenas de milissegundos
Por isso, a utilização média em janelas de vários segundos não consegue distinguir padrões em burst de padrões suaves
Mas a abordagem proposta, isto é, “fazer benchmark de quanto trabalho o servidor consegue executar antes que ocorram erros ou latência inaceitável”, também é inerentemente instável
Quando se tenta encontrar o ponto em que o servidor começa a ficar instável, as medições ficam muito ruidosas, e pela teoria de filas todo ruído é amplificado perto do ponto crítico
Além disso, o próprio “volume de trabalho que o servidor está executando agora” costuma ser definido de forma instável (é RPS? cada requisição tem um custo diferente, e o IPC também varia com o tempo)
No fim, o intervalo de confiança obtido pela abordagem de teste de carga acaba sendo mais ou menos do mesmo nível que construir um modelo empírico com métricas de utilização. A premissa é medir corretamente a utilização
Se você souber usar
perfouftrace, dá para obter métricas de processador muito detalhadas em curto prazo e ver diretamente várias causas, como stalls de CPU por cache miss ou acesso à memória, efeito do scheduler etc.Mas a maioria das pessoas, mesmo recebendo métricas como IPC ou taxa de cache hit, não sabe o que fazer com isso
No fim, o que a maioria realmente quer observar é latência e utilização
Falando de forma grosseira, na maioria das cargas de trabalho não há muito problema de latência até a utilização de CPU passar de 80%
Mas acima disso, começa a afetar seriamente a latência da carga de trabalho
Quanto isso afeta a latência, só medindo a própria carga de trabalho para saber
O quanto você é sensível à latência também depende da organização e do objetivo, e às vezes otimizar throughput pode ser mais importante
Se você se importa com latência e throughput ao mesmo tempo, meça os dois e decida por conta própria o trade-off adequado
Acho que um dos pontos centrais é justamente que a própria definição de “volume de trabalho” é instável
Por exemplo, é diferente se estamos falando de um serviço público atendendo requisições reais de usuários ou de uma carga de treinamento de IA no backend calculando dados acumulados com calma
A minha abordagem é que, no ambiente moderno de CPUs multicore com hyperthreading, em que bursts acabam acontecendo com frequência, 60% de utilização de CPU já caracteriza um servidor como “altamente carregado”
Se passar de 60% por uma parte significativa do dia, considero adequado dividir o trabalho (principalmente para serviços que respondem a requisições de usuários)
No passado, esse critério frequentemente levava a pensar em autoscaling na nuvem
Hoje em dia, dá para usar servidores com mais de 100 núcleos por algo em torno de 30 mil dólares, então a situação ficou mais complexa
Hoje, eu tenderia a uma abordagem híbrida: provisionar um pouco demais no hardware real e expandir conforme necessário para serviços em nuvem (ou uma nuvem interna baseada em Kubernetes)
O StackOverflow, nos primeiros anos, operava tráfego enorme com eficiência usando rack dedicado e uplink de 10 Gbps, e hoje isso é ainda mais fácil de fazer
Em resumo, pelo meu critério, se a carga de CPU se mantiver em 65% por mais de 30 minutos, isso já conta como 100% de uso efetivo e eu concluo que é preciso escalar em breve
Em uma keynote recente da IEEE Hot Interconnects, também foi mencionado um caso de tuning de latência do Ultra Ethernet
Em janelas de 2 ou 5 segundos pode parecer tudo estável, mas quando se olha na escala de 100 ms o fenômeno de frame burst aparece claramente
Ou seja, se a janela de medição do profiling não combina com a carga real, você pode chegar a uma conclusão errada por falso negativo, e isso acaba piorando o problema
Concordo totalmente com o ponto levantado
É justamente por causa da natureza não linear da utilização de CPU que o próprio valor em % se distancia da realidade
Em outras palavras, o argumento é que a mentira é a utilização de CPU medida apenas em porcentagem
Se duas cargas de trabalho registram ambas 100% de uso de CPU, mas uma delas consome muito mais energia e eleva bastante a temperatura do processador, surge a dúvida: será que, na prática, essa carga não está usando mais recursos de transistores?
Mesmo que a utilização de CPU não seja uma métrica perfeita, na prática ela ainda pode ser útil
Mesmo em experiência simples de trabalho de SRE, para tarefas CPU-bound eu já usei números de utilização de CPU junto com teoria de filas para dimensionar servidores antes de grandes eventos, e mesmo usando um critério de %CPU bem mais conservador do que uma abordagem mais “tradicional”, o resultado saiu muito melhor em custo-benefício
O ponto principal é: se uma métrica é um pouco imprecisa mas útil no campo, vale usá-la sem se preocupar demais
Dito isso, eu mantinha a utilização de CPU em produção obrigatoriamente abaixo de 40% para garantir um pouco de headroom em vários cenários
Faltou, na minha opinião, uma justificativa lógica mais forte do autor para a ideia de “evitar alta utilização por teoria de filas”
Acho que mesmo uma métrica meio capenga basta, desde que seja usada adequadamente na prática
Por exemplo, mesmo usando simplesmente média/máximo de métricas percentílicas por host, ou calculando percentis na agregação final a partir de histogramas por host, na prática a troca entre uma abordagem e outra quase nunca fez diferença relevante na operação real
Tem gente que se preocupa demais com o que está matematicamente certo ou errado, mas no mundo real isso muitas vezes não muda grande coisa
40% na verdade me parece uma utilização bem folgada
Acho que olhar CPU% junto com
loadavgajuda bastante a entender o estado do sistemaSe o
loadavgestá alto e o CPU% está baixo, pode ser que o sistema esteja preso em rede ou I/O, ou esperando por system callsNesse caso, olhar só CPU% pode fazer você perder o que realmente está sofrendo
Tenho a sensação de já ter ouvido exatamente esse discurso antes
O que o autor menciona está sendo repetido há décadas em livros de teoria de filas, então é curioso que ele escreva como se tivesse descoberto isso só agora
Isso me lembrou o texto de Brendan Gregg, "CPU Utilization is Wrong"
O ponto central do blog é que a utilização de CPU usa apenas a “situação” de CPU ocupada como métrica, e inclusive considera como busy casos em que a CPU está parada esperando
E o IPC é uma medida para entender o volume real de trabalho útil escondido dentro desse estado de busy
Se houver mais algum Brendan por aí, espero que possa explicar
Acho que não se deve contar o desempenho fornecido por hyperthreads como se fosse o dobro
Na prática, mesmo aproveitando bem hyperthreads, muitas vezes o ganho adicional fica em algo como 15% a 30%. Em compensação, a latência pode dobrar
Como também é preciso considerar a queda de clock quando a utilização dos núcleos sobe, é importante sempre lembrar que no mundo real isso é não linear
Só com as informações fornecidas pelo sistema operacional já dá para estimar a utilização com muito mais precisão, levando em conta o efeito de hyperthreads e a queda de clock
Ir além disso e modelar efeitos como limitação de cache/largura de banda de memória ou stalls de pipeline causando perda de desempenho também não me parece tão difícil
Um fator que complica a situação é que a eficiência do hyperthread varia enormemente conforme a arquitetura da CPU e a carga de trabalho
Por exemplo, implementações da AMD (especialmente as Zen mais recentes) tendem a entregar desempenho muito mais independente do que as da Intel (assumindo que não haja gargalo de largura de banda de memória)
Em aplicações memory-bound, costuma ser mais fácil ver scaling melhor com hyperthread
Em um projeto de renderização do qual participei antes, como era memory-bound, vimos ganho de 60% a 70% de desempenho só com hyperthread
Há algum tempo rodei um benchmark simples com POV-Ray num i7-3770K (4C/8T)
De 1 thread para 2 threads foi exatamente o dobro, de 2 para 4 também dobrou, mas de 4 para 8 houve só uns 15% de ganho
Talvez num benchmark esquisito que repita artificialmente cache misses desse para tirar desempenho quase dobrado com SMT, mas tenho dúvidas sobre o quanto isso seria realista
Aliás, estou pensando em rodar de novo o teste com POV-Ray. Faz tanto tempo que até bate saudade
Parece que o autor percebeu que o desempenho não cresce linearmente conforme o número de %CPU utilization e, como resultado, concluiu que o próprio %CPU utilization é uma “mentira”
Mesmo sem hyperthreading e sem clock throttling, e até em casos que parecem ter menos variáveis, como Apple silicon, o scaling não sai exatamente proporcional
Quando vários núcleos começam a ser usados ao mesmo tempo, gargalos em recursos fora da CPU, como overhead de transferência de dados, frequentemente causam o mesmo tipo de problema
A terminologia de núcleos usada pelo autor está confusa e fora do padrão
Ele chama um 5900X de sistema de 24 núcleos, mas na prática é uma estrutura com 12 núcleos físicos e 24 hyperthreads em execução
Doze são núcleos físicos e os outros doze são threads acopladas a cada núcleo
Mesmo que haja dois conjuntos de instruction pipeline, as unidades funcionais internas são compartilhadas
Alguns anos atrás, tentando explicar hyperthreading para meu irmão mais novo, usei uma analogia que ficou na memória: é como papel higiênico de folha dupla
Você não consegue separar em 24 unidades independentes, mas é parecido com usar algo de 12 de forma duas vezes mais útil
Depois do feedback, corrigi a descrição para 12 núcleos / 24 threads
Mas o meu sistema operacional mostrava a utilização como se fossem 24 núcleos, então isso acabou me confundindo
Seria interessante se a Intel lançasse núcleos definidos por software
Seria o oposto lógico do hyperthreading: uma estrutura em que dois ou mais núcleos compartilham recursos e se convertem em “um grande núcleo”
Veja a patente no link abaixo
https://patents.google.com/patent/EP4579444A1/en
Quando um par SMT executa o mesmo tipo de carga de trabalho, o efeito do SMT cai por causa da disputa por recursos internos e unidades de execução
Se as workloads forem totalmente diferentes, o boost pode até ser maior
Hoje a situação fica ainda mais complexa com variáveis como P-cores, E-cores, turbo/não turbo nos CPUs modernos
Houve um estudo mostrando que adicionar SMT trazia mais ganho de desempenho por watt do que adicionar turbo, o que achei muito interessante
Identificação total com isso
Uma vez, no passado, o CPU do servidor chegou a 60% e eu expliquei para meu gerente que já não havia mais folga, e ele ficou me olhando com uma expressão estranha
Naquele momento, eu queria muito ter uma explicação como esta
Explicar junto a teoria de filas costuma funcionar muito bem
Abaixo de 60%, o atraso de enfileiramento é praticamente desprezível
Ao passar de 70%, a latência começa a ficar claramente perceptível, e a partir de 80% quase dobra
Na minha experiência real, eu mirava 65% com base em tempo P95, e isso quase batia com o critério teórico
Em resumo, a regra prática é: “60% é o limite de uso, a partir de 80% a latência explode”
Mas isso pode mudar completamente dependendo da carga de trabalho
Principalmente hoje, numa era em que CPUs de servidor têm 32 núcleos
Muitas vezes a utilização de CPU não se comporta como esperado
Normalmente, num texto desse tipo, eu esperaria uma discussão sobre como o valor de Utilization é calculado no Linux/Windows, mas na prática também há muitos casos em que a RAM é o gargalo e a CPU fica ociosa esperando ou reduzindo clock
Na prática, a utilização de CPU é apenas uma medida de quanto o sistema operacional (seja Windows ou Linux) atribui threads a cada núcleo
Mas, se essa thread estiver 100% parada dentro de um
memcpy, isso ainda entra como utilizaçãoA vantagem do hyperthreading é que, se uma thread estiver presa na unidade AVX/vetorial e outra estiver presa em
memcpy/RAM, dá para aumentar a utilização de cada unidade e elevar o desempenho total e a utilizaçãoNo fim, CPU Utilization é um tema intuitivamente difícil de entender há muito tempo, e sempre surgem novas perspectivas
Ainda assim, é sempre um assunto interessante
A verdadeira “mentira” é acreditar que um núcleo com hyperthread se comporta igual a um núcleo de verdade
No fim, depois de usar isso por mais de 20 anos, a gente esquece a natureza original da coisa e volta a se surpreender repetidamente com por que as medições de desempenho parecem estranhas
Outra coisa é que, no fundo, o processador só está “executando” (100%) ou “parado” (0%)
Atribuir uma porcentagem intermediária por si só nada mais é do que uma média em alguma unidade de tempo específica
Já tive uma conversa assim no passado
Gerente: como a utilização de CPU está em 100%, não deveríamos trocar o servidor por uma instância maior?
Eu: “mas a CPU está realmente fazendo trabalho útil agora?”
No fim, como busy waiting também entra como utilização de CPU, às vezes o número sobe sem relação com trabalho útil de verdade
É a expressão correta.