Perché l'aggiunta è rapida come operazioni bit-saggio nei moderni processori?


73

So che le operazioni bit per bit sono così veloci sui processori moderni, perché possono operare su 32 o 64 bit in parallelo, quindi le operazioni bit per bit richiedono solo un ciclo di clock. Tuttavia, l'aggiunta è un'operazione complessa che consiste in almeno una e forse fino a una decina di operazioni bit-saggio, quindi ho naturalmente pensato che sarebbe 3-4 volte più lento. Sono stato sorpreso di vedere, dopo un semplice benchmark, che l'aggiunta è esattamente veloce come una qualsiasi delle operazioni bit-saggio (XOR, OR, E ecc.). Qualcuno può far luce su questo?




1
Sì, anche la moltiplicazione è stata piuttosto rapida nei miei test. Era solo circa 2 volte più lento dell'aggiunta, mentre la divisione era circa 30 volte (!) Più lenta.
Anonimo

Panoramica compatta degli additivi per
Franki,

Più elaborato: tesi di dottorato del dottor Jun Chen "Strutture a prefisso parallelo per binari e moduli {2n − 1, 2n, 2n + 1} adders
Franki

Risposte:


104

L'aggiunta è veloce perché i progettisti della CPU hanno inserito i circuiti necessari per renderlo veloce. Richiede significativamente più porte rispetto alle operazioni bit per bit, ma è abbastanza frequente che i progettisti di CPU hanno ritenuto che ne valesse la pena. Vedi https://en.wikipedia.org/wiki/Adder_(electronics) .

Entrambi possono essere resi abbastanza veloci da essere eseguiti in un singolo ciclo della CPU. Non sono altrettanto veloci - l'aggiunta richiede più porte e più latenza di un'operazione bit a bit - ma è abbastanza veloce che un processore può farlo in un ciclo di clock. Esiste un sovraccarico di latenza per istruzione per la decodifica e la logica di controllo delle istruzioni, e la latenza per ciò è significativamente maggiore della latenza per eseguire un'operazione bit a bit, quindi la differenza tra i due viene sommersa da quel sovraccarico. La risposta di AProgrammer e la risposta di Paul92 spiegano bene quegli effetti.


I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
DW

38

Ci sono molti aspetti

  • Il costo relativo di un'operazione bit a bit e un'aggiunta. Un sommatore ingenuo avrà una profondità di gate che dipende linearmente dalla larghezza della parola. Esistono approcci alternativi, più costosi in termini di porte, che riducono la profondità (IIRC la profondità quindi dipende logaritmicamente dalla larghezza della parola). Altri hanno fornito riferimenti per tali tecniche, sottolineo solo che la differenza è anche meno importante di quello che può sembrare solo considerando il costo dell'operazione a causa della necessità di una logica di controllo che aggiunge ritardi.

  • Quindi c'è il fatto che i processori sono di solito con clock (sono a conoscenza di alcuni progetti di ricerca o di clock con scopi speciali, ma non sono nemmeno sicuro che alcuni siano disponibili in commercio). Ciò significa che qualunque sia la velocità di un'operazione, ci vorrà un multiplo intero del ciclo di clock.

  • Infine ci sono le considerazioni micro-architettoniche: sei sicuro di misurare ciò che vuoi? Al giorno d'oggi, i processori tendono ad essere pipeline, multi-scalare, con esecuzione fuori ordine e quant'altro. Ciò significa che sono in grado di eseguire diverse istruzioni contemporaneamente, in varie fasi di completamento. Se si desidera dimostrare con le misurazioni che un'operazione richiede più tempo di un'altra, è necessario prendere in considerazione questi aspetti poiché il loro obiettivo è nascondere la differenza. Potresti avere lo stesso throughput per operazioni di addizione e bit per bit quando usi dati indipendenti, ma una misura della latenza o l'introduzione di dipendenze tra le operazioni potrebbe mostrare diversamente. E devi anche essere sicuro che il collo di bottiglia della tua misura sia nell'esecuzione e non ad esempio negli accessi alla memoria.


6
+1. Sì, la maggior parte dei processori ha un clock, ma alcune CPU senza clock sono disponibili in commercio.
David Cary,

2
Un'altra possibilità è che un processore possa memorizzare un registro a 64 bit come un pezzo a 16 bit e tre pezzi a 17 bit, in cui i bit extra di ciascun pezzo che contiene un rinvio vengono riportati dal basso. Un'aggiunta che è seguita da un'operazione bit per bit o un negozio potrebbe richiedere 1-2 cicli extra per propagare il carry, ma un'aggiunta che è seguita da un'altra aggiunta non lo farebbe. Inoltre, nel caso del "negozio", il tempo di propagazione aggiuntivo potrebbe ritardare le prestazioni del negozio, ma non sarebbe necessario che il codice lo "aspetti".
supercat,

3
@supercat Il Pentium 4 ha fatto qualcosa del genere, con un ALU a doppia velocità (rispetto al resto del processore) che avrebbe i 16 o 32 bit bassi pronti per un'operazione successiva un mezzo ciclo prima dei bit della metà superiore.
Jeffrey Bosboom,

2
sei sicuro di misurare ciò che vuoi? In questo caso, la conclusione dell'OP dalle misurazioni sembra essere corretta per la stragrande maggioranza delle CPU. L'aggiunta è così comune che le CPU superscalari hanno unità di aggiunta su tutte le porte di esecuzione e i booleani sono così economici da implementare (nel conteggio dei transistor) che sono presenti anche su tutte le porte. Quindi aggiungi e booleani hanno quasi sempre lo stesso throughput (ad esempio 4 per clock in Intel Haswell).
Peter Cordes,

2
L'aggiunta di numeri interi SIMD ha spesso una velocità effettiva inferiore rispetto a quella booleana SIMD, anche se di solito hanno la stessa latenza. Le CPU Intel da PentiumII a Broadwell possono eseguire solo add-vector-int (ad es. paddw) A 2 per clock, ma booleane (come pand) a 3 per clock. (Skylake mette un sommatore vettoriale su tutte e tre le porte di esecuzione vettoriale.)
Peter Cordes,

24

Le CPU funzionano a cicli. Ad ogni ciclo, succede qualcosa. Di solito, un'istruzione impiega più cicli per essere eseguita, ma più istruzioni vengono eseguite contemporaneamente, in stati diversi.

Ad esempio, un semplice processore potrebbe avere 3 passaggi per ciascuna istruzione: recupero, esecuzione e memorizzazione. In qualsiasi momento vengono elaborate 3 istruzioni: una viene recuperata, una viene eseguita e una memorizza i risultati. Questa si chiama pipeline e ha in questo esempio 3 fasi. I processori moderni hanno condutture con oltre 15 fasi. Tuttavia, inoltre, così come la maggior parte delle operazioni aritmetiche, di solito vengono eseguite in una fase (sto parlando dell'operazione di aggiungere 2 numeri dall'ALU, non dell'istruzione stessa - a seconda dell'architettura del processore, l'istruzione potrebbe richiedere più cicli per recuperare argomenti dalla memoria, eseguire condizionali, archiviare risultati in memoria).

La durata di un ciclo è determinata dal percorso critico più lungo. Fondamentalmente, è il tempo più lungo necessario per completare una fase della pipeline. Se si desidera rendere più veloce la CPU, è necessario ottimizzare il percorso critico. Se la riduzione del percorso critico di per sé non è possibile, può essere suddivisa in 2 fasi della pipeline e ora è possibile eseguire il clock della CPU a quasi il doppio della frequenza (presupponendo che non vi sia un altro percorso critico che ti impedisca di farlo) ). Ma questo comporta un sovraccarico: è necessario inserire un registro tra le fasi della pipeline. Ciò significa che non si ottiene realmente una velocità pari a 2x (il registro richiede tempo per memorizzare i dati) e si è complicato l'intero progetto.

Esistono già metodi abbastanza efficienti per eseguire l'addizione (ad esempio portare addizionatori lookahead) e l'addizione non è un percorso critico per la velocità del processore, quindi non ha senso suddividerla in più cicli.

Inoltre, nota che mentre può sembrare complicato per te, in hardware le cose possono essere fatte in parallelo molto velocemente.


3
Il grande sovraccarico da condotte più lunghe sono più cicli per recuperare da un errore di filiale! Al giorno d'oggi, spendere transistor per bufferizzare i dati tra le fasi è minore. Anche una semplice CPU pipeline deve essere recuperata / decodificata prima delle istruzioni che stanno effettivamente eseguendo. Se la CPU scopre che il front-end stava lavorando su un codice errato perché un ramo ha seguito una strada diversa da quella prevista (o qualche altra speculazione errata), deve buttare via quel lavoro e iniziare dall'istruzione corretta. Le cose peggiorano solo con CPU super-ordinate fuori servizio che possono avere molti insn in volo.
Peter Cordes,

12

I processori sono sincronizzati, quindi anche se alcune istruzioni possono essere chiaramente eseguite più velocemente di altre, potrebbero richiedere lo stesso numero di cicli.

Probabilmente scoprirai che i circuiti necessari per trasportare i dati tra i registri e le unità di esecuzione sono significativamente più complicati degli additivi.

Si noti che la semplice istruzione MOV (da registro a registro) esegue un calcolo persino inferiore rispetto alla logica bit a bit, tuttavia sia MOV che ADD richiedono di solito un ciclo. Se MOV fosse realizzato due volte più velocemente, le CPU verrebbero sincronizzate due volte più velocemente e gli ADD sarebbero due cicli.


I commenti non sono per una discussione estesa; questa conversazione è stata spostata in chat .
Gilles 'SO- smetti di essere malvagio'

1
Sintesi della discussione: alcune CPU fuori servizio gestiscono MOV specialmente con la ridenominazione del registro, con una latenza pari a zero. Vedi Il MOV di x86 può davvero essere "gratuito"? Perché non riesco a riprodurlo affatto? per i dettagli completi di quanto costa MOV.
Peter Cordes,

12

L'aggiunta è abbastanza importante da non aspettare che un bit di riporto si propaghi attraverso un accumulatore a 64 bit: il termine è un sommatore riporto e fanno praticamente parte delle CPU a 8 bit (e delle loro ALU) e verso l'alto. In effetti, i processori moderni tendono a non aver bisogno di molto più tempo di esecuzione per una moltiplicazione completa: carry-lookahead è in realtà uno strumento davvero vecchio (e relativamente conveniente) nella cassetta degli attrezzi di un progettista di processori.


La moltiplicazione dei numeri interi ha una latenza decisamente maggiore e una velocità effettiva inferiore rispetto a ADD su x86. Ma è incredibilmente veloce considerando quanti additivi ci vogliono per costruire un moltiplicatore veloce: ad es. Su Intel da Nehalem e AMD da Ryzen, la moltiplicazione intera scalare 8/16/32/64 bit è una latenza di 3 cicli, con una velocità di trasmissione per 1c (un'unità di esecuzione con pipeline completa). Questo fa schifo rispetto al throughput ADD di 3 o 4 per clock, ma è sorprendente rispetto alla latenza IMUL a 9 cicli in Intel Pentium P5. Le cose sono simili per SIMD: la moltiplicazione vector-int è una latenza più elevata e una velocità di trasmissione inferiore rispetto a quella aggiunta, ma comunque veloce.
Peter Cordes,

Quindi sì, moltiplicare era molto più costoso rispetto ad altre istruzioni di quanto non sia ora. Evitarlo ad un costo di più di 2 istruzioni di solito non ne vale la pena, e talvolta non vale nemmeno un sostituto di 2 istruzioni (ad es. Con un turno + aggiungi leaistruzione).
Peter Cordes,

9

Penso che ti sarebbe difficile trovare un processore che ha richiesto più cicli che un'operazione bitwise. In parte perché la maggior parte dei processori deve eseguire almeno un'aggiunta per ciclo di istruzioni semplicemente per incrementare il contatore del programma. Le semplici operazioni bit per bit non sono poi così utili.

(Ciclo di istruzioni, non ciclo di clock - ad es. Il 6502 richiede un minimo di due cicli di clock per istruzione poiché non è pipeline e non dispone di una cache di istruzioni)

Il vero concetto che potresti perdere è quello del percorso critico : all'interno di un chip, l'operazione più lunga che può essere eseguita in un ciclo determina, a livello hardware, la velocità con cui il chip può essere sincronizzato.

L'eccezione a questa è la logica asincrona (usata raramente e poco commercializzata), che esegue davvero a velocità diverse a seconda del tempo di propagazione della logica, della temperatura del dispositivo ecc.


Non si tratta di operazioni bit a bit controllabili dall'utente, ma alcune istruzioni sull'8086 (ad es. Azzeramento del flag di interrupt ) hanno richiesto meno cicli di un'aggiunta di numeri interi. Più astrattamente, un sistema RISC in cui tutte le istruzioni hanno una parola in termini di dimensioni potrebbe utilizzare un semplice contatore binario per PC, che sarebbe un circuito molto più veloce di un sommatore generico.
Segna

L'aggiunta sul contatore del programma tende ad essere molto semplice rispetto a un'istruzione aritmetica aggiuntiva, poiché uno degli operandi è piccolo (o una dimensione dell'istruzione o un offset di salto relativo che è anche di dimensioni limitate)
Ben Voigt

6502 è stato pipeline - ha letto il primo byte dell'istruzione successiva durante l'ultimo ciclo di quello precedente. In caso contrario, recuperare / decodificare / eseguire sarebbe stato di almeno tre cicli.
gnasher729,

8

A livello di gate, hai ragione nel dire che ci vuole più lavoro per fare l'addizione e quindi impiega più tempo. Tuttavia, quel costo è sufficientemente banale che non importa.

Processori moderni sono cronometrati. Non è possibile eseguire istruzioni in alcun modo tranne i multipli di questa frequenza di clock. Se le frequenze di clock venissero spinte più in alto, per massimizzare la velocità delle operazioni bit per bit, dovresti impiegare almeno 2 cicli in aggiunta. Gran parte di questo tempo sarebbe trascorso in attesa perché in realtà non hai bisogno di 2 cicli completi. Avevi solo bisogno di 1.1 (o un numero simile). Ora il tuo chip si aggiunge più lentamente di chiunque altro sul mercato.

Peggio ancora, il semplice atto di aggiungere o fare operazioni bit per bit è solo una piccola parte di ciò che accade durante un ciclo. Devi essere in grado di recuperare / decodificare le istruzioni all'interno di un ciclo. Devi essere in grado di eseguire operazioni cache all'interno di un ciclo. Molte altre cose stanno accadendo sulla stessa scala temporale della semplice aggiunta o operazione bit a bit.

La soluzione, ovviamente, è quella di sviluppare una pipeline molto profonda, suddividendo questi compiti in minuscole parti che si adattano al minuscolo tempo di ciclo definito da un'operazione bit a bit. Il Pentium 4 ha mostrato notoriamente i limiti del pensiero in questi termini profondi della pipeline. Sorgono tutti i tipi di problemi. In particolare la ramificazione diventa notoriamente difficile perché devi svuotare la pipeline una volta che hai i dati per capire quale ramo prendere.


7

I processori moderni sono sincronizzati: ogni operazione richiede un numero integrale di cicli di clock. I progettisti del processore determinano la durata di un ciclo di clock. Vi sono due considerazioni: una, la velocità dell'hardware, ad esempio misurata come ritardo di una singola porta NAND. Ciò dipende dalla tecnologia utilizzata e da compromessi come la velocità rispetto all'utilizzo di energia. È indipendente dal design del processore. Due, i progettisti decidono che la lunghezza di un ciclo di clock equivale a n ritardi di una singola porta NAND, dove n potrebbe essere 10, o 30 o qualsiasi altro valore.

Questa scelta n limita la complessità delle operazioni che possono essere elaborate in un ciclo. Ci saranno operazioni che possono essere eseguite in 16 ma non in 15 ritardi NAND. Quindi scegliere n = 16 significa che un'operazione del genere può essere eseguita in un ciclo, scegliendo n = 15 significa che non può essere eseguita.

I progettisti sceglieranno n in modo che molte operazioni importanti possano essere quasi eseguite in uno o forse due o tre cicli. n verrà scelto localmente ottimale: se si sostituisse n con n-1, la maggior parte delle operazioni sarebbe un po 'più veloce, ma alcune (quelle che necessitano davvero dei ritardi NAND completi) sarebbero più lente. Se poche operazioni rallentassero, in modo che l'esecuzione complessiva del programma sia mediamente più veloce, allora avresti scelto n-1. Potresti anche aver scelto n + 1. Ciò rende la maggior parte delle operazioni un po 'più lente, ma se si hanno molte operazioni che non possono essere eseguite entro n ritardi ma che possono essere eseguite entro n + 1 ritardi, il processore sarebbe più veloce.

Ora la tua domanda: aggiungere e sottrarre sono operazioni così comuni che vuoi essere in grado di eseguirle in un singolo ciclo. Di conseguenza, non importa che AND, OR ecc. Possano essere eseguiti più velocemente: hanno ancora bisogno di quel ciclo. Ovviamente l'unità "calcolatrice" AND, OR etc ha molto tempo per modificare i pollici, ma non è possibile evitarlo.

Nota che non è solo se un'operazione può essere eseguita entro n ritardi o NAND: un'aggiunta, ad esempio, può essere resa più veloce essendo un po 'intelligente, ancora più veloce essendo molto intelligente, ancora un po' più veloce investendo quantità straordinarie di hardware e alla fine un processore può avere una miscela di circuiti molto veloci molto costosi e un po 'più lenti ed economici, quindi c'è la possibilità di fare un'operazione abbastanza velocemente spendendo più soldi su di essa.

Ora si potrebbe fare la velocità di clock così in alto / il ciclo così breve che solo le operazioni po 'semplice esecuzione in un ciclo e tutto il resto in due o più. Molto probabilmente questo rallenterebbe il processore. Per le operazioni che richiedono due cicli, di solito c'è un sovraccarico per spostare un'istruzione incompleta da un ciclo all'altro, quindi due cicli non significano che hai il doppio del tempo per l'esecuzione. Quindi, per fare l'aggiunta in due cicli, non è possibile raddoppiare la velocità di clock.


6

Consentitemi di correggere alcune cose che non sono state menzionate esplicitamente nelle risposte esistenti:

So che le operazioni bit per bit sono così veloci sui processori moderni, perché possono operare su 32 o 64 bit in parallelo,

Questo è vero. L'etichettatura di una CPU come bit "XX" di solito (non sempre) significa che la maggior parte delle sue strutture comuni (larghezze di registro, RAM indirizzabile ecc.) Hanno dimensioni di XX bit (spesso "+/- 1" o meno). Ma per quanto riguarda la tua domanda, puoi tranquillamente supporre che una CPU a 32 bit o 64 bit eseguirà qualsiasi operazione di bit di base su 32 o 64 bit in tempo costante.

quindi le operazioni bit a bit richiedono solo un ciclo di clock.

Questa conclusione non è necessariamente il caso. Soprattutto le CPU con ricchi set di istruzioni (google CISC vs. RISC) possono facilmente richiedere più di un ciclo anche per comandi semplici. Con l'interlacciamento, anche i comandi semplici potrebbero essere suddivisi in fetch-exec-store con 3 orologi (come esempio).

Tuttavia la dipendenza è un'operazione complessa

No, l'aggiunta di numeri interi è un'operazione semplice; sottrazione pure. È molto facile implementare gli additivi in ​​tutto l'hardware e fanno le loro cose istantaneamente come le operazioni di bit base.

che consiste in almeno una e forse fino a una dozzina di operazioni bit a bit, quindi naturalmente ho pensato che sarebbe 3-4 volte più lento.

Ci vorranno 3-4 volte più transistor, ma rispetto al quadro generale che è trascurabile.

Sono stato sorpreso di vedere dopo un semplice benchmark che l'aggiunta è esattamente veloce come una qualsiasi delle operazioni bit a bit (XOR, OR, AND ecc.). Qualcuno può far luce su questo?

Sì: l'aggiunta di numeri interi è un'operazione a bit (con qualche bit in più rispetto agli altri, ma comunque). Non è necessario fare nulla per gradi, non sono necessari algoritmi complicati, orologi o altro.

Se desideri aggiungere più bit dell'architettura della tua CPU, dovrai sostenere una penalità di doverlo fare in più fasi. Ma questo è su un altro livello di complessità (livello del linguaggio di programmazione, non livello di assembly / codice macchina). Questo era un problema comune in passato (o oggi su piccole CPU integrate). Per PC, ecc., I loro 32 o 64 bit sono sufficienti per i tipi di dati più comuni per iniziare a diventare un punto controverso.


È interessante notare che ridurre il costo del tempo di addizione da O (N) a O (sqrt (N)) non aumenta in modo significativo il numero richiesto di transistor o la complessità del routing (ogni fase deve solo consentire a un filo di trasporto di intrufolarsi dal basso e ci devono essere fasi di fusione extra sqrt (N). Il costo del tempo può essere ridotto a O (lgN) a un costo di transistor O (lgN), ma in molti casi può essere utile elaborare qualcosa come un 64- aggiunta di bit, ad esempio otto aggiunte a 8 bit (utilizzando l'inoltro sqrtN) unite con tre livelli di logica di fusione, anziché come 64 aggiunte a 1 bit con sei livelli di fusione.
supercat

Sì, gli additivi sono abbastanza semplici. Ciò che è davvero impressionante sono le moderne CPU x86 con un moltiplicatore intero a 64 bit di latenza a 3 cicli completamente pipeline . (ad esempio, imul rax, rcxha una latenza di 3c e una velocità effettiva di 1c sulla famiglia Intel Sandybridge e AMD Ryzen). Anche la moltiplicazione a 64 bit (producendo il risultato a 128 bit in rdx: rax) ha la stessa latenza e throughput, ma è implementata come 2 uops (che funzionano in parallelo su porte diverse). (Vedi agner.org/optimize per le tabelle di istruzioni e un'eccellente guida al microarca).
Peter Cordes,

[add-with-carry] è su un altro livello di complessità (livello del linguaggio di programmazione, non livello di assemblaggio / codice macchina . Dipende dalla lingua. Il compilatore CA destinato a una CPU a 16 bit deve emettere add / adc per te quando viene compilato aggiunta di due uint32_tvalori, ancora oggi rilevante per int64_t su target a 32 bit. AVR è un microcontrollore RISC a 8 bit, quindi gli interi a 32 bit richiedono 4 istruzioni: godbolt.org/g/wre0fM
Peter Cordes

Sì, @PeterCordes, questo è ciò che intendevo, ho chiarito un po 'la mia frase.
AnoE,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.