Проблема производительности при сериализации многомерных массивов с использованием BinaryFormatter в .NET - PullRequest
0 голосов
/ 09 ноября 2011

Я использую BinaryFormatter для сериализации довольно простого многомерного массива с плавающей точкой, хотя я подозреваю, что проблема возникает с любыми примитивными типами. Мой многомерный массив содержит 10000x16 операций с плавающей запятой (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-массив в 1D-массив и сериализовать его и размеры. Другой вариант, который я рассматриваю, - это переключение на protobuf-net.

1 Ответ

1 голос
/ 25 января 2012

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

            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 [] в качестве байта [], поскольку структура памяти одинакова.

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

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

...