2009-03-06 27 views
10

Vorrei ottenere byte[] da un float[] il più rapidamente possibile, senza eseguire il ciclo attraverso l'intero array (tramite un cast, probabilmente). Il codice non sicuro va bene. Grazie!Qual è il modo più veloce per convertire un float [] in un byte []?

Sto cercando un array di byte 4 volte più lungo dell'array float (la dimensione dell'array di byte sarà 4 volte quella dell'array float, poiché ogni float è composto da 4 byte). Lo passerò a un BinaryWriter.

EDIT: Per quei critici che urlano "ottimizzazione prematura": Ho benchmark questo utilizzando ANTS Profiler prima ho ottimizzato. C'è stato un aumento significativo della velocità perché il file ha una cache write-through e l'array float è esattamente dimensionato per corrispondere alla dimensione del settore sul disco. Il writer binario racchiude un handle di file creato con l'API di Win32 pinvoke. L'ottimizzazione si verifica poiché questo riduce il numero di chiamate di funzione.

E, per quanto riguarda la memoria, questa applicazione crea enormi cache che utilizzano molta memoria. Posso allocare il buffer di byte una volta e riutilizzarlo più volte - l'utilizzo della doppia memoria in questa particolare istanza equivale a un errore di arrotondamento nel consumo totale di memoria dell'app.

Quindi credo che la lezione qui è di non fare ipotesi premature;)

+0

Cosa vuole realmente ? Ogni float lanciato su un byte o su un array quattro volte più lungo contenente la rappresentazione in byte dei float? – Khoth

+0

Che cosa significa "quattro volte più lungo"? – ryeguy

+0

Sarebbe utile sapere cosa si intende utilizzare i byte * per * dopo. La risposta accettata non è ottimale in diverse situazioni se si è disposti a utilizzare codice non sicuro ... – ShuggyCoUk

risposta

7

Se non si desidera che avvenga alcuna conversione, suggerirei Buffer.BlockCopy().

public static void BlockCopy(
    Array src, 
    int srcOffset, 
    Array dst, 
    int dstOffset, 
    int count 
) 

Ad esempio:

float[] floatArray = new float[1000]; 
byte[] byteArray = new byte[floatArray.Length * 4]; 

Buffer.BlockCopy(floatArray, 0, byteArray, 0, byteArray.Length); 
+1

In questo modo raddoppierà la quantità di allocazione di memoria * in aggiunta * per iterare sugli array * due * (una volta per copiare, una volta per scrivere). Molto inefficiente sia in termini di velocità che di memoria. Non consigliato. – vladr

+0

L'ultimo parametro non deve essere moltiplicato per sizeof (float)? – jdmichal

+0

@jdmichal - Sì, hai ragione. – Jeremy

1

Anche se è possibile ottenere un puntatore byte* utilizzando unsafe e fixed, non è possibile convertire il byte* a byte[] in modo che lo scrittore di accettarla come un parametro senza eseguire la copia dei dati. Che non vuoi fare in quanto raddoppierà il tuo ingombro di memoria e aggiungi un'iterazione extra sull'inevitabile iterazione che deve essere eseguita per inviare i dati sul disco.

Invece, si sono ancora meglio iterazione di matrice di carri e la scrittura di ogni float allo scrittore singolarmente, utilizzando il metodo Write(double). Sarà ancora veloce a causa del buffering all'interno dello scrittore. Vedi i numeri di sixlettervariables.

+0

Non sai cosa intendi. Voglio solo l'indicizzazione a livello di byte nell'array a virgola mobile (in realtà sto passando l'array a un writer). – Nick

+0

@Vlad: cosa dovrebbe significare? Come può un tipo di dati non essere rappresentabile come byte? Vedi la mia risposta. – ryeguy

+0

significa che la rappresentazione binaria di (float) 0 e quella di (byte) 0 non sono uguali (per uno non hanno la stessa dimensione). – vladr

-1

Anche se in pratica fa fare un ciclo for dietro le quinte, si fa fare il lavoro in una linea

byte[] byteArray = floatArray.Select(
        f=>System.BitConverter.GetBytes(f)).Aggregate(
        (bytes, f) => {List<byte> temp = bytes.ToList(); temp.AddRange(f); return temp.ToArray(); }); 
3

È meglio-off lasciando che il BinaryWriter do this for you. Ci sarà un'iterazione sull'intero insieme di dati, indipendentemente dal metodo utilizzato, quindi non ha senso giocare con i byte.

18

L'ottimizzazione prematura è la radice di tutto il male! Il suggerimento di Vlad di iterare su ogni float è una risposta molto più ragionevole rispetto al passaggio a un byte []. Prendere la seguente tabella di tempi di esecuzione per un numero crescente di elementi (in media di 50 corse):

Elements  BinaryWriter(float)  BinaryWriter(byte[]) 
----------------------------------------------------------- 
10    8.72ms     8.76ms 
100    8.94ms     8.82ms 
1000   10.32ms     9.06ms 
10000   32.56ms     10.34ms 
100000   213.28ms     739.90ms 
1000000  1955.92ms    10668.56ms 

C'è poca differenza tra i due per un piccolo numero di elementi.Una volta raggiunto l'enorme numero di elementi disponibili, il tempo impiegato per copiare dal float [] al byte [] supera di gran lunga i benefici.

Quindi, andare con ciò che è semplice:

float[] data = new float[...]; 
foreach(float value in data) 
{ 
    writer.Write(value); 
} 
+1

Numeri reali, bello. :) – Jeremy

+0

L'ho analizzato utilizzando il profiler ANTS prima di ottimizzarlo. C'è stato un aumento significativo della velocità perché il file ha una cache write-through e l'array float è esattamente dimensionato per corrispondere alla dimensione del settore sul disco. Il writer binario racchiude un handle di file creato con l'API win32. ;) – Nick

+1

Buono, ma aggiungerei che, a meno che tu non stia scrivendo milioni di float o eseguendoli migliaia di volte, ~ 200ms è un numero non importante nel grande schema di esecuzione del programma. – user7116

18

C'è una sporca veloce (non codice non sicuro) modo di fare questo:

[StructLayout(LayoutKind.Explicit)] 
struct BytetoDoubleConverter 
{ 
    [FieldOffset(0)] 
    public Byte[] Bytes; 

    [FieldOffset(0)] 
    public Double[] Doubles; 
} 
//... 
static Double Sum(byte[] data) 
{ 
    BytetoDoubleConverter convert = new BytetoDoubleConverter { Bytes = data }; 
    Double result = 0; 
    for (int i = 0; i < convert.Doubles.Length/sizeof(Double); i++) 
    { 
     result += convert.Doubles[i]; 
    } 
    return result; 
} 

Questo funzionerà, ma sono Non sono sicuro del supporto su Mono o sulle versioni più recenti di CLR. L'unica cosa strana è che il array.Length è la lunghezza dei byte. Questo può essere spiegato perché analizza la lunghezza dell'array memorizzata con l'array, e poiché questa matrice era un array di byte, la lunghezza sarà comunque in lunghezza in byte. L'indicizzatore pensa che Double sia otto byte di grandi dimensioni quindi non è necessario alcun calcolo lì.

Ho cercato ancora un po ', e in realtà è descritta a MSDN, How to: Create a C/C++ Union by Using Attributes (C# and Visual Basic), quindi è probabile che questo sarà supportato nelle versioni future. Comunque non sono sicuro di Mono.

+0

+1 Bella tecnica! Questo alias di riferimento è sicuro dal punto di vista del garbage collector? –

+1

Giusto per evitare che qualcuno abbia un'idea sbagliata, un 'System.Double' (o in C# semplicemente' double') è 8 byte (o 64 bit) e ** non ** 4 byte (o 32 bit). – Aidiakapi

+0

Ciò può anche consentire l'accesso alla memoria non inizializzata o altra memoria. Buffer overflow exploits _go! _ – TylerY86

0

Abbiamo una classe chiamata LudicrousSpeedSerialization e contiene il seguente metodo non sicuro:

static public byte[] ConvertFloatsToBytes(float[] data) 
    { 
     int n = data.Length; 
     byte[] ret = new byte[n * sizeof(float)]; 
     if (n == 0) return ret; 

     unsafe 
     { 
      fixed (byte* pByteArray = &ret[0]) 
      { 
       float* pFloatArray = (float*)pByteArray; 
       for (int i = 0; i < n; i++) 
       { 
        pFloatArray[i] = data[i]; 
       } 
      } 
     } 

     return ret; 
    } 
11

C'è una via che evita la copia della memoria e iterazione.

È possibile utilizzare un trucco davvero brutto per modificare temporaneamente la matrice in un altro tipo utilizzando la manipolazione della memoria (non sicura).

Ho provato questo hack in entrambi i sistemi operativi a 32 bit & a 64 bit, quindi dovrebbe essere portatile.

L'utilizzo fonte + campione viene portato avanti a https://gist.github.com/1050703, ma per comodità te l'incollarlo anche qui:

public static unsafe class FastArraySerializer 
{ 
    [StructLayout(LayoutKind.Explicit)] 
    private struct Union 
    { 
     [FieldOffset(0)] public byte[] bytes; 
     [FieldOffset(0)] public float[] floats; 
    } 

    [StructLayout(LayoutKind.Sequential, Pack = 1)] 
    private struct ArrayHeader 
    { 
     public UIntPtr type; 
     public UIntPtr length; 
    } 

    private static readonly UIntPtr BYTE_ARRAY_TYPE; 
    private static readonly UIntPtr FLOAT_ARRAY_TYPE; 

    static FastArraySerializer() 
    { 
     fixed (void* pBytes = new byte[1]) 
     fixed (void* pFloats = new float[1]) 
     { 
      BYTE_ARRAY_TYPE = getHeader(pBytes)->type; 
      FLOAT_ARRAY_TYPE = getHeader(pFloats)->type; 
     } 
    } 

    public static void AsByteArray(this float[] floats, Action<byte[]> action) 
    { 
     if (floats.handleNullOrEmptyArray(action)) 
      return; 

     var union = new Union {floats = floats}; 
     union.floats.toByteArray(); 
     try 
     { 
      action(union.bytes); 
     } 
     finally 
     { 
      union.bytes.toFloatArray(); 
     } 
    } 

    public static void AsFloatArray(this byte[] bytes, Action<float[]> action) 
    { 
     if (bytes.handleNullOrEmptyArray(action)) 
      return; 

     var union = new Union {bytes = bytes}; 
     union.bytes.toFloatArray(); 
     try 
     { 
      action(union.floats); 
     } 
     finally 
     { 
      union.floats.toByteArray(); 
     } 
    } 

    public static bool handleNullOrEmptyArray<TSrc,TDst>(this TSrc[] array, Action<TDst[]> action) 
    { 
     if (array == null) 
     { 
      action(null); 
      return true; 
     } 

     if (array.Length == 0) 
     { 
      action(new TDst[0]); 
      return true; 
     } 

     return false; 
    } 

    private static ArrayHeader* getHeader(void* pBytes) 
    { 
     return (ArrayHeader*)pBytes - 1; 
    } 

    private static void toFloatArray(this byte[] bytes) 
    { 
     fixed (void* pArray = bytes) 
     { 
      var pHeader = getHeader(pArray); 

      pHeader->type = FLOAT_ARRAY_TYPE; 
      pHeader->length = (UIntPtr)(bytes.Length/sizeof(float)); 
     } 
    } 

    private static void toByteArray(this float[] floats) 
    { 
     fixed(void* pArray = floats) 
     { 
      var pHeader = getHeader(pArray); 

      pHeader->type = BYTE_ARRAY_TYPE; 
      pHeader->length = (UIntPtr)(floats.Length * sizeof(float)); 
     } 
    } 
} 

E l'utilizzo è:

var floats = new float[] {0, 1, 0, 1}; 
floats.AsByteArray(bytes => 
{ 
    foreach (var b in bytes) 
    { 
     Console.WriteLine(b); 
    } 
}); 
+1

-1 per essere completamente non-portatile. L'hai provato anche su una macchina a 64 bit? – Gabe

+1

no: è un hack. Se e quando accedo a un Macchina a 64 bit, potrei verificarlo e magari adattarlo, ma non è nemmeno a prova di futuro.Nella CLR v.Prossimo potrebbe essere completamente rotto.Vi è un compromesso qui: puoi usare una soluzione più robusta e pagare in performance, o uso il modo più veloce a cui riesco a pensare e vivere al limite :-) –

+1

Ho avuto la possibilità di usarlo su una macchina a 64 bit, quindi ho reso il codice portatile –

Problemi correlati