Лучший / более быстрый способ заполнить большой массив в C # - PullRequest
11 голосов
/ 08 сентября 2011

У меня есть 3 * .dat файла (346 КБ, 725 КБ, 1762 КБ), которые заполнены json-строкой "больших" int-массивов.

Каждый раз, когда создается мой объект (несколько раз), явозьмите эти три файла и используйте JsonConvert.DeserializeObject для десериализации массивов в объект.

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

Каковы различные способы ускорить инициализацию этих объектов?

Ответы [ 3 ]

9 голосов
/ 08 сентября 2011

Самый быстрый способ - вручную сериализовать данные.

Простой способ сделать это - создать FileStream, а затем обернуть его в BinaryWriter / BinaryReader.

У вас есть доступ к функциям для записи основных структур данных (numbers, string, char, byte[] и char[]).

Простой способ записать int[] (ненужный, если он имеет фиксированный размер), добавив длину массива либо к int / long (в зависимости от размера, unsigned на самом деле не дает никаких преимуществ, поскольку массивы использовать подписанные типы данных для хранения их длины). А затем напишите все целые.

Два способа записать все целые будут следующими:
1. Просто зациклите весь массив.
2. Преобразуйте его в byte[] и запишите, используя BinaryWriter.Write(byte[])

Вот как вы можете реализовать их оба:

// Writing
BinaryWriter writer = new BinaryWriter(new FileStream(...));
int[] intArr = new int[1000];

writer.Write(intArr.Length);
for (int i = 0; i < intArr.Length; i++)
    writer.Write(intArr[i]);

// Reading
BinaryReader reader = new BinaryReader(new FileStream(...));
int[] intArr = new int[reader.ReadInt32()];

for (int i = 0; i < intArr.Length; i++)
    intArr[i] = reader.ReadInt32();

// Writing, method 2
BinaryWriter writer = new BinaryWriter(new FileStream(...));
int[] intArr = new int[1000];
byte[] byteArr = new byte[intArr.Length * sizeof(int)];
Buffer.BlockCopy(intArr, 0, byteArr, 0, intArr.Length * sizeof(int));

writer.Write(intArr.Length);
writer.Write(byteArr);

// Reading, method 2
BinaryReader reader = new BinaryReader(new FileStream(...));
int[] intArr = new int[reader.ReadInt32()];
byte[] byteArr = reader.ReadBytes(intArr.Length * sizeof(int));
Buffer.BlockCopy(byteArr, 0, intArr, 0, byteArr.Length);

Я решил проверить все это на массиве из 10000 целых чисел, и я провел тест 10000 раз.

В результате первый метод потребляет в моей системе в среднем 888200 нс (около 0,89 мс).
В то время как метод 2 потребляет в моей системе всего 568600 нс (в среднем 0,57 мс).

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

Очевидно, что метод 2 быстрее, чем метод 1, хотя, возможно, менее читабелен.

Еще одна причина, по которой метод 1 может быть лучше метода 2, заключается в том, что для метода 2 требуется вдвое больше свободной оперативной памяти, чем для данных, которые вы собираетесь записать (оригинал int[] и byte[], преобразованные из * 1035). *), когда речь идет об ограниченном объеме ОЗУ / очень больших файлах (речь идет о 512 МБ +), хотя в этом случае вы всегда можете сделать гибридное решение, например, записав 128 МБ за раз.

Обратите внимание, что метод 1 также требует этого дополнительного пространства, но поскольку он разделен на 1 операцию на элемент int[], он может освободить память намного раньше.

Как-то так, будет записывать 128 МБ int[] одновременно:

const int WRITECOUNT = 32 * 1024 * 1024; // 32 * sizeof(int)MB

int[] intArr = new int[140 * 1024 * 1024]; // 140 * sizeof(int)MB
for (int i = 0; i < intArr.Length; i++)
    intArr[i] = i;

byte[] byteArr = new byte[WRITECOUNT * sizeof(int)]; // 128MB

int dataDone = 0;

using (Stream fileStream = new FileStream("data.dat", FileMode.Create))
using (BinaryWriter writer = new BinaryWriter(fileStream))
{
    while (dataDone < intArr.Length)
    {
        int dataToWrite = intArr.Length - dataDone;
        if (dataToWrite > WRITECOUNT) dataToWrite = WRITECOUNT;
        Buffer.BlockCopy(intArr, dataDone, byteArr, 0, dataToWrite * sizeof(int));
        writer.Write(byteArr);
        dataDone += dataToWrite;
    }
}

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

6 голосов
/ 08 сентября 2011

Если у вас есть только несколько целых чисел, то использование JSON действительно будет неэффективным с точки зрения анализа. Вы можете использовать BinaryReader и BinaryWriter для эффективной записи двоичных файлов ... но мне не ясно, зачем вам нужно читать файл каждый раз, когда вы создаете объект. Почему каждый новый объект не может хранить ссылку на исходный массив, который был прочитан один раз? Или, если им нужно изменить данные, вы можете оставить один «канонический источник» и просто копировать этот массив в память каждый раз, когда создаете объект.

5 голосов
/ 08 сентября 2011

Самый быстрый способ создать байтовый массив из массива целых чисел - это использовать Buffer.BlockCopy

byte[] result = new byte[a.Length * sizeof(int)];
Buffer.BlockCopy(a, 0, result, 0, result.Length);
// write result to FileStream or wherever

Если вы сохраняете размер массива в первом элементе, вы можете использовать его снова для десериализации. Убедитесь, что все умещается в памяти, но, глядя на размер файла, оно должно быть.

var buffer = File.ReadAllBytes(@"...");
int size = BitConverter.ToInt32(buffer,0);
var result = new int[size];
Buffer.BlockCopy(buffer, 0, result, result.length);

Двоичный файл не читается человеком, но определенно быстрее, чем JSON.

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