Suponha que eu receba uma matriz de números inteiros de largura fixa (ou seja, eles se encaixam em um registro de largura ), . Eu quero calcular a soma em uma máquina com aritmética de complemento 2, que executa adições do módulo com semântica envolvente. Isso é fácil - mas a soma pode exceder o tamanho do registro e, se o fizer, o resultado estará errado.
Se a soma não exceder, desejo calculá-la e verificar se não há excedência o mais rápido possível. Se a soma exceder, quero apenas saber que sim, não me importo com nenhum valor.
Adicionar ingenuamente números na ordem não funciona, porque uma soma parcial pode estourar. Por exemplo, com registradores de 8 bits, é válido e possui uma soma de , mesmo que a soma parcial excede o intervalo de registro .
Obviamente, eu poderia usar um registro maior como um acumulador, mas vamos assumir o caso interessante em que já estou usando o maior tamanho de registro possível.
Existe uma técnica conhecida para adicionar números com o sinal oposto à soma parcial atual . Essa técnica evita estouros a cada etapa, com o custo de não ser compatível com o cache e não aproveitar muito a previsão de ramificação e a execução especulativa.
Existe uma técnica mais rápida que talvez aproveite a permissão para estourar somas parciais e seja mais rápida em uma máquina típica com um sinalizador de estouro, um cache, um preditor de ramificação e execução e cargas especulativas?
(Este é um acompanhamento da soma segura de estouro )
fonte
Respostas:
Você pode adicionarn números de tamanho W sem qualquer excesso se você estiver usando ⌈ logn ⌉ + w bits aritméticos. Minha sugestão é fazer exatamente isso e depois verificar se o resultado está no intervalo. Os algoritmos para a aritmética de multiprecisão são bem conhecidos (consulte a seção 4.3 do TAOCP, se você precisar de uma referência); geralmente há suporte de hardware para adição ( sinalizador de transporte e inclusão com instruções de transporte ), mesmo sem esse suporte, você pode implementá-lo sem salto dependente de dados ( o que é bom para os preditores de salto) e você precisa de apenas uma transmissão dos dados e pode visitá-los na ordem mais conveniente (o que é bom para o cache).
Se os dados não couberem na memória, o fator limitante será o pedido de veiculação e quão bem você conseguirá sobrepor o pedido de veiculação à computação.
Se os dados couberem na memória, você provavelmente terá⌈ logn ⌉ ≤ w (a única exceção que consigo pensar é no microprocessador de 8 bits, que geralmente possui 64 K de memória), o que significa que você está fazendo uma aritmética de precisão dupla. A sobrecarga ao longo de um loop fazendoW A aritmética de bits pode ser apenas duas instruções (uma para assinar estender, outra para adicionar com carry) e um leve aumento da pressão do registro (mas, se eu estiver certo, até o registro faminto do x86 tem registros suficientes que o único acesso à memória no o loop interno pode buscar os dados). Eu acho que é provável que um processador OO seja capaz de agendar operações adicionais durante a latência de carga da memória, para que o loop interno seja executado na velocidade da memória e, portanto, o exercício será o de maximizar o uso da largura de banda disponível (pré-busca técnicas de intercalação podem ajudar, dependendo da arquitetura da memória).
Considerando o ponto mais recente, é difícil pensar em outros algoritmos com melhor desempenho. Os saltos dependentes de dados (e, portanto, não previsíveis) estão fora de questão, assim como várias passagens nos dados. Mesmo tentar usar os vários núcleos do processador de hoje seria difícil, pois a largura de banda da memória provavelmente ficará saturada, mas poderia ser uma maneira fácil de implementar o acesso intercalado.
fonte
Em uma máquina onde tipos inteiros se comportam como um anel algébrico abstrato [basicamente significando que eles envolvem], pode-se calcular as somas dos itens [i] e (item [i] >> 16) para até 32767 itens. O primeiro valor daria os 32 bits inferiores da soma correta. O último valor produziria os bits 16-47 de algo próximo à soma correta e, usando o valor anterior, ele pode ser facilmente ajustado para produzir os bits 16-47 da soma correta exata.
O pseudocódigo seria algo como:
Após o código acima, Sum2 e Sum1 juntos devem gerar a soma correta, independentemente dos estouros intermediários. Se for necessário totalizar mais de 32768 números, eles podem ser divididos em grupos de 32768 e, após calcular Sum2 para cada grupo, é possível adicioná-lo a uma "grande soma" de duas variáveis para todos os grupos como um todo.
Em alguns idiomas, o operador shift right right pode ser substituído por uma divisão por 65536. Isso geralmente funciona ao calcular o Sum2, mas não ao extrair o Sum1MSB. O problema é que alguns idiomas arredondam as divisões para zero, enquanto aqui é necessário executar um arredondamento de divisão para o próximo número mais baixo (em direção ao infinito negativo). Os erros na computação do Sum2 seriam corrigidos mais tarde, mas os erros na computação do Sum2LSB afetariam o resultado final.
Observe que nada nos resultados finais indicaria se algum dos cálculos envolvendo Sum1 havia "transbordado", mas se os valores são garantidos para quebrar o código de forma limpa, não é necessário se preocupar se ocorreu algum transbordamento.
fonte