¿Cuál es la forma más rápida de convertir un flotante [] en un byte []?

StackOverflow https://stackoverflow.com/questions/619041

  •  03-07-2019
  •  | 
  •  

Pregunta

Me gustaría obtener un byte [] de un float [] lo más rápido posible, sin recorrer toda la matriz (probablemente mediante un reparto). El código inseguro está bien. Gracias!

Estoy buscando una matriz de bytes 4 veces más larga que la matriz flotante (la dimensión de la matriz de bytes será 4 veces mayor que la matriz flotante, ya que cada flotante está compuesta de 4 bytes). Le pasaré esto a un BinaryWriter.

EDITAR : Para aquellos críticos que gritan "optimización prematura": He comparado esto usando ANTS profiler antes de optimizar. Hubo un aumento significativo de la velocidad porque el archivo tiene una memoria caché de escritura y la matriz flotante tiene el tamaño exacto para que coincida con el tamaño del sector en el disco. El escritor binario envuelve un identificador de archivo creado con pinvoke 'd win32 API. La optimización se produce ya que esto disminuye el número de llamadas a funciones.

Y, con respecto a la memoria, esta aplicación crea cachés masivos que usan mucha memoria. Puedo asignar el búfer de bytes una vez y reutilizarlo muchas veces: el uso de memoria doble en este caso particular equivale a un error de redondeo en el consumo general de memoria de la aplicación.

Entonces, supongo que la lección aquí no es hacer suposiciones prematuras;)

¿Fue útil?

Solución

Si no desea que se produzca ninguna conversión, sugeriría Buffer.BlockCopy ().

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

Por ejemplo:

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

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

Otros consejos

Hay una forma rápida y sucia (código no inseguro) de hacer esto:

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

Esto funcionará, pero no estoy seguro del soporte en Mono o versiones más recientes del CLR . Lo único extraño es que el array.Length es la longitud de bytes. Esto puede explicarse porque analiza la longitud de la matriz almacenada con la matriz, y porque esta matriz era una matriz de bytes, esa longitud seguirá estando en longitud de bytes. El indexador piensa que Double tiene ocho bytes de tamaño, por lo que no es necesario realizar ningún cálculo allí.

Lo he buscado un poco más, y en realidad se describe en MSDN , Cómo: Crear una unión C / C ++ mediante el uso de atributos (C # y Visual Basic) , por lo que es probable que esto sea compatible en futuras versiones. Sin embargo, no estoy seguro acerca de Mono.

¡La optimización prematura es la raíz de todo mal! La sugerencia de @ Vlad de iterar sobre cada flotador es una respuesta mucho más razonable que cambiar a un byte []. Tome la siguiente tabla de tiempos de ejecución para aumentar el número de elementos (promedio de 50 ejecuciones):

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

Hay poca diferencia entre los dos para un pequeño número de elementos. Una vez que ingresa a la gran cantidad de elementos, el tiempo dedicado a copiar del flotante [] al byte [] supera con creces los beneficios.

Así que ve con lo que es simple:

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

Hay una forma de evitar la copia e iteración de la memoria.

Puede usar un truco realmente feo para cambiar temporalmente su matriz a otro tipo usando la manipulación de memoria (insegura).

Probé este truco en 32 & amp; SO de 64 bits, por lo que debería ser portátil.

El uso de la fuente + muestra se mantiene en https://gist.github.com/1050703 , pero para su comodidad, también lo pegaré aquí:

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

Y el uso es:

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

Es mejor dejar que BinaryWriter haga esto por usted . Habrá una iteración sobre todo su conjunto de datos, independientemente del método que utilice, por lo que no tiene sentido jugar con bytes.

Aunque puede obtener un puntero byte * usando inseguro y fixed , no puede convertir el byte * byte [] para que el escritor lo acepte como parámetro sin realizar una copia de datos. Lo que no desea hacer, ya que duplicará la huella de su memoria y agregará una iteración adicional sobre la inevitable iteración que debe realizarse para generar los datos en el disco.

En cambio, todavía es mejor que itere sobre la matriz de flotantes y escriba cada float en el escritor individualmente , usando Write (double) método. Seguirá siendo rápido debido al almacenamiento en búfer dentro del escritor. Consulte los números de sixlettervariables .

Tenemos una clase llamada LudicrousSpeedSerialization y contiene el siguiente método inseguro:

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

Aunque básicamente hace un bucle for detrás de escena, hace el trabajo en una línea

byte[] byteArray = floatArray.Select(
                    f=>System.BitConverter.GetBytes(f)).Aggregate(
                    (bytes, f) => {List<byte> temp = bytes.ToList(); temp.AddRange(f); return temp.ToArray(); });
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top