Каков самый быстрый способ преобразовать число с плавающей точкой [] в байт []?

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

  •  03-07-2019
  •  | 
  •  

Вопрос

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

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

РЕДАКТИРОВАТЬ:Тем критикам, которые кричат ​​о «преждевременной оптимизации»:Прежде чем оптимизировать, я проверил это с помощью профилировщика ANTS.Произошло значительное увеличение скорости, поскольку файл имеет кэш со сквозной записью, а размер массива с плавающей запятой точно соответствует размеру сектора на диске.Модуль записи двоичных файлов оборачивает дескриптор файла, созданный с помощью pinvokeбы использовать Win32 API.Оптимизация происходит, поскольку при этом уменьшается количество вызовов функций.

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

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

Это было полезно?

Решение

Если вы не хотите, чтобы какое-либо преобразование происходило, я бы предложил 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);

Другие советы

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

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

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

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

Преждевременная оптимизация – корень всех зол!Предложение @Vlad перебирать каждое число с плавающей запятой - гораздо более разумный ответ, чем переключение на byte[].Возьмите следующую таблицу времени выполнения для увеличения количества элементов (в среднем 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[] в byte[], намного перевешивает преимущества.

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

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

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

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

Я тестировал этот хак как в 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);
    }
});

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

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

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

У нас есть класс 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;
    }

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

byte[] byteArray = floatArray.Select(
                    f=>System.BitConverter.GetBytes(f)).Aggregate(
                    (bytes, f) => {List<byte> temp = bytes.ToList(); temp.AddRange(f); return temp.ToArray(); });
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top