Какой самый быстрый способ преобразовать float [] в байт []? - PullRequest
12 голосов
/ 06 марта 2009

Я бы хотел получить byte[] из float[] как можно быстрее, без зацикливания всего массива (возможно, с помощью приведения). Небезопасный код - это хорошо. Спасибо!

Я ищу байтовый массив в 4 раза длиннее, чем массив float (размер байтового массива будет в 4 раза больше размера массива float, поскольку каждый float состоит из 4 байтов). Я передам это BinaryWriter.

EDIT : Критики кричат ​​о «преждевременной оптимизации»: Я проверил это с помощью профилировщика ANTS, прежде чем оптимизировать. Произошло значительное увеличение скорости, поскольку файл имеет сквозной кэш-память, а массив с плавающей точкой точно соответствует размеру сектора на диске. Программа записи двоичных файлов переносит дескриптор файла, созданный с помощью pinvoke 'win32 API. Оптимизация происходит, поскольку это уменьшает количество вызовов функций.

И, что касается памяти, это приложение создает массивные кеши, которые используют много памяти. Я могу выделить байтовый буфер один раз и повторно использовать его много раз - двойное использование памяти в данном конкретном случае составляет ошибку округления в общем потреблении памяти приложением.

Так что я думаю, что урок здесь не в том, чтобы делать преждевременные предположения;)

Ответы [ 8 ]

19 голосов
/ 06 марта 2009

Существует грязный пост (небезопасный код) способ сделать это:

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

Это будет работать, но я не уверен в поддержке Mono или более новых версий CLR . Единственное, что странно, это то, что array.Length - это длина в байтах. Это может быть объяснено, потому что он смотрит на длину массива, хранящегося в массиве, и потому что этот массив был байтовым массивом, длина которого все еще будет в байтовой длине. Индексатор думает, что Double имеет размер восемь байт, поэтому здесь нет необходимости в вычислениях.

Я искал его еще немного, и оно действительно описано на MSDN , Как: создать объединение C / C ++ с использованием атрибутов (C # и Visual Basic) , так что, скорее всего, это будет поддерживаться в будущих версиях. Я не уверен насчет Моно.

18 голосов
/ 06 марта 2009

Преждевременная оптимизация - корень всего зла! @ Предложение Влада перебирать каждое число с плавающей запятой - гораздо более разумный ответ, чем переключение на байт []. Возьмите следующую таблицу времени выполнения для увеличения количества элементов (в среднем 50 прогонов):

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

Существует небольшая разница между ними для небольшого количества элементов. Как только вы попадаете в огромное количество элементов, время, потраченное на копирование с плавающего [] в байт [], значительно перевешивает преимущества.

Итак, давайте просто:

float[] data = new float[...];
foreach(float value in data)
{
    writer.Write(value);
}
12 голосов
/ 26 августа 2010

Есть способ избежать копирования и итерации памяти.

Вы можете использовать действительно уродливый хак, чтобы временно изменить массив на другой тип с помощью (небезопасных) манипуляций с памятью.

Я протестировал этот хак в 32- и 64-битных ОС, поэтому он должен быть переносимым.

Источник + пример использования поддерживается на https://gist.github.com/1050703, но для вашего удобства я также вставлю его сюда:

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

И использование:

var floats = new float[] {0, 1, 0, 1};
floats.AsByteArray(bytes =>
{
    foreach (var b in bytes)
    {
        Console.WriteLine(b);
    }
});
7 голосов
/ 06 марта 2009

Если вы не хотите, чтобы какое-либо преобразование происходило, я бы предложил Buffer.BlockCopy ().

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

Например:

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

Buffer.BlockCopy(floatArray, 0, byteArray, 0, byteArray.Length);
3 голосов
/ 06 марта 2009

Вам лучше позволить BinaryWriter сделать это за вас . Будет проходить итерация по всему набору данных независимо от того, какой метод вы используете, поэтому бессмысленно играть с байтами.

1 голос
/ 06 марта 2009

Хотя вы можете получить указатель byte*, используя unsafe и fixed, вы не можете преобразовать byte* в byte[], чтобы записывающее устройство приняло его в качестве параметра, не выполняя копирования данных. Что вы не хотите делать, так как это удвоит объем памяти и добавит дополнительную итерацию к неизбежной итерации, которую необходимо выполнить для вывода данных на диск.

Вместо этого вам все же лучше перебирать массив чисел с плавающей точкой и записывать каждый float в пишущий модуль индивидуально , используя метод Write(double). Это все еще будет быстро из-за буферизации внутри писателя. Смотрите sixlettervariables номера.

0 голосов
/ 06 марта 2009

У нас есть класс с именем LudicrousSpeedSerialization, и он содержит следующий небезопасный метод:

    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;
    }
0 голосов
/ 06 марта 2009

Несмотря на то, что он в основном выполняет цикл for за кулисами, он выполняет работу в одну строку

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