Eu sei que as operações bit a bit são tão rápidas nos processadores modernos, porque elas podem operar em 32 ou 64 bits em paralelo; portanto, as operações bit a bit levam apenas um ciclo de clock. No entanto, a adição é uma operação complexa que consiste em pelo menos uma e possivelmente até uma dúzia de operações bit a bit, então naturalmente pensei que seria 3-4 vezes mais lenta. Fiquei surpreso ao ver, após um simples benchmark, que a adição é exatamente tão rápida quanto qualquer uma das operações bit a bit (XOR, OR, AND etc.). Alguém pode esclarecer isto?
73
Respostas:
A adição é rápida porque os projetistas de CPU instalaram o circuito necessário para torná-lo mais rápido. São necessários muito mais portões do que operações bit a bit, mas é frequente o suficiente para que os projetistas de CPU considerem que vale a pena. Consulte https://en.wikipedia.org/wiki/Adder_(electronics) .
Ambos podem ser feitos com rapidez suficiente para serem executados em um único ciclo da CPU. Eles não são igualmente rápidos - a adição requer mais portas e mais latência do que uma operação bit a bit - mas é rápido o suficiente para que um processador possa fazer isso em um ciclo de clock. Há uma sobrecarga de latência por instrução para a lógica de decodificação e controle de instruções, e a latência é significativamente maior que a latência para executar uma operação bit a bit, de modo que a diferença entre os dois é inundada por essa sobrecarga. A resposta do AProgrammer e a resposta de Paul92 explicam bem esses efeitos.
fonte
Existem vários aspectos.
O custo relativo de uma operação bit a bit e uma adição. Um somador ingênuo terá uma profundidade de porta que depende linearmente da largura da palavra. Existem abordagens alternativas, mais caras em termos de portões, que reduzem a profundidade (IIRC a profundidade então depende logaritmicamente da largura da palavra). Outros já deram referências para essas técnicas, apenas ressaltarei que a diferença também é menos importante do que parece, considerando apenas o custo da operação, devido à necessidade de lógica de controle que adicione atrasos.
Além disso, existe o fato de que os processadores geralmente têm clock de clock (estou ciente de algumas pesquisas ou projetos com clock de uso especial, mas não tenho certeza de que alguns estejam disponíveis comercialmente). Isso significa que, qualquer que seja a velocidade de uma operação, será necessário um múltiplo inteiro do ciclo do relógio.
Finalmente, há as considerações micro-arquitetônicas: você tem certeza de que mede o que deseja? Atualmente, os processadores tendem a ser pipelines, multi-escalares, com execução fora de ordem e qualquer outra coisa. Isso significa que eles são capazes de executar várias instruções ao mesmo tempo, em vários estágios de conclusão. Se você deseja mostrar por medições que uma operação leva mais tempo que outra, é necessário levar em consideração esses aspectos, pois o objetivo deles é ocultar essas diferenças. Você pode muito bem ter a mesma taxa de transferência para operações de adição e bit a bit ao usar dados independentes, mas uma medida da latência ou introdução de dependências entre as operações pode mostrar o contrário. E você também deve ter certeza de que o gargalo da sua medida está na execução e não, por exemplo, nos acessos à memória.
fonte
paddw
) a 2 por relógio, mas booleanos (comopand
) a 3 por relógio. (Skylake coloca uma víbora vetor em todas as portas de execução de três vetores.)CPUs operam em ciclos. A cada ciclo, algo acontece. Normalmente, uma instrução leva mais ciclos para executar, mas várias instruções são executadas ao mesmo tempo, em diferentes estados.
Por exemplo, um processador simples pode ter 3 etapas para cada instrução: buscar, executar e armazenar. A qualquer momento, três instruções estão sendo processadas: uma está sendo buscada, uma está sendo executada e uma armazena seus resultados. Isso é chamado de pipeline e possui neste exemplo três estágios. Os processadores modernos possuem dutos com mais de 15 estágios. No entanto, além disso, assim como a maioria das operações aritméticas, geralmente são executadas em um estágio (estou falando sobre a operação de adicionar 2 números pela ALU, não sobre a própria instrução - dependendo da arquitetura do processador, a instrução pode exigir mais ciclos para buscar argumentos da memória, executar condicionais, armazenar resultados na memória).
A duração de um ciclo é determinada pelo caminho crítico mais longo. Basicamente, é o tempo mais longo necessário para que algum estágio do pipline seja concluído. Se você deseja acelerar a CPU, precisa otimizar o caminho crítico. Se a redução do caminho crítico em si não for possível, ele poderá ser dividido em 2 estágios do pipeline, e agora você poderá sincronizar sua CPU com quase o dobro da frequência (supondo que não exista outro caminho crítico que o impeça de fazer isso). ) Mas isso vem com uma sobrecarga: você precisa inserir um registro entre os estágios do pipeline. O que significa que você não ganha velocidade 2x (o registro precisa de tempo para armazenar os dados) e complicou todo o design.
Já existem métodos bastante eficientes para executar a adição (por exemplo, carregadores com adição de olhais) e a adição não é um caminho crítico para a velocidade do processador; portanto, não faz sentido dividi-lo em vários ciclos.
Além disso, observe que, embora possa parecer complicado para você, no hardware as coisas podem ser feitas em paralelo muito rapidamente.
fonte
Os processadores têm clock de clock, portanto, mesmo que algumas instruções possam ser claramente feitas mais rapidamente que outras, elas podem levar o mesmo número de ciclos.
Você provavelmente descobrirá que o circuito necessário para transportar dados entre registradores e unidades de execução é significativamente mais complicado que os somadores.
Observe que a instrução MOV simples (registrar para registrar) faz ainda menos cálculos que a lógica bit a bit, mas ambos, MOV e ADD, geralmente levam um ciclo. Se o MOV pudesse ser feito duas vezes mais rápido, as CPUs teriam um clock duas vezes mais rápido e os ADDs teriam dois ciclos.
fonte
A adição é importante o suficiente para não esperar um bit de transporte percorrer um acumulador de 64 bits: o termo é um somador de carry-lookahead e eles são basicamente parte de CPUs de 8 bits (e suas ALUs) e superiores. De fato, os processadores modernos tendem a não precisar de muito mais tempo de execução para uma multiplicação completa: carry-lookahead é realmente uma ferramenta muito antiga (e comparativamente acessível) na caixa de ferramentas de um designer de processador.
fonte
lea
instrução shift + add ).Eu acho que seria difícil encontrar um processador que tivesse uma adição adicional de mais ciclos do que uma operação bit a bit. Em parte porque a maioria dos processadores deve realizar pelo menos uma adição por ciclo de instruções simplesmente para aumentar o contador do programa. Meras operações bit a bit não são tão úteis.
(Ciclo de instruções, não ciclo de clock - por exemplo, o 6502 realiza no mínimo dois ciclos de clock por instrução devido a não ter pipelines e a não ter um cache de instruções)
O conceito real que você pode estar perdendo é o do caminho crítico : dentro de um chip, a operação mais longa que pode ser executada em um ciclo determina, no nível do hardware, a rapidez com que o chip pode ter clock.
A exceção é a lógica assíncrona (raramente usada e pouco comercializada), que realmente é executada em velocidades diferentes, dependendo do tempo de propagação da lógica, da temperatura do dispositivo etc.
fonte
No nível do portão, você está certo de que é preciso mais trabalho para fazer acréscimos e, portanto, leva mais tempo. No entanto, esse custo é trivial o suficiente e não importa.
Processadores modernos são cronometrados. Você não pode executar instruções com nada além de múltiplos dessa taxa de clock. Se as taxas de clock fossem aumentadas, para maximizar a velocidade das operações bit a bit, você teria que gastar pelo menos 2 ciclos além disso. Muito desse tempo seria gasto esperando, porque você realmente não precisava dos 2 ciclos completos. Você só precisava de 1.1 (ou algum número assim). Agora, seu chip é mais lento que todos os outros no mercado.
Pior, o simples ato de adicionar ou executar operações bit a bit é apenas uma pequena parte do que está acontecendo durante um ciclo. Você precisa buscar / decodificar instruções dentro de um ciclo. Você deve poder executar operações de cache dentro de um ciclo. Muitas outras coisas acontecem na mesma escala de tempo da operação simples de adição ou de bits.
A solução, é claro, é desenvolver um pipeline extremamente profundo, dividindo essas tarefas em pequenas partes que se encaixam no pequeno tempo de ciclo definido por uma operação bit a bit. O Pentium 4 mostrou famosos os limites do pensamento nesses termos profundos do pipeline. Todos os tipos de problemas surgem. Em particular, a ramificação se torna notoriamente difícil porque você precisa liberar o pipeline depois de ter os dados para descobrir qual ramificação deve ser executada.
fonte
Os processadores modernos têm clock de clock: cada operação requer um número integral de ciclos de clock. Os projetistas do processador determinam a duração de um ciclo de clock. Existem duas considerações: Uma, a velocidade do hardware, por exemplo, medida como o atraso de um único gate NAND. Isso depende da tecnologia usada e de compensações como velocidade versus uso de energia. É independente do design do processador. Segundo, os projetistas decidem que a duração de um ciclo de clock é igual a n atrasos de um único gate NAND, onde n pode ser 10 ou 30 ou qualquer outro valor.
Essa opção n limita a complexidade das operações que podem ser processadas em um ciclo. Haverá operações que podem ser feitas em 16, mas não em 15 atrasos NAND. Portanto, escolher n = 16 significa que essa operação pode ser realizada em um ciclo, escolhendo n = 15 significa que não pode ser realizada.
Os projetistas escolherão n para que muitas operações importantes possam ser realizadas em um ou em dois ou três ciclos. n será escolhido localmente ideal: se você substituísse n por n-1, a maioria das operações seria um pouco mais rápida, mas algumas (aquelas que realmente precisam de todos os atrasos n na NAND) seriam mais lentas. Se poucas operações desacelerassem, para que a execução geral do programa fosse mais rápida, em média, você escolheria n-1. Você também pode ter escolhido n + 1. Isso torna a maioria das operações um pouco mais lentas, mas se você tiver muitas operações que não podem ser realizadas com n atrasos, mas que podem ser realizadas com n + 1 atrasos, isso tornaria o processador mais rápido.
Agora sua pergunta: adicionar e subtrair são operações tão comuns que você deseja poder executá-las em um único ciclo. Como resultado, não importa que AND, OR etc. possam executar mais rapidamente: eles ainda precisam desse ciclo. É claro que a unidade "calculando" AND, OR etc tem muito tempo para mexer os polegares, mas isso não pode ser ajudado.
Observe que não é apenas se uma operação pode ser realizada com n atrasos NAND ou não: uma adição, por exemplo, pode ser feita mais rapidamente sendo um pouco inteligente, ainda mais rápida por ser muito inteligente, ainda mais rápida, investindo quantidades extraordinárias de hardware e, finalmente, um processador pode ter uma mistura de circuitos muito rápidos, muito caros e um pouco mais lentos e mais baratos; portanto, há a possibilidade de fazer uma operação com rapidez suficiente, gastando mais dinheiro com isso.
Agora você pode tornar a velocidade do relógio tão alta / o ciclo tão curto que apenas as operações simples de bits são executadas em um ciclo e todo o resto em dois ou mais. Isso provavelmente atrasaria o processador. Para operações que levam dois ciclos, geralmente há sobrecarga para mover uma instrução incompleta de um ciclo para o próximo, portanto, dois ciclos não significa que você tem o dobro do tempo para execução. Então, para fazer a adição em dois ciclos, você não pode dobrar a velocidade do relógio.
fonte
Deixe-me corrigir algumas coisas que não foram mencionadas explicitamente em suas respostas existentes:
Isso é verdade. Rotular uma CPU como bit "XX" geralmente (nem sempre) significa que a maioria de suas estruturas comuns (larguras de registro, RAM endereçável etc.) tem tamanho de XX bits (geralmente "+/- 1" ou algo assim). Mas em relação à sua pergunta, você pode assumir com segurança que uma CPU com 32 bits ou 64 bits fará qualquer operação básica de bits em 32 ou 64 bits em tempo constante.
Esta conclusão não é necessariamente o caso. Especialmente CPUs com conjuntos de instruções avançados (google CISC vs. RISC) podem facilmente levar mais de um ciclo para comandos simples. Com a intercalação, mesmo comandos simples podem ser divididos em fetch-exec-store com 3 relógios (como exemplo).
Não, a adição de números inteiros é uma operação simples; subtração também. É muito fácil implementar adicionadores em hardware completo, e eles fazem suas coisas instantaneamente como operações básicas de bits.
Serão necessários 3-4 vezes mais transistores, mas em comparação com o quadro geral que é negligenciável.
Sim: a adição de números inteiros é uma operação bit a bit (com mais alguns bits que os outros, mas ainda assim). Não há necessidade de fazer nada em etapas, não há necessidade de algoritmos complicados, relógios ou qualquer outra coisa.
Se você deseja adicionar mais bits do que a arquitetura da CPU, você será penalizado por fazê-lo em etapas. Mas isso está em outro nível de complexidade (nível da linguagem de programação, não nível de montagem / código de máquina). Esse era um problema comum no passado (ou hoje em pequenas CPUs incorporadas). Para PCs etc., seus 32 ou 64 bits são suficientes para que os tipos de dados mais comuns comecem a se tornar um ponto discutível.
fonte
imul rax, rcx
possui latência 3c e taxa de transferência de 1 por 1c na família Intel Sandybridge e AMD Ryzen). Mesmo a multiplicação completa de 64 bits (produzindo o resultado de 128 bits em rdx: rax) tem a mesma latência e taxa de transferência, mas é implementada como 2 uops (que são executados em paralelo em portas diferentes). (Consulte agner.org/optimize para obter tabelas de instruções e um excelente guia de microarquitetura).uint32_t
valores. Isso ainda é relevante hoje para int64_t em destinos de 32 bits. O AVR é um microcontrolador RISC de 8 bits, portanto, números inteiros de 32 bits exigem 4 instruções: godbolt.org/g/wre0fM