Newsgroup: it.comp.lang.c++
Titolo: Scrivere in modo efficiente su un file un bit alla volta

Ciao a tutti!
ho scritto questa funzione per salvare su file alcuni dati un bit alla
volta. Il parametro Delta contiene il dato da scrivere, mentre Length
contiene il numero di bit che compongono il dato. Dal momento che il
tutto mi sembra un pò lento sto cercando un modo per velocizzare la
cosa. Esiste qualche altro modo più veloce?
Grazie Ciao!
void WriteBitsToFile(unsigned Data, unsigned Length, std::ofstream &
OutFile) {
	static unsigned char BitBuffer = 0; static unsigned BitCounter = 0;
	for (unsigned i = Length; i --; ) {
		(BitBuffer <<= 1) |= ((Data >> i) & 0x1); BitCounter ++;
		if (BitCounter == 8) { OutFile.write((char *) & BitBuffer, sizeof
(BitBuffer)); BitCounter = 0; }
	}
}

> volta. Il parametro Delta contiene il dato da scrivere, mentre Length
Ehm, ovviamente Data, non Delta :-P

 OutFile) {
dunque, da quel che capisco, Data è un ansigned "by value" e 
non un puntatore. Questo pare suggerire che tu non 
trasferisca MAI una "Length" superiore a sizeof (unsigned), 
che sulla mia macchina è 32 e sulla tua magari può anche 
essere 64, ma cmq non è enorme.
Allora, perché non predisporre maschere di filtraggio 
precalcolate ? (e passare una ENUM, che ti esime dal 
controllo del parametro Length a valori legali) =
Tipo (inserisco spazi per la lettura)
unsigned Masks [32] = {
0b 00000000 00000000 00000000 00000001,
0b 00000000 00000000 00000000 00000011,
0b 00000000 00000000 00000000 00000111,
......................................
0b 00000000 00000000 00000011 11111111,
......................................
0b 00000000 00000000 11111111 11111111,
......................................
0b 00111111 11111111 11111111 11111111,
0b 01111111 11111111 11111111 11111111,
0b 11111111 11111111 11111111 11111111,
};
Questo se non ti servono mai OFFSETS di bit iniziale non 
interi, se no devi prevedere molte più maschere diversamente 
allineate (io le ho allineate tutte a sinistra, se il LSB è 
tale ... controllalo, non so se è definito il bit ordering)
A quel punto eviti un ciclo, fai un unico masking e scrivi 
il dato. Se devi per forza scrivere un carattere alla volta, 
puoi fare solo otto maschere, e affettare l'unsigned tramite 
una union, tipo
union Affetta {
unsigned DatoInput;
unsighed char PezziOutput [sizeof (unsigned)]
};
Spero di aver capito bene quel che vuoi fare. I nomi che usi 
(buffer) paiono indicare la volontà di bufferizzare, ma 
l'aspetto della funzioen mi pare sincrono, che flushi e 
butti fuori il dato ad ogni chiamata, quindi ero dubbioso
ciao
Soviet


Ciao,
grazie per la risposta. La mia funzione mantiene un buffer interno
(BitBuffer) perchè ad ogni chiamata Data deve essere 'accodato' a
quello precedente. Mi spiego con un esempio.
Alla prima chiamata Data = 00000....01011b e Length = 4
Alla seconda chiamata Data = 00000....0011b e Length = 3
Quindi quello che voglio scrivere è: 1011011 cioè i 4 lsb del primo
Data e i 3 lsb del secondo. In pratica il buffer mi serve per
'accodare' i Data da scrivere, e anche perchè non posso inviare allo
stream dati più piccoli di un byte. Ho paura che l'uso delle maschere
sia complesso perchè non sò a priori dove inizio ad accodare nel
BitBuffer visto che cambia ogni volta a seconda di dove ero arrivato
ad accodare con il predecente valore.
Ciao!

 ad accodare con il predecente valore.
Beh non e' cosi' complesso: devi solo stare attento a non sforare il
tuo numero massimo di bit. In questo modo, fai solo 1--2 scritture
(senza loop) invece che N scritture (con loop). Esempio pseudocodice
coi numeri che hai dato tu:
* bitBuffer = 0
* bitBuffer << 4
* bitBuffer |= (00000....01011b & mask[4])
* bitBuffer << 3
* bitBuffer |= (00000....0011b & mask[3])
C'e' la possibilita' che pero' la nuova scrittura sfori il buffer;
quindi, all'esempio precedente devi aggiungere:
* controlla che la lunghezza del bitBuffer + la lunghezza dei nuovi
dati non superi 8 bit (o quello che e')
* se non lo supera, vai tranquillo come sopra
* se lo supera, spezza in due: scrivi il massimo che puoi in questo
bitBuffer, scrivi il bitBuffer su disco, svuotalo (rimettilo a 0), e
poi scrivici su quello che rimane
BTW, ti serve davvero fare il masking? Da come hai fatto l'esempio, i
bit superiori sono gia' tutti 0, se li generi cosi' non hai bisogno di
masking.
Cmq, e' possibile che il tempo maggiore lo sprechi facendo tutti
quegli accessi alla memoria in scrittura. Davvero devi scrivere ogni
byte? Perche' non scrivi ogni parola, e.g. accumuli 32 bit invece che
8 e scrivi quelli? O addirittura bufferizzi qualcosa di piu' lungo
(centinaia / migliaia di byte) e li scrivi in un colpo solo? Dovresti
guadagnarci parecchio.
Pleg

 guadagnarci parecchio.
Penso di apportare le modifiche nei prox gg, poi vi scriverò un
feedback.
Grazie ancora, Ciao!

 Ciao, mi pare un ottimo suggerimento, lo proverò senz'altro!
Salviamo un altro ciclo di clock (forse, magari il compilatore lo fa
gia' per te :)
Non hai bisogno di mettere a 0 il buffer e poi fare la seconda
scrittura, basta che sovrascrivi il buffer con la seconda scrittura --
no azzeramento, no shift.
Pleg

Ciao,
oggi ho trovato 5 min per provare a seguire i vs suggerimenti e mi
sono imbattuto in una strana situazione: Ho modificato il codice del
mio primo post cambiando solo due cose:
1. static unsigned char BitBuffer = 0;     ----->     static unsigned
BitBuffer = 0;
2. if (BitCounter == 8) { OutFile.write((char *) & BitBuffer, sizeof
(BitBuffer)); BitCounter = 0; }
------>    if (BitCounter == 32) { OutFile.write((char *) & BitBuffer,
sizeof (BitBuffer)); BitCounter = 0; }
in pratica ho cambiato il mio buffer da una variabile char ad una
variabile unsigned (nel tentativo di accedere meno spesso alla .write
()
Risultato: il codice si è molto velocizzato, ma i dati letti dal file
non risultano più corretti. Per leggere il file uso questa procedura
(basata ancora su un buffer di tipo unsigned char):
unsigned ReadBitsFromFile(unsigned Length, std::ifstream & InFile) {
	static unsigned char BitBuffer = 0; static unsigned BitCounter = 0;
unsigned Data = 0;
	for (unsigned i = Length; i --; ) {
		if (BitCounter == 0) { InFile.read((char *) & BitBuffer, sizeof
(BitBuffer)); BitCounter = 8; }
		(Data <<= 1) |= ((BitBuffer >> (BitCounter - 1)) & 0x1); BitCounter
--;
	}
	return Data;
}
Ora, la cosa strana è che se modifico anche la funzione
ReadBitsFromFile nello stesso modo in cui ho modificato la funzione di
scrittura, tutto torna a funzionare. In pratica succede che:
1. se scrivo un unsigned char alla volta e leggo un unsigned char alla
volta, tutto ok
2. se scrivo un unsigned alla volta e leggo un unsigned alla volta, ok
3. se scrivo un unsigned alla volta e leggo un unsigned char alla
volta non riottengo gli stessi dati!
Aggiungo che sto compilando con VC++ 2008 Express Edition su una
macchina con OS XP a 32 bit.
A parte che la soluzione numero 2 funziona e va bene, come mai accade
una cosa simile?
Grazie Ciao

 volta non riottengo gli stessi dati!
Perche' dovresti fare una cosa del genere?
Cmq, potrebbe essere un problema di endianness?
Pleg

 Pleg

 indirizzi di memoria bassi o alti.
http://www.c-faq.com/cpp/ifendian.html
Cmq, checcemporta? :D
Se uno cerca di leggere i singoli byte per ricostruire un int, deve
smazzarsi 'sto problema... ma non vedo perche' farlo.
Scrivi/leggi un int direttamente, e ti risparmi complicazioni. Oppure
scrivi/leggi a blocchi piu' grandi, e poi manipola i singoli bit da
li' (cosa che sicuramente fa bene agli accessi alla memoria).
Pleg