Domanda

Vorrei ottenere un byte [] da un float [] il più rapidamente possibile, senza passare attraverso l'intero array (probabilmente attraverso un cast). Il codice non sicuro va bene. Grazie!

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 passo a un BinaryWriter.

Modifica : A quei critici che urlano "ottimizzazione prematura": Ho analizzato questo usando il profiler ANTS prima di ottimizzarlo. Si è verificato un aumento significativo della velocità poiché il file ha una cache write-through e l'array float è dimensionato esattamente per corrispondere alla dimensione del settore sul disco. Il writer binario racchiude un handle di file creato con l'API pinvoke 'd win32. L'ottimizzazione si verifica poiché riduce il numero di chiamate di funzione.

E, per quanto riguarda la memoria, questa applicazione crea enormi cache che usano 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 complessivo di memoria dell'app.

Quindi immagino che la lezione qui non sia quella di fare ipotesi premature;)

È stato utile?

Soluzione

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);

Altri suggerimenti

Esiste un modo veloce (non un codice non sicuro) per farlo:

[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;
}

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

L'ho cercato ancora un po ', ed è in realtà descritto su MSDN , Procedura: creare un'unione C / C ++ utilizzando gli attributi (C # e Visual Basic) , quindi è probabile che questo sarà supportato nelle versioni future. Non sono sicuro di Mono però.

L'ottimizzazione precoce è la radice di tutti i mali! Il suggerimento di Vlad di iterare su ogni float è una risposta molto più ragionevole rispetto al passaggio a un byte []. Prendi la seguente tabella di runtime per aumentare il numero di elementi (media di 50 esecuzioni):

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 entrato nell'enorme numero di elementi, il tempo impiegato per copiare dal float [] al byte [] supera di gran lunga i vantaggi.

Quindi vai con ciò che è semplice:

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

Esiste un modo per evitare la copia e l'iterazione della memoria.

Puoi usare un brutto hack per cambiare temporaneamente l'array in un altro tipo usando la manipolazione della memoria (non sicura).

Ho provato questo hack sia in 32 & amp; Sistema operativo a 64 bit, quindi dovrebbe essere portatile.

L'utilizzo del sorgente + campione è mantenuto su https://gist.github.com/1050703 , ma per tua comodità, lo incollerò 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'uso è:

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

Stai meglio lasciando che BinaryWriter faccia questo per te . Ci sarà iterazione sull'intero set di dati indipendentemente dal metodo utilizzato, quindi non ha senso giocare con i byte.

Sebbene sia possibile ottenere un puntatore byte * utilizzando non sicuro e riparato , non è possibile convertire il byte * a byte [] per consentire allo scrittore di accettarlo come parametro senza eseguire la copia dei dati. Cosa che non si desidera fare in quanto raddoppierà la propria impronta di memoria e aggiungendo un'ulteriore iterazione sull'inevitabile iterazione che deve essere eseguita al fine di trasmettere i dati su disco.

Invece, stai ancora meglio ripetendo l'array di float e scrivendo ogni float nello scrittore individualmente , usando Write (double) metodo. Sarà ancora veloce a causa del buffering all'interno dello scrittore. Vedi i numeri di sixlettervariables .

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;
    }

Anche se fondamentalmente fa un ciclo for dietro le quinte, fa il lavoro in una riga

byte[] byteArray = floatArray.Select(
                    f=>System.BitConverter.GetBytes(f)).Aggregate(
                    (bytes, f) => {List<byte> temp = bytes.ToList(); temp.AddRange(f); return temp.ToArray(); });
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top