- Implementa uma função que determina se um ano é bissexto usando apenas 3 instruções de CPU
- Esse método usa operações de bits e multiplicação para funcionar sem a abordagem tradicional com desvios
- Essa técnica é precisa no intervalo de 0 a 102499 anos
- Resultados de benchmark mostram desempenho cerca de 3,8 vezes mais rápido que o método convencional
Visão geral e apresentação do problema
- Para anos entre 0 e 102499, há uma função que determina se o ano é bissexto usando apenas 3 instruções de CPU
- A função usada de fato tem a forma
((y * 1073750999) & 3221352463) <= 126976
- Explica o princípio, o funcionamento e a utilidade prática dessa técnica de bit-twiddling
Método tradicional para determinar anos bissextos
- Normalmente, a verificação de ano bissexto é implementada com divisão modular e desvios condicionais
Otimização da abordagem padrão
- A verificação de
(y % 100) pode ser substituída por (y % 25), e a de (y % 400) por (y % 16)
- Isso é possível porque o número já foi previamente dividido por 4, permitindo a troca para a relação com 25 e 16
- Há uma constante mágica que converte a operação
(y % 25) em multiplicação e comparação, sem divisão
- Exemplo: pode ser transformada em
x * 3264175145 > 171798691
- Com uma máscara de bits adicional,
(y & 3) ou (y & 15) pode substituir a divisão por 4 ou 16
- Compiladores automatizam essas transformações, mas elas também podem ser aplicadas manualmente em outras linguagens
Implementação sem desvios (branchless)
Encontrando constantes mágicas: abordagem de bit-twiddling
- Para tornar a expressão de verificação de ano bissexto ainda mais simples, foram usados busca combinatória e o Z3, um SMT Solver
- Forma:
((y * f) & m) <= t
- As constantes f, m e t que satisfazem os requisitos foram buscadas com o Z3
- Foram encontrados valores que produzem resultados corretos no intervalo de 0 a 102499
- O resultado final é
(y * 1073750999) & 3221352463 <= 126976
Explicação do princípio da função e da estrutura interna
- Ao analisar as constantes em binário, elas são divididas em três regiões principais de bits: A, B e C
- Dependendo do estado dos bits em cada região, elas cobrem as 3 condições da verificação de ano bissexto
- Decomposição lógica da função:
- Região A: verifica a condição de múltiplo de 4, incluindo se
(y % 4) != 0
- Região B: filtra se
(y % 100) != 0 com vários padrões, como finais 14, 57, 71 etc.
- Região C: verifica se
(y % 16) == 0, ou seja, se é múltiplo de 16
- Explica o princípio pelo qual a multiplicação combina várias condições sem calcular restos diretamente
- Ao multiplicar pela constante mágica, formam-se padrões de bits característicos em múltiplos de 100 e outros casos
- O texto também inclui uma análise matemática da estrutura interna, incluindo erros de multiplicação e padrões em vários dígitos
Possibilidade de expandir o intervalo e a largura de bits
- Também é apresentado um método para buscar combinações adequadas de constantes mágicas ao expandir para 64 bits
- É possível variar os valores de
f, m e t para encontrar o maior intervalo possível
- Há também, no StackExchange, casos com combinações ótimas e provas de uso do Z3
Benchmark e comparação de desempenho real
- Resultados de benchmark:
- Em valores previsíveis, como 2025, a diferença é quase nula, com 0,65~0,69ns
- Com entradas aleatórias,
is_leap_year_fast mostra desempenho cerca de 3,8 vezes mais rápido
- Quando o padrão de entrada torna a abordagem com desvios imprevisível, há uma vantagem significativa
Conclusão e aplicabilidade prática
- Em aplicações reais, o ganho é pequeno quando os valores são previsíveis, mas a técnica é muito útil em cenários com grande volume de dados aleatórios
- Ao substituir bibliotecas padrão reais em Python, C# etc., é necessário fazer um benchmark realista do programa como um todo
- A ideia e a implementação em si são interessantes e, em situações específicas, constituem uma solução atraente em termos de desempenho
2 comentários
Um texto que lembra o fast inverse sqrt
Comentários do Hacker News
is_leap_year1para algo comois_leap_year2, então talvez não haja tanta necessidade de otimizar manualmente no nível do código-fonte em C, embora isso ainda possa ser útil em outras linguagens; também chamou atenção como a versão mais recente do código-fonte do programa cal trata a verificação de ano bissexto de forma muito simples((y * 1073750999) & 3221352463) <= 126976funciona acabe sendo complexa; na verdade, isso é o esperadoCMP(X, Y)eficiente para comparações no estilo UNIX, exemplos de otimização da função signum, exemplos de código assembly para Motorola 68000, conjuntos de macros de bits originados do OpenSolaris e outras referências; também mencionam com pesar que o blog do Open Solaris desapareceu, e recomendam esse material para quem se interessa por otimização de código