2011-02-07 12 views
14

Nella nostra applicazione, abbiamo un array di byte molto grande e dobbiamo convertire questi byte in tipi diversi. Attualmente, usiamo BitConverter.ToXXXX() per questo scopo. I nostri colpitori pesanti sono, ToInt16 e ToUInt64.Cast veloce in C# usando BitConverter, può essere più veloce?

Per UInt64, il nostro problema è che il flusso di dati ha effettivamente 6 byte di dati per rappresentare un intero grande. Poiché non v'è alcuna funzione nativa per convertire 6-byte di dati per UInt64, facciamo:

UInt64 value = BitConverter.ToUInt64() & 0x0000ffffffffffff; 

Il nostro uso di ToInt16 è più semplice, non c'è bisogno di fare qualsiasi manipolazione bit.

Facciamo così tante di queste 2 operazioni che volevo chiedere alla comunità SO se c'è un modo più veloce per fare queste conversioni. In questo momento, circa il 20% di tutti i nostri cicli di CPU viene consumato da queste due funzioni.

+8

Le prestazioni intere non sono probabilmente il tuo problema. L'elaborazione di array di grandi dimensioni fa quasi sempre rallentare il rallentamento della RAM bus. Presta attenzione al contatore delle prestazioni "Last Level Cache Misses" nell'output del tuo profiler. –

+0

@Hans: hai sicuramente ragione: siamo legati alla memoria. Ma per quello, non so cosa fare. Abbiamo un grande array e dobbiamo attraversare ogni byte per estrarre i dati. Mentre procedo linearmente nell'array, il prefetcher dell'hardware si blocca probabilmente nel pattern di accesso e al di là di questo, non so che altro si possa fare. --thanks – SomethingBetter

risposta

6

Avete pensato di utilizzare direttamente i puntatori di memoria. Non posso garantire per la sua prestazione, ma si tratta di un trucco comune in C++ \ C ...

 byte[] arr = { 1, 2, 3, 4, 5, 6, 7, 8 ,9,10,11,12,13,14,15,16}; 

     fixed (byte* a2rr = &arr[0]) 
     { 

      UInt64* uint64ptr = (UInt64*) a2rr; 
      Console.WriteLine("The value is {0:X2}", (*uint64ptr & 0x0000FFFFFFFFFFFF)); 
      uint64ptr = (UInt64*) ((byte*) uint64ptr+6); 
      Console.WriteLine("The value is {0:X2}", (*uint64ptr & 0x0000FFFFFFFFFFFF)); 
     } 

Avrai bisogno di rendere la vostra assemblea "non sicuro" nelle impostazioni di generazione così come contrassegnare il metodo che avresti fatto anche questo non sicuro. Sei anche legato a little endian con questo approccio.

+0

Questo si è rivelato il modo più veloce per farlo, almeno finora. – SomethingBetter

+1

Stai attento a questo. Se vuoi leggere uno di quei numeri da 6 byte alla fine di un array, otterrai un'eccezione. Cioè, se nell'esempio sopra la matrice fosse lunga solo 12 byte, si otterrebbe un'eccezione durante la lettura del secondo valore. –

2

Perché non:

UInt16 valLow = BitConverter.ToUInt16(); 
UInt64 valHigh = (UInt64)BitConverter.ToUInt32(); 
UInt64 Value = (valHigh << 16) | valLow; 

si può fare che una singola istruzione, anche se il compilatore JIT sarà probabilmente farlo automaticamente per voi.

Ciò ti impedirà di leggere quei due byte in più che finisci per gettare via.

Se ciò non riduce la CPU, probabilmente vorrai scrivere il tuo convertitore che legge i byte direttamente dal buffer. È possibile utilizzare l'indicizzazione degli array o, se si ritiene necessario, il codice non sicuro con i puntatori.

Si noti che, come indicato da un commentatore, se si utilizza uno di questi suggerimenti, o si è limitati a una particolare "endianità", o si dovrà scrivere il codice per rilevare piccoli/grandi endian e reagire di conseguenza. L'esempio di codice che ho mostrato sopra funziona per little endian (x86).

+2

Dovresti dire che questo funziona per una certa endianità (penso poco, ma sto sempre mescolando i due). Può o non può importare all'OP. –

+0

@Martinho: buon punto. Ho aggiornato la mia risposta. –

+0

Ho seguito il tuo suggerimento iniziale pensando che leggere quei 2 byte in più e buttarli fuori mi debba rallentare, ma si è scoperto, questo è il modo più lento di farlo. Suppongo che dato che i dati sono già memorizzati nella cache, non importa davvero leggere 8 o 6 byte alla volta. Il tuo secondo suggerimento, il codice @Jimmy fornito come risposta funziona molto più velocemente. - grazie – SomethingBetter

4

È possibile utilizzare la classe System.Buffer per copiare tutta una serie ad un'altra matrice di tipo diverso come un facile, 'block copy' funzionamento:

Procedimento BlockCopy accede ai byte nella matrice parametro src usando offset in memoria, non costrutti di programmazione come indici o limiti di array superiore e inferiore.

I tipi di array devono essere di tipo "primitivo", devono essere allineati e l'operazione di copia è sensibile all'endian. Nel tuo caso di interi a 6 byte, non può essere allineato con nessuno dei tipi "primitivi" di .NET, a meno che tu non possa ottenere l'array sorgente con due byte di padding per ogni sei, che quindi si allineeranno a Int64. Ma questo metodo funzionerà per gli array di Int16, che potrebbero velocizzare alcune delle operazioni.

+0

Grazie per le informazioni su System.Buffer.BlockCopy. Nel nostro caso, l'UInt64 e l'Int16 sono interfogliati nell'array, quindi BlockCopy non funzionerà per noi, ma questa informazione è stata utile, possiamo usare questo metodo in futuro. – SomethingBetter

1

Vedere la risposta a una domanda simile here. È la stessa manipolazione della memoria non sicura come nella risposta di Jimmy, ma in un modo più "amichevole" per i consumatori. Ti consentirà di visualizzare l'array byte come array UInt64.