C'è qualche vantaggio nella manipolazione di bit in stile c rispetto a std :: bitset?


16

Lavoro quasi esclusivamente in C ++ 11/14, e di solito mi arrabbio quando vedo codice come questo:

std::int64_t mArray;
mArray |= someMask << 1;

Questo è solo un esempio; Sto parlando di manipolazione saggia in generale. In C ++, c'è davvero qualche punto? Quanto sopra è sconvolgente e soggetto a errori, mentre l'utilizzo di un std::bitsetconsente di:

  1. modificare più facilmente la dimensione di std::bitsetcome necessario regolando un parametro modello e lasciando che l'implementazione si occupi del resto, e
  2. dedicare meno tempo a capire cosa sta succedendo (e possibilmente commettere errori) e scrivere std::bitsetin un modo simile std::arrayo altri contenitori di dati.

La mia domanda è; c'è qualche motivo per non usare std::bitsetsu tipi primitivi, se non per compatibilità con le versioni precedenti?


La dimensione di a std::bitsetè fissata in fase di compilazione. Questo è l'unico inconveniente paralizzante che mi viene in mente.
rwong

1
@rwong Sto parlando della std::bitsetmanipolazione dei bit in stile vs c (ad es. int), che è anche corretta in fase di compilazione.
quant

Uno dei motivi potrebbe essere il codice legacy: il codice è stato scritto quando std::bitsetnon era disponibile (o noto all'autore) e non c'era motivo di riscrivere il codice da usare std::bitset.
Bart van Ingen Schenau,

Personalmente ritengo che il problema di come rendere "comprensibili" operazioni su un insieme / mappa / array di variabili binarie "sia ancora in gran parte irrisolto, poiché in pratica ci sono molte operazioni che non possono essere ridotte a semplici operazioni. Esistono anche troppi modi per rappresentare tali insiemi, di cui bitsetuno, ma anche un piccolo vettore o un insieme di ints (di indice bit) potrebbe essere legittimo. La filosofia di C / C ++ non nasconde queste complessità di scelta al programmatore.
rwong,

Risposte:


12

Da un punto di vista logico (non tecnico), non vi è alcun vantaggio.

Qualsiasi semplice codice C / C ++ può essere inserito in un "costrutto di libreria" adatto. Dopo tale conclusione, la questione del "se questo sia più vantaggioso di così" diventa una questione controversa.

Da un punto di vista della velocità, C / C ++ dovrebbe consentire al costrutto di libreria di generare codice efficiente quanto il semplice codice che avvolge. Ciò è tuttavia soggetto a:

  • Funzione in linea
  • Controllo in fase di compilazione ed eliminazione di controlli di runtime non necessari
  • Eliminazione del codice morto
  • Molte altre ottimizzazioni del codice ...

Utilizzando questo tipo di argomento non tecnico, qualsiasi "funzione mancante" potrebbe essere aggiunta da chiunque, e quindi non viene considerata come uno svantaggio.

Tuttavia, i requisiti e le limitazioni integrati non possono essere superati con un codice aggiuntivo. Di seguito, sostengo che la dimensione di std::bitsetè una costante di tempo di compilazione, e quindi, sebbene non sia considerata uno svantaggio, è comunque qualcosa che influenza la scelta dell'utente.


Da un punto di vista estetico (leggibilità, facilità di manutenzione ecc.), C'è una differenza.

Tuttavia, non è evidente che il std::bitsetcodice vince immediatamente sul semplice codice C. Bisogna guardare pezzi di codice più grandi (e non alcuni esempi di giocattoli) per dire se l'uso di std::bitsetha migliorato la qualità umana del codice sorgente.


La velocità di manipolazione dei bit dipende dallo stile di codifica. Lo stile di codifica influenza sia la manipolazione dei bit C / C ++, sia ugualmente applicabile std::bitset, come spiegato di seguito.


Se si scrive codice che utilizza il operator []per leggere e scrivere un bit alla volta, sarà necessario farlo più volte se ci sono più bit da manipolare. Lo stesso si può dire del codice in stile C.

Tuttavia, bitsetha anche altri operatori, come operator &=, operator <<=ecc, che opera su tutta la larghezza del bitset. Poiché i macchinari sottostanti possono spesso operare su 32 bit, 64 bit e talvolta 128 bit (con SIMD) alla volta (nello stesso numero di cicli della CPU), codice progettato per trarre vantaggio da tali operazioni multi-bit può essere più veloce del codice di manipolazione dei bit "loopy".

L'idea generale si chiama SWAR (SIMD all'interno di un registro) ed è un argomento secondario sotto manipolazioni di bit.


Alcuni fornitori di C ++ potrebbero implementare bitsettra 64 e 128 bit con SIMD. Alcuni fornitori potrebbero non (ma potrebbero eventualmente farlo). Se è necessario sapere cosa sta facendo la libreria del fornitore C ++, l'unico modo è quello di esaminare lo smontaggio.


Per quanto riguarda se std::bitsetha dei limiti, posso fare due esempi.

  1. La dimensione di std::bitsetdeve essere nota al momento della compilazione. Per creare una matrice di bit con dimensioni scelte dinamicamente, si dovrà usare std::vector<bool>.
  2. L'attuale specifica C ++ per std::bitsetnon fornisce un modo per estrarre una porzione consecutiva di N bit da un maggiore bitsetdi M bit.

Il primo è fondamentale, nel senso che per le persone che hanno bisogno di bitset di dimensioni dinamiche, devono scegliere altre opzioni.

Il secondo può essere superato, perché è possibile scrivere una sorta di adattatori per eseguire l'attività, anche se lo standard bitsetnon è estensibile.


Esistono alcuni tipi di operazioni SWAR avanzate che non vengono fornite immediatamente std::bitset. Si potrebbe leggere di queste operazioni su questo sito Web su permutazioni di bit . Come al solito, è possibile implementarli da soli, operando sopra std::bitset.


Per quanto riguarda la discussione sulla performance.

Un'ammonizione: molte persone chiedono perché (qualcosa) dalla libreria standard è molto più lento di un semplice codice in stile C. Non vorrei ripetere la conoscenza dei prerequisiti del microbenchmarking qui, ma ho solo questo consiglio: assicurati di eseguire il benchmark in "modalità di rilascio" (con ottimizzazioni abilitate), e assicurati che il codice non venga eliminato (eliminazione del codice morto) o sollevato da un ciclo (movimento del codice invariante) .

Dato che in generale non possiamo dire se qualcuno (su Internet) stia eseguendo correttamente i microbenchmark, l'unico modo per ottenere una conclusione affidabile è quello di fare i nostri microbenchmark e documentare i dettagli e sottoporli a revisione pubblica e critica. Non fa male ri-fare microbenchmark che altri hanno già fatto.


Il problema n. 2 significa anche che il bitset non può essere usato in nessuna configurazione parallela in cui ogni thread dovrebbe funzionare su un sottoinsieme del bitset.
user239558

@ user239558 Dubito che qualcuno vorrebbe parallelizzarsi sullo stesso std::bitset. Non esiste una garanzia di coerenza della memoria (in std::bitset), il che significa che non dovrebbe essere condiviso tra i core. Le persone che hanno bisogno di condividerlo su più core tenderanno a costruire la propria implementazione. Quando i dati sono condivisi tra diversi core, è consuetudine allinearli al limite della cache. Non farlo riduce le prestazioni ed espone più insidie ​​di non atomicità. Non ho abbastanza conoscenze per fornire una panoramica su come costruire un'implementazione parallelizzabile di std::bitset.
rwong,

la programmazione parallela dei dati di solito non richiede consistenza della memoria. sincronizzi solo tra le fasi. Vorrei assolutamente elaborare un bitset in parallelo, penso che chiunque abbia una grande bitsetvolontà.
user239558

@utente239558 che sembra implicare la copia (l'intervallo rilevante di bitset che deve essere elaborato da ciascun core deve essere copiato prima dell'inizio dell'elaborazione). Sono d'accordo con questo, anche se penso che chiunque pensi alla parallelizzazione avrebbe già pensato di implementare la propria implementazione. In generale, molte funzionalità di libreria standard C ++ sono fornite come implementazioni di base; chiunque abbia esigenze più serie implementerà le proprie.
1919

no non c'è copia. sta semplicemente accedendo a diverse parti di una struttura di dati statica. quindi non è necessaria alcuna sincronizzazione.
user239558

2

Questo certamente non si applica in tutti i casi, ma a volte un algoritmo potrebbe dipendere dall'efficienza del bit-style in stile C per fornire significativi miglioramenti delle prestazioni. Il primo esempio che mi viene in mente è l'uso di bitboard , intelligenti codifiche di numeri interi di posizioni di giochi da tavolo, per accelerare i motori di scacchi e simili. Qui, la dimensione fissa dei tipi interi non è un problema, poiché le scacchiere sono sempre sempre 8 * 8.

Per un semplice esempio, considera la seguente funzione (presa da questa risposta di Ben Jackson ) che mette alla prova una posizione Connect Four per la vittoria:

// return whether newboard includes a win
bool haswon2(uint64_t newboard)
{
    uint64_t y = newboard & (newboard >> 6);
    uint64_t z = newboard & (newboard >> 7);
    uint64_t w = newboard & (newboard >> 8);
    uint64_t x = newboard & (newboard >> 1);
    return (y & (y >> 2 * 6)) | // check \ diagonal
           (z & (z >> 2 * 7)) | // check horizontal -
           (w & (w >> 2 * 8)) | // check / diagonal
           (x & (x >> 2));      // check vertical |
}

2
Pensi che std::bitsetsarebbe più lento?
quant

Bene, da una rapida occhiata alla fonte, il bitset libc ++ si basa su un singolo size_t o su un array di essi, quindi probabilmente si comporterebbe in qualcosa di sostanzialmente equivalente / identico, specialmente su un sistema in cui sizeof (size_t) == 8 - quindi no, probabilmente non sarebbe più lento.
Ryan Pavlik,
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.