Qual é a maneira mais rápida para converter um float [] para um byte []?

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

  •  03-07-2019
  •  | 
  •  

Pergunta

Gostaria de obter uma byte[] de um float[] o mais rápido possível, sem loop através de toda a matriz (via um elenco, provavelmente). código inseguro é bom. Obrigado!

Estou à procura de um tempo array de bytes 4 mais do que a matriz float (a dimensão da matriz de bytes será de 4 vezes a da matriz float, uma vez que cada flutuador é composto por 4 bytes). Vou passar isso para um BinaryWriter.

Editar : Para esses críticos gritando "otimização prematura": Eu aferido isso usando ANTS profiler antes de eu otimizado. Houve um aumento significativo de velocidade porque o arquivo tem um write-through cache e a matriz float é exatamente dimensionado para corresponder ao tamanho do setor no disco. O escritor binário envolve um identificador de arquivo criado com pinvoke'd API win32. A otimização ocorre uma vez que este reduz o número de chamadas de função.

E, no que diz respeito à memória, esta aplicação cria caches maciço que muita memória uso. Eu posso alocar o buffer byte uma vez e re usar-lo muitas vezes -. O uso de memória dupla neste quantidades instância específica a um erro de arredondamento no consumo de memória global do aplicativo

Então eu acho que a lição aqui não é fazer suposições prematuras;)

Foi útil?

Solução

Se você não quer qualquer conversão acontecer, gostaria de sugerir Buffer.BlockCopy ().

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

Por exemplo:

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

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

Outras dicas

Há um sujo rápido (código não inseguro) maneira de fazer isso:

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

Isto irá funcionar, mas eu não tenho certeza do apoio em Mono ou versões mais recentes do CLR . A única coisa estranha é que o array.Length é o comprimento bytes. Isto pode ser explicado porque olha para o comprimento da matriz armazenada com a matriz, e por essa matriz era uma matriz de bytes que o comprimento ainda estará em comprimento byte. O indexador faz pensar sobre o Duplo sendo oito bytes grande, então nenhum cálculo é necessário lá.

Eu olhei para ele um pouco mais, e é realmente descrito em MSDN , Como criar uma União C / C ++ usando Atributos (C # e Visual Basic) , então é possível que este será suportado em versões futuras. Eu não estou certo sobre Mono embora.

A optimização prematura é a raiz de todo o mal! @ Sugestão de Vlad para iterar sobre cada flutuador é uma resposta muito mais razoável do que a mudança para um byte []. Tome a seguinte tabela de tempos de execução para aumentar o número de elementos (média de 50 corridas):

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

Há pouca diferença entre os dois para um pequeno número de elementos. Uma vez que você entrar no enorme número de elementos variam, o tempo gasto copiando do flutuador [] para o byte [] supera em muito os benefícios.

Assim, ir com o que é simples:

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

Há uma maneira que evita memória copiando e iteração.

Você pode usar um hack realmente feio para a mudança temporária sua matriz para outro tipo usando (inseguro) manipulação de memória.

Eu testei este hack em ambos OS 32 e 64 bits, por isso deve ser portátil.

O uso de amostra + fonte é mantida a https://gist.github.com/1050703 , mas para sua conveniência eu vou colá-lo aqui também:

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 o uso é:

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

Você está em melhor-off deixando o BinaryWriter fazer isso por você . Não vai ser iteração ao longo de todo o seu conjunto de dados, independentemente do método que você usar, então não há nenhum ponto em jogar com bytes.

Embora você pode obter um ponteiro byte* usando unsafe e fixed, você não pode converter o byte* para byte[] para que o escritor a aceitá-lo como um parâmetro sem a realização de copiar dados. Que você não quer fazer como ele irá duplicar o seu consumo de memória e adicionar uma iteração extra sobre a iteração inevitável que precisa ser realizada, a fim de enviar os dados para o disco.

Em vez disso, você ainda é melhor fora de iteração sobre a matriz de carros alegóricos e escrever cada float ao escritor individualmente , usando o método Write(double). Ele ainda vai ser rápido por causa do tamponamento dentro do escritor. Veja os números de sixlettervariables.

Nós temos uma classe chamada LudicrousSpeedSerialization e contém o seguinte 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;
    }

Embora basicamente faz fazer um loop nos bastidores, ele faz o trabalho em uma linha

byte[] byteArray = floatArray.Select(
                    f=>System.BitConverter.GetBytes(f)).Aggregate(
                    (bytes, f) => {List<byte> temp = bytes.ToList(); temp.AddRange(f); return temp.ToArray(); });
Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top