Verificando se é ano bissexto com 3 instruções
(hueffner.de)- 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
- A função usada de fato tem a forma
- 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
- Verifica-se se é divisível por 4, se não é divisível por 100 e se é divisível por 400
- Código de exemplo:
if ((y % 4) != 0) return false if ((y % 100) != 0) return true if ((y % 400) == 0) return true return false
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
- Exemplo: pode ser transformada em
- 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)
- Também é possível converter para uma forma sem desvios:
return !(y & ((y % 25) ? 3 : 15)) - Esse tipo de abordagem é adequado para code golf, em que se busca reduzir o tamanho do código
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
- Forma:
- 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) != 0com 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
- Região A: verifica a condição de múltiplo de 4, incluindo se
- 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,metpara encontrar o maior intervalo possível - Há também, no StackExchange, casos com combinações ótimas e provas de uso do Z3
- É possível variar os valores de
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_fastmostra 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