Quanto deve essere bravo un programmatore a tutto tondo con operazioni bit-saggio? [chiuso]


34

Ho sfogliato un po 'di codice OpenJDK di recente e ho trovato alcuni intriganti pezzi di codice che hanno a che fare con operazioni bit-saggio . Ho anche posto una domanda al riguardo su StackOverflow.

Un altro esempio che illustra il punto:

 1141       public static int bitCount(int i) {
 1142           // HD, Figure 5-2
 1143           i = i - ((i >>> 1) & 0x55555555);
 1144           i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);
 1145           i = (i + (i >>> 4)) & 0x0f0f0f0f;
 1146           i = i + (i >>> 8);
 1147           i = i + (i >>> 16);
 1148           return i & 0x3f;
 1149       }

Questo codice può essere trovato nella classe Integer .

Non posso fare a meno di sentirmi stupido quando guardo questo. Mi sono persa una o due lezioni al college o non è qualcosa che dovrei ottenere ? Posso fare semplici operazioni bit-saggio (come ANDing, ORing, XORing, shifting), ma dai, come si fa a inventare un codice come quello sopra?

Quanto deve essere bravo un programmatore a tutto tondo con operazioni bit-saggio?

Una nota a margine ... Quello che mi preoccupa è che la persona che ha risposto alla mia domanda su StackOverflow ha risposto in pochi minuti. Se fosse riuscito a farlo, perché avrei semplicemente fissato come un cervo nei fari?


4
Che tipo di lavoro di sviluppo fai (o vuoi fare, se non lo stai facendo in questo momento)? Non vedo che ciò sia utile nello sviluppo web, ma ho visto un sacco di operazioni bit a bit nei sistemi integrati.
Thomas Owens

26
Se sto assumendo qualcuno per fare lo sviluppo dell'interfaccia utente o lo sviluppo web, la manipolazione dei bit non è qualcosa di cui mi chiederei perché, è probabile, non lo vedranno mai. Tuttavia, mi aspetterei che qualcuno che lavora con i protocolli di rete, i sistemi integrati e il driver del dispositivo funzioni con esso.
Thomas Owens

11
Cosa diavolo è >>>come operatore?
DeadMG,

10
@DeadMG: spostamento a destra senza segno. download.oracle.com/javase/tutorial/java/nutsandbolts/op3.html
c_maker

3
// HD, Figure 5-2sarebbe la prima cosa che darei un'occhiata. Secondo i commenti all'inizio del file, HDè Henry S. Warren, Jr.'s Hacker's Delight.
schnaader,

Risposte:


38

Direi che come sviluppatore a tutto tondo, devi capire gli operatori e le operazioni bit a bit.

Quindi, come minimo, dovresti essere in grado di capire il codice sopra dopo un po 'di riflessione.

Le operazioni bit a bit tendono ad essere di livello piuttosto basso, quindi se lavori su siti Web e software LOB, è improbabile che le usi molto.

Come altre cose, se non le utilizzi molto, non ne parleresti.

Quindi, non dovresti preoccuparti che qualcuno sia in grado di capirlo molto rapidamente, poiché (probabilmente) lavorano molto con questo tipo di codice. Possibilmente scrivere codice OS, codice driver o altra manipolazione di bit complicata.


1
+1: Le operazioni bit a bit sono un po 'di conoscenza importante (nessun gioco di parole previsto) per qualsiasi sviluppatore, ma ora sono davvero davvero cruciali in situazioni specifiche. Se non li hai mai incontrati tutti i giorni, allora avere una conoscenza generale è meglio che schiavizzarli. Mantieni libero lo spazio del cervello.
Nicholas Smith,

Dovresti anche capire quando li useresti e non rifuggire dal loro uso se sono la soluzione corretta per il problema in questione.
user606723,

Per aggiungere al commento di @ user606723 - in realtà ci sono alcuni posti in cui vengono solitamente utilizzate cose bit per bit e che sono più o meno comunemente riscontrate - hashing (e cose ad esso correlate) ed estrarre / impostare particolari colori di RGB se sono memorizzati in un int. Ad esempio, le informazioni sulla CPU possono essere lette controllando i bit flag restituiti da un registro specifico, ma ciò implica asm e di solito ha wrapper di livello superiore se necessario.
TC1,

36

Se si capisce come risolvere problemi come "determinare se sono impostati i bit 3 e 8", "cancellare il bit 5" o "trovare il valore intero rappresentato dai bit 7-12", si ha abbastanza comprensione degli operatori bit a bit per controllare la Can Casella Twiddle Bits nell'elenco di controllo "ben arrotondato".

Il tuo esempio proviene da Hacker's Delight , una raccolta di algoritmi ad alte prestazioni per la manipolazione di piccoli frammenti di dati come numeri interi. Chiunque abbia scritto quel codice in origine non lo ha semplicemente sputato in cinque minuti; la storia dietro è più probabile che ci fosse la necessità di un modo rapido e senza ramificazioni per contare i bit e l'autore ha avuto un po 'di tempo da dedicare a fissare stringhe di bit e inventare un modo per risolvere il problema. Nessuno capirà come funziona a prima vista se non l'hanno visto prima. Con una solida conoscenza delle basi bit per bit e un po 'di tempo impiegato a sperimentare il codice, probabilmente potresti capire come fa quello che fa.

Anche se non capisci questi algoritmi, il solo sapere che esistono aumenta la tua "rotondità" perché quando arriva il momento di affrontare, diciamo, il conteggio dei bit ad alte prestazioni, sai cosa studiare. Nel mondo pre-Google, è stato molto più difficile scoprire queste cose; ora sono i tasti premuti.

L'utente che ha risposto alla tua domanda SO potrebbe aver già visto il problema o ha studiato l'hash. Scrivilo e chiedi.


+1 su almeno essere consapevoli di queste cose. È una buona cosa sapere un po 'di più. Se le persone del settore iniziano a parlare di cose del genere, non vuoi essere il tipo nella stanza che non ha il minimo indizio di cosa si sta discutendo.
maple_shaft

3
+1 per la risoluzione dell'abbreviazione "HD" nel commento sopra riportato.
Péter Török,

Adoro questo genere di cose e ho appena ordinato il libro HD. Grazie per il riferimento.
Tcrosley,

8

Dal tuo esempio ci sono alcune cose che dovresti assolutamente sapere senza pensarci davvero.

1143 i = i - ((i >>> 1) & 0x55555555);

Dovresti riconoscere lo schema di bit 0x555 ... come uno schema di bit alternato 0101 0101 0101 e che gli operatori lo stanno compensando di 1 bit (a destra), e che è un'operazione di mascheramento (e che cosa significa mascheramento).

1144 i = (i & 0x33333333) + ((i >>> 2) & 0x33333333);

Ancora una volta un modello, questo è 0011 0011 0011. Inoltre, questa volta ne sta cambiando due e mascherando di nuovo. lo spostamento e il mascheramento stanno seguendo uno schema che dovresti riconoscere ...

1145 i = (i + (i >>> 4)) & 0x0f0f0f0f;

il modello si solidifica. Questa volta sono 00001111 00001111 e, ovviamente, lo stiamo spostando 4 questa volta. ogni volta che ci spostiamo dalle dimensioni della maschera.

1148 ritorno i & 0x3f;

un altro modello di bit, 3f è un blocco di zeri seguito da un blocco più grande di quelli.

Tutte queste cose dovrebbero essere ovvie a colpo d'occhio se sei "ben arrotondato". Anche se non pensi mai che lo userai, probabilmente perderai alcune opportunità per semplificare notevolmente il tuo codice se non lo conosci.

Anche in un linguaggio di livello superiore, i bit patter vengono utilizzati per memorizzare MOLTE quantità maggiori di dati in campi più piccoli. Questo è il motivo per cui vedi sempre limiti di 127/8, 63/4 e 255/6 nei giochi, è perché devi conservare così tante cose che senza impacchettare i campi sarai costretto a usare fino a dieci volte il quantità di memoria. (Bene, il massimo sarebbe se avessi bisogno di archiviare un gran numero di booleani in un array, potresti risparmiare 32-64 volte la quantità di memoria che utilizzeresti se non ci pensassi - la maggior parte delle lingue implementano i booleani come una parola che spesso sarà di 32 bit. Coloro che non si sentono a proprio agio a questo livello resisteranno alle opportunità di archiviare dati come questo semplicemente perché hanno paura dell'ignoto.

Inoltre eviteranno cose come l'analisi manuale dei pacchetti consegnati sulla rete in un formato compresso - qualcosa che è banale se non hai paura. Ciò potrebbe richiedere un gioco che richiede un pacchetto da 1k fino a richiedere 200 byte, il pacchetto più piccolo scorrerà attraverso la rete in modo più efficiente e ridurrà la latenza e consentirà velocità di interazione più elevate (che potrebbero abilitare intere nuove modalità di gioco per un gioco).


5

Mi è capitato di riconoscere il codice perché l'ho visto prima nel software per manipolare i frame video. Se lavorassi regolarmente con cose come CODEC audio e video, protocolli di rete o registri di chip, vedresti molte operazioni bit per bit e diventerebbe una seconda natura per te.

Non dovresti sentirti male se il tuo lavoro non coincide molto spesso con quei domini. Conosco bene le operazioni bit per bit, ma rallento molto lentamente nelle rare occasioni in cui ho bisogno di scrivere una GUI, a causa di tutte le stranezze con layout, ponderazione ed espansione e tali che sono sicuro che sono una seconda natura per gli altri. I tuoi punti di forza sono ovunque tu abbia più esperienza.


4

le cose principali di cui dovresti essere a conoscenza sono il modo in cui sono rappresentati gli interi (in generale un bitvector a lunghezza fissa in cui la lunghezza dipende dalla piattaforma) e quali operazioni sono disponibili su di essi

le principali operazioni aritmetiche + - * / %possono essere comprese senza la necessità di capirlo anche se può essere utile per le micro-ottimizzazioni (anche se la maggior parte delle volte il compilatore sarà in grado di occuparsene per te)

il set di manipolazione dei bit | & ~ ^ << >> >>>richiede almeno una comprensione passeggera per poterli usare

tuttavia la maggior parte delle volte li userete solo per passare flag di bit a un metodo come ORing insieme e passare un int e quindi ANDestrarre le impostazioni è più leggibile rispetto al passaggio di più (fino a 32) booleani in un lungo elenco di parametri e consente le possibili bandiere da cambiare senza cambiare l'interfaccia

per non parlare dei booleani sono generalmente tenuti separatamente in byte o ints invece di raggrupparli come fanno le bandiere


per quanto riguarda lo snippet di codice esegue un conteggio parallelo dei bit, ciò consente l'esecuzione dell'algoritmo in O(log(n))cui n è il numero di bit anziché il ciclo ingenuo che èO(n)

il primo passo è il più difficile da capire, ma se si inizia dal setup che deve sostituire le sequenze di bit 0b00a 0b00, 0b01a 0b01, 0b10a 0b01e 0b11per 0b10diventa più facile da seguire

quindi per il primo passo i - ((i >>> 1) & 0x55555555)se prendiamo iper essere uguali a 0b00_01_10_11allora l'output di questo dovrebbe essere0b00_01_01_10

(nota che 0x5è uguale a 0b0101)

iuf prendiamo i = 0b00_01_10_11questo significa che 0b00_01_01_10 - (0b00_00_11_01 & 0b01_01_01_01)è ciò 0b00_01_10_11 - 0b00_00_01_01che a sua volta diventa0b00_01_01_10

avrebbero potuto fare (i & 0x55555555) + ((i >>> 1) & 0x55555555)per lo stesso risultato ma questa è 1 operazione aggiuntiva

le seguenti fasi sono simili


4
La qualità più importante di questo codice è che è privo di diramazioni, il che probabilmente offre vantaggi ancora maggiori rispetto alla riduzione della complessità.
Simon Richter,

3

Tutti dovrebbero comprendere le operazioni di base in bit. È la composizione delle operazioni di base per eseguire le attività in un modo ottimizzato e robusto che richiede molta pratica.

Coloro che lavorano quotidianamente con la manipolazione dei bit (come le persone incorporate), ovviamente, svilupperanno una forte intuizione e una bella borsa di trucchi.

Quante abilità dovrebbe avere un programmatore che non fa cose di basso livello con una manipolazione saggia? Abbastanza per riuscire a sederti con una strofa come quella che hai incollato e lavorarci lentamente come se fosse un rompicapo o un rompicapo.

Allo stesso modo, direi che un programmatore incorporato dovrebbe capire tanto su http quanto un web dev capisce sulla manipolazione bit-saggia. In altre parole, è "OK" non essere un mago se non lo si utilizza sempre.


3
In realtà in alcuni casi un programmatore incorporato deve capire di più su http di uno sviluppatore web (io faccio entrambe le cose). Facendo lo sviluppo web, di solito puoi contare su un qualche tipo di framework. Come sviluppatore incorporato che lavora con dispositivi connessi a Internet, ho dovuto codificare uno stack http da zero.
Tcrosley,

@tremamente, sì, hai assolutamente ragione. Forse un esempio migliore di "http" sarebbe stato qualcosa come "ORM" o "JEE". Il punto principale è che generalmente non si può avere padronanza su alcuni argomenti a meno che non li pratichino regolarmente.
Angelo,

Sono d'accordo e non ho mai avuto a che fare con ORM o JEE (solo JME quando era chiamato J2ME).
Tcrosley,


3

Quanto sono difficili da interpretare gli operatori bit per bit?

Programma i sistemi integrati. Ho praticato molto queste cose. La tua domanda collegata sulle mappe hash con il codice

static int hash(int h) {
   // This function ensures that hashCodes that differ only by
   // constant multiples at each bit position have a bounded
   // number of collisions (approximately 8 at default load factor).
   h ^= (h >>> 20) ^ (h >>> 12);
   return h ^ (h >>> 7) ^ (h >>> 4);
}

aveva perfettamente senso per me il tempo necessario per dettare ad alta voce il codice. Gli eventi descritti in bitCountsono immediatamente chiari, ma ci vuole un minuto per capire perché conta effettivamente i bit. I commenti sarebbero grandi, però, e renderebbero la comprensione di ciò che il codice fa solo leggermente più difficile del problema di hash.

È importante distinguere tra leggere e comprendere il codice. Sono in grado di interpretare il bitCountcodice e leggere ciò che fa, ma provare perché funziona o anche se funziona richiederebbe un minuto. C'è una differenza tra la capacità di leggere il codice senza problemi e la capacità di capire perché il codice è così com'è. Alcuni algoritmi sono semplicemente difficili. La cosa del hashCodice aveva un senso, ma il commento ha spiegato il motivo per il quale era stato fatto. Non scoraggiarti se una funzione che utilizza operatori bit per bit è difficile da capire, sono spesso usati per fare cose matematiche difficili che sarebbero difficili a prescindere dal formato.

Un'analogia

Sono abituato a queste cose. Un argomento a cui non sono abituato è regex. Di tanto in tanto mi occupo di costruire script, ma mai nel lavoro di sviluppo quotidiano.

So usare i seguenti elementi di una regex:

  • [] classi di caratteri
  • I *, .e +caratteri jolly
  • L'inizio della stringa ^e la fine della stringa$
  • Le classi di caratteri \ d, \ w e \ s
  • La bandiera / g

Questo è sufficiente per creare query semplici e molte delle query che vedo non si allontanano molto da questo.

Tutto ciò che non è in questo elenco, raggiungo un cheat sheet. Qualunque cosa, cioè, tranne {}e ()- Il cheat sheet non sarà sufficiente. So abbastanza di questi ragazzi per sapere che avrò bisogno di una lavagna, un manuale di riferimento e forse un collega. Puoi racchiudere alcuni pazzi algoritmi in poche righe di regex.

Per progettare una regex che richiede o suggerisce qualcosa che non è nella mia lista di elementi noti, elencherò tutte le classi di input che mi aspetto di riconoscere e le inserirò in una suite di test. Costruirò la regex lentamente e in modo incrementale, con molti passaggi intermittenti, e commetterò questi passaggi per il controllo del codice sorgente e / o li lascerò in un commento in modo che io possa capire cosa dovrebbe succedere in seguito quando si rompe. Se è nel codice di produzione, mi assicurerò che venga esaminato da qualcuno con più esperienza.

È qui che ci si trova con operatori bit per bit?

Quindi vuoi essere ben arrotondato?

Secondo la mia stima, se sei in grado di interpretare il codice come questo estraendo un pezzo di carta o andando alla lavagna ed eseguendo le operazioni manualmente, ti qualifichi come arrotondato. Per qualificarti come un buon programmatore a tutto tondo nell'area delle operazioni bit a bit, dovresti essere in grado di fare quattro cose:

  1. Essere in grado di leggere e scrivere le operazioni comuni in modo fluido
    Per un programmatore di applicazioni, le operazioni comuni con operatori bit a bit includono gli operatori di base di |e &per impostare e cancellare flag. Questo dovrebbe essere facile. Dovresti essere in grado di leggere e scrivere cose come

    open('file', O_WRONLY | O_APPEND | O_CREAT );
    // Use an OR operator ^ here and ^ here to set multiple flags
    

    senza rallentare (supponendo che tu sappia cosa significano le bandiere ).

  2. Essere in grado di leggere operazioni più complesse con alcuni lavori
    Contare i bit molto velocemente nel tempo O (log (n)) senza rami, assicurando che il numero di collisioni negli hashCode possa differire di un importo limitato e analizzare indirizzi e-mail , numeri di telefono o HTML con regex sono problemi difficili. È ragionevole per chiunque non sia un esperto in queste aree raggiungere la lavagna, è irragionevole non essere in grado di iniziare a lavorare per capire.

  3. Essere in grado di scrivere alcuni algoritmi complessi con molto lavoro
    Se non sei un esperto, non dovresti aspettarti di essere in grado di fare cose complesse e difficili. Tuttavia, un buon programmatore dovrebbe essere in grado di farlo lavorando continuamente. Fallo abbastanza e presto diventerai un esperto :)


2

Se sei andato in un'università decente, avresti dovuto seguire un corso di matematica discreta. Avresti imparato binari aritmetici e logici binari, ottali ed esadecimali.

In quella nota è normale sentirsi confuso da ciò, se è una consolazione per te dato che scrivo principalmente applicazioni web, ho raramente bisogno di guardare o scrivere codice come questo, ma dal momento che capisco l'aritmetica binaria e il comportamento degli operatori bit a bit Alla fine riesco a capire cosa sta succedendo qui dato abbastanza tempo.


2

Come programmatore di telefoni cellulari ho dovuto affrontare questo genere di cose. È ragionevolmente comune dove il dispositivo non ha molta memoria o dove la velocità di trasmissione è importante. In entrambi i casi, si cerca di raggruppare quante più informazioni possibili in pochi byte.

Non ricordo di aver usato gli operatori bit per bit in circa 5 anni di PHP (forse sono solo io), non in 10 anni o giù di lì per la programmazione di Windows, sebbene alcune cose di Windows di livello inferiore comprimano i bit.

Dici "Non posso fare a meno di sentirmi stupido quando guardo questo". NON - sentirti arrabbiato.

Hai appena incontrato l'output di un programmatore di cowboy.

Non sa nulla della scrittura di codice gestibile? Spero sinceramente che sia lui quello che dovrà tornare a questo in un anno e provare a ricordare cosa significa.

Non so se hai tagliato i commenti o se non ce ne sono stati, ma questo codice non avrebbe superato la revisione del codice in cui ero responsabile della qualità (e lo sono stato un paio di volte).

Ecco una buona regola empirica: gli unici "numeri interi nudi" consentiti nel codice sono 0 1 e 1. Tutti gli altri numeri dovrebbero essere #definiti, costi, enumerazioni, ecc., A seconda della lingua.

Se quei 3 e 0x33333333 hanno detto qualcosa come NUM_WIDGET_SHIFT_BITS e WIDGET_READ_MASK il codice sarebbe stato più facile da leggere.

Peccato per chiunque lo abbia messo in evidenza in un progetto open source, ma anche per i commenti sul codice personale e usare definizioni / enumerazioni significative e avere i propri standard di codifica.


Considererei anche le costanti esadecimali. 0xFF00è molto più leggibile (per me) di 0b1111111100000000. Non voglio contare per determinare il numero di bit impostati.
Kevin Vermeer,

1

Questo particolare codice è tratto direttamente dal libro Hacker's Delight , figura 5.2. È online in C (la funzione pop) qui . Nota che l'autore ora consiglia di utilizzare versioni aggiornate: http://www.hackersdelight.org/HDcode/newCode/pop_arrayHS.c.txt

Se vuoi imparare questo tipo di micro-ottimizzazioni, suggerirei quel libro; è divertente, ma a meno che tu non stia programmando bit di livello molto basso spesso non lo capirai; e la maggior parte delle volte il compilatore sarà in grado di fare molte di queste ottimizzazioni per te.

Aiuta anche a riscrivere tutti i numeri esadecimali in binario per comprendere questo tipo di algoritmi e lavorarci su uno o due casi di test.


1

Spiegazione per esempio. I dati sono sequenze di bit. Consente di contare i bit sul byte 01001101 con le seguenti operazioni disponibili: 1. Possiamo verificare il valore dell'ultimo bit. 2. Possiamo spostare la sequenza.

  1. 01001101 -> l'ultimo byte è 1, totale = 1. turni
  2. 10100110 -> l'ultimo byte è 0, totale = 1. turni
  3. 01010011 -> l'ultimo byte è 1, totale = 2. turni
  4. 10101001 -> l'ultimo byte è 1, totale = 3. turni
  5. 11010100 -> l'ultimo byte è 0, totale = 3. turni
  6. 01101010 -> l'ultimo byte è 0, totale = 3. turni
  7. 00110101 -> l'ultimo byte è 1, totale = 4. turni
  8. 10011010 -> l'ultimo byte è 0, totale = 4. turni

La nostra risposta: 4.

Non è stato difficile, vero? Il grosso problema delle operazioni bit per bit è che ci sono cose limitate che possiamo fare. Non possiamo accedere un po 'direttamente. Ad esempio, possiamo conoscere il valore dell'ultimo bit confrontandolo con il MASK 00000001 e possiamo fare in modo che ogni bit sia l'ultimo con le operazioni di spostamento. Naturalmente, l'algoritmo risultante sembrerà spaventoso per coloro che non sono abituati. Niente a che fare con l'intelligenza.


0

Non direi che ne hai bisogno a meno che il lavoro che stai facendo non sia collegato a:

  • Elaborazione audio
  • Elaborazione video
  • Grafica
  • Networking (in particolare laddove la dimensione del pacchetto è importante)
  • Enormi quantità di dati

Memorizzare le autorizzazioni in flag di stile unix è anche un altro uso per questo, se si dispone di un modello di autorizzazioni particolarmente complesso per il proprio sistema, o si vuole davvero stipare tutto in un singolo byte, a spese della leggibilità.

A parte queste aree lo considererei un grande vantaggio se uno sviluppatore / sviluppatore senior potesse dimostrare bit shifting e usare | & e ^ poiché mostra un interesse per la professione che potresti dire porta a un codice più stabile e affidabile.

Per non "ottenere" il metodo a prima vista, come detto, hai bisogno di una spiegazione di cosa sta facendo e di alcuni retroscena. Non direi che è legato all'intelligenza, ma quanto hai familiarità con il lavoro con esadecimali su base giornaliera e il riconoscimento dei problemi che alcuni schemi possono risolvere.

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.