Проблема с производительностью при сериализации многомерных массивов с использованием BinaryFormatter в .NET

#.net #performance #serialization #multidimensional-array #binaryformatter

#.net #Производительность #сериализация #многомерный массив #binaryformatter

Вопрос:

Я использую BinaryFormatter для сериализации довольно простого многомерного массива с плавающими значениями, хотя я подозреваю, что проблема возникает с любыми примитивными типами. Мой многомерный массив содержит значения с плавающей точкой 10000×16 (160 Кб), а сериализация на моем ПК выполняется со скоростью ~ 8 МБ / с (60-секундная тестовая запись ~ 500 МБ на SSD-накопитель). Код:

         Stopwatch stopwatch = new Stopwatch();

        float[,] data = new float[10000 , 16];  // Two-dimensional array of 160,000 floats.
        // OR
        float[]  data = new float[10000 * 16];  // One-dimensional array of 160,000 floats.

        var formatter = new BinaryFormatter();
        var stream = new FileStream("C:\Temp\test_serialization.data", FileMode.Create, FileAccess.Write);

        // Serialize to disk the array 1000 times.
        stopwatch.Reset();
        stopwatch.Start();
        for (int i = 0; i < 1000; i  )
        {
            formatter.Serialize(stream, data);
        }
        stream.Close();
        stopwatch.Stop();

        TimeSpan ts = stopwatch.Elapsed;

        // Format and display the TimeSpan value.
        string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:000}",
            ts.Hours, ts.Minutes, ts.Seconds,
            ts.Milliseconds);
        Console.WriteLine("Runtime "   elapsedTime);
        var info = new FileInfo(stream.Name);
        Console.WriteLine("Speed: {0:0.00} MB/s", info.Length / ts.TotalSeconds / 1024.0 / 1024.0);
  

При выполнении того же действия, но с использованием одномерного массива из 160 тыс. плавающих значений, тот же объем данных сериализуется на диск со скоростью ~ 179 МБ / с. Более чем в 20 раз быстрее! Почему сериализация двумерного массива с использованием BinaryFormatter выполняется так плохо?Базовое хранилище для двух массивов в памяти должно быть идентичным. (Я выполнил небезопасный собственный pin_ptr и копирование в 2D-массивы и из них в C / CLI).

Хакерским решением было бы реализовать ISerializable и выполнить memcopy (небезопасное / ptr закрепление / block memcopy) 2D-массив в одномерный массив и сериализовать его и размеры. Другой вариант, который я рассматриваю, — это переключение на protobuf-net .

Комментарии:

1. Это тратит слишком много времени на размышления. Массивы создают проблемы, поскольку они ковариантны в .NET. Вы можете ускорить это с помощью неровных массивов.

Ответ №1:

Нет необходимости отказываться от вашей структуры данных или копировать значения, вы можете использовать следующий код для достижения такой же производительности:

             fixed (float* ptr = data)
            {
                byte* arr = (byte*)ptr;
                int size = sizeof(float);

                for (int j = 0; j < data.Length * size; j  )
                {
                    stream.WriteByte(arr[j]);
                }
            }
  

По сути, вы сами пишете выходной поток, и, как вы сказали, вы просто используете float[] в качестве byte[], поскольку структура памяти такая же.

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

Если у вас есть подобные базовые потребности, я бы настоятельно рекомендовал использовать protobuf.однако net. Разработка замедлилась и базировалась на одном парне, так что это довольно рискованно (когда я попытался помочь с проблемой производительности, он даже не потрудился посмотреть изменения, которые я предложил внести). Однако, если вы хотите сериализовать сложные структуры данных, двоичная сериализация будет не намного медленнее, чем protobuf, хотя последняя официально не поддерживается на платформе .NET (Google выпустил код для нее для Java, Python и C ).