Estou ciente do impacto no desempenho ao misturar entradas assinadas com flutuadores.
É pior misturar ints não assinadas com flutuadores?
Existe algum acerto ao misturar assinados / não assinados sem carros alegóricos?
Os diferentes tamanhos (u32, u16, u8, i32, i16, i8) afetam o desempenho? Em quais plataformas?
c++
performance
Luis
fonte
fonte
Respostas:
A grande penalidade de misturar ints (de qualquer tipo) e floats é porque eles estão em conjuntos de registros diferentes. Para ir de um registro definido para o outro, é necessário gravar o valor na memória e lê-lo novamente, o que implica uma paralisação de carga e armazenamento .
Alternar entre tamanhos diferentes ou assinatura de entradas mantém tudo no mesmo conjunto de registros, para evitar a grande penalidade. Pode haver penalidades menores devido a extensões de sinal etc., mas são muito menores do que uma loja de acerto de carga.
fonte
Eu suspeito que as informações sobre o Xbox 360 e PS3 especificamente estejam atrás de muros somente para desenvolvedores licenciados, como a maioria dos detalhes de baixo nível. No entanto, podemos construir um programa x86 equivalente e desmontá-lo para ter uma idéia geral.
Primeiro, vamos ver quais custos de alargamento não assinados:
A parte relevante é desmontada (usando o GCC 4.4.5):
Então basicamente o mesmo - em um caso, movemos um byte, no outro, movemos uma palavra. Próximo:
Torna-se em:
Portanto, o custo da extensão do sinal é qualquer que seja o custo,
movsbl
e nãomovzbl
o nível de sub-instrução. Isso é basicamente impossível de quantificar nos processadores modernos devido à maneira como os processadores modernos funcionam. Todo o resto, variando da velocidade da memória ao cache, até o que estava no pipeline de antemão, vai dominar o tempo de execução.Nos ~ 10 minutos que levei para escrever esses testes, eu poderia facilmente encontrar um bug de desempenho real e, assim que ligo qualquer nível de otimização do compilador, o código fica irreconhecível para tarefas simples.
Isso não é Stack Overflow, por isso espero que ninguém aqui afirme que a microoptimização não importa. Os jogos geralmente trabalham com dados muito grandes e muito numéricos; portanto, uma atenção cuidadosa às ramificações, projeções, agendamento, alinhamento de estrutura etc. pode fornecer melhorias muito críticas. Qualquer pessoa que tenha passado muito tempo otimizando o código PPC provavelmente tem pelo menos uma história de horror sobre lojas de carregamento de itens atingidos. Mas neste caso, isso realmente não importa. O tamanho do armazenamento do seu tipo inteiro não afeta o desempenho, desde que esteja alinhado e caiba em um registro.
fonte
Operações inteiras assinadas podem ser mais caras em quase todas as arquiteturas. Por exemplo, a divisão por uma constante é mais rápida quando não assinada, por exemplo:
será otimizado para:
Mas...
otimizará para:
ou em sistemas onde a ramificação é barata,
O mesmo vale para o módulo. Isso também vale para não-potências-de-2 (mas o exemplo é mais complexo). Se sua arquitetura não possui uma divisão de hardware (por exemplo, a maioria dos ARM), as divisões não assinadas de não consts também são mais rápidas.
Em geral, dizer ao compilador que números negativos não podem resultar ajudará na otimização de expressões, especialmente aquelas usadas para finalização de loop e outras condicionais.
Quanto a diferentes tamanhos de entrada, sim, há um leve impacto, mas você teria que pesar esse valor vs movimentar menos memória. Atualmente, você provavelmente ganha mais acessando menos memória do que perde com a expansão de tamanho. Você está muito longe da micro-otimização nesse ponto.
fonte
As operações com int assinado ou não assinado têm o mesmo custo nos processadores atuais (x86_64, x86, powerpc, arm). No processador de 32 bits, u32, u16, u8 s32, s16, s8 devem ser os mesmos. Você pode ter penalidade com mau alinhamento.
Mas converter int para flutuar ou flutuar para int é uma operação cara. Você pode encontrar facilmente a implementação otimizada (SSE2, Neon ...).
O ponto mais importante é provavelmente o acesso à memória. Se seus dados não couberem no cache L1 / L2, você perderá mais ciclo do que conversão.
fonte
Jon Purdy diz acima (não posso comentar) que o não assinado pode ser mais lento porque não pode exceder. Eu discordo, aritmética não assinada é simples módulo aritmético moular 2 para o número de bits na palavra. As operações assinadas, em princípio, podem sofrer estouros, mas geralmente são desativadas.
Às vezes, você pode fazer coisas inteligentes (mas não muito legíveis), como agrupar dois ou mais itens de dados em um int e obter várias operações por instrução (aritmética de bolso). Mas você precisa entender o que está fazendo. Claro que a MMX permite que você faça isso naturalmente. Mas, às vezes, usar o maior tamanho de palavra suportado por HW e compactar manualmente os dados fornece a implementação mais rápida.
Tenha cuidado com o alinhamento de dados. Na maioria das implementações de HW, as cargas e lojas não alinhadas são mais lentas. Alinhamento natural, significa que, por exemplo, uma palavra de 4 bytes, o endereço é um múltiplo de quatro e os endereços da palavra de oito bytes devem ser múltiplos de oito bytes. Isso é transferido para o SSE (128 bits favorece o alinhamento de 16 bytes). Em breve, o AVX estenderá esses tamanhos de registro "vetorial" para 256 bits e depois para 512 bits. E as cargas / lojas alinhadas serão mais rápidas que as desalinhadas. Para os geeks de HW, uma operação de memória desalinhada pode abranger coisas como o cacheline e até os limites de página, para os quais o HW precisa ter cuidado.
fonte
É um pouco melhor usar números inteiros assinados para índices de loop, porque o estouro assinado não é definido em C; portanto, o compilador assumirá que esses loops têm menos casos de canto. Isso é controlado pelo "-fstrict-overflow" do gcc (ativado por padrão) e provavelmente é difícil notar o efeito sem ler a saída do assembly.
Além disso, o x86 funciona melhor se você não misturar tipos, porque pode usar operandos de memória. Se precisar converter tipos (sinal ou zero extensões), isso significa uma carga explícita e o uso de um registro.
Fique com int para variáveis locais e a maior parte disso acontecerá por padrão.
fonte
Como Celion aponta, a sobrecarga de conversão entre entradas e flutuadores tem a ver com a cópia e conversão de valores entre registros. A única sobrecarga de entradas não assinadas por si só vem de seu comportamento abrangente garantido, que requer uma certa quantidade de verificação de sobrecarga no código compilado.
Basicamente, não há sobrecarga na conversão entre números inteiros assinados e não assinados. Tamanhos diferentes de número inteiro podem ser (infinitesimalmente) mais rápidos ou mais lentos para acessar, dependendo da plataforma. De um modo geral, o tamanho do número inteiro mais próximo do tamanho da palavra da plataforma será o mais rápido de acessar, mas a diferença geral de desempenho depende de muitos outros fatores, principalmente do tamanho do cache: se você usar
uint64_t
quando tudo o que precisaruint32_t
, poderá menos dados caberão no cache de uma só vez e você poderá sofrer uma sobrecarga de carga.É um pouco excessivo pensar nisso, no entanto. Se você usa tipos apropriados para seus dados, as coisas devem funcionar perfeitamente bem, e a quantidade de energia a ser obtida pela seleção de tipos baseados na arquitetura é desprezível.
fonte