MilkyCoder
@MilkyCoder
Гений

Как повысть скорость записи случайных блоков?

В указанном коде, если убрать махинации с курсором файла, скорость записи мегабайта составляет 20мс. Но если записывать блоки случайно, тогда запись мегабайта происходит за не приличные 800мс, почти секунда и это при то что у меня навороченный SSD. Не помогает даже шаманство с FileOptions.RandomAccess. При последовательной записи тоже меняется Position, почему если я его меняю вручную, тогда так сильно падает производительность. Разница в 40 раз. Посоветуйте что нибудь плз.

static string path = @"...\ConsoleApplication10\bin\Debug\1.dat";
        static int count = 1;
        static int len = 1024 * 1024;

        static void Test()
        {
            var rnd = new Random();
            var sw = Stopwatch.StartNew();

            var b = new byte[4];
            var fs = new FileStream(path, FileMode.OpenOrCreate, FileAccess.ReadWrite, FileShare.ReadWrite, 4096, FileOptions.RandomAccess | FileOptions.SequentialScan);

            if (fs.Length == 0)
            {
                fs.SetLength(len);
            }
            
            Console.WriteLine("Test started");

            for (var i = 0; i < len / 4; ++i)
            {
                var ind = 20;

                b[0] = (byte)ind;
                b[1] = (byte)(ind >> 8);
                b[2] = (byte)(ind >> 16);
                b[3] = (byte)(ind >> 24);

                //fs.Position = i * 4;
                //fs.Seek(i * 4, SeekOrigin.Begin);
                fs.Seek(rnd.Next(0, len / 4) * 4, SeekOrigin.Begin);

                fs.Write(b, 0, 4);
            }

            fs.Close();

            Console.WriteLine("Test end time - " + sw.ElapsedMilliseconds);
        }
  • Вопрос задан
  • 2562 просмотра
Решения вопроса 1
@mayorovp
В SqLite используется WAL (write-ahead log) для ускорения записи в БД. Вы можете сделать так же, превратив тем самым рандомные записи в последовательные.
Ответ написан
Пригласить эксперта
Ответы на вопрос 4
AlekseyNemiro
@AlekseyNemiro
full-stack developer
Можно попробовать использовать MemoryMappedFile:
string path = @"1.dat";
int len = 1024 * 1024;

var rnd = new Random();
var sw = Stopwatch.StartNew();

var b = new byte[4];

Console.WriteLine("Test started");

using (var map = MemoryMappedFile.CreateFromFile(path, FileMode.Create, path, len))
{
  using (var accessor = map.CreateViewAccessor())
  {
    for (var i = 0; i < len / 4; ++i)
    {
      b[0] = (byte)rnd.Next(0, 255);
      b[1] = (byte)rnd.Next(0, 255);
      b[2] = (byte)rnd.Next(0, 255);
      b[3] = (byte)rnd.Next(0, 255);

      accessor.WriteArray(rnd.Next(0, len / 4) * 4, b, 0, 4);
    }
  }
}
      
Console.WriteLine("Test end time - " + sw.ElapsedMilliseconds);
Console.ReadKey();
Ответ написан
dabrahabra
@dabrahabra
.NET Developer
Вы можете попробовать обойти кэш системы использую флаг WriteThrough: MSDN
НО! SSD дает огромную производительность в random read, но не очень дружит с random write.
Да, по сравнению с HDD он будет быстрее, но Вы заплатите продолжительностью его жизни. И вот почему (не гарантирую 100% точности):
  1. SSD хранит данные блоками по N байт
  2. если нужно изменить один байт в блоке: вычитывается весь блок в память, изменяется байт, очищается блок на SSD, из памяти блок записывается на SSD
  3. для повышении производительности и равномерного изнашивания при стирании и записи блока SSD записывает его по новому местоположению
  4. есть специальная команда TRIM которая дает SSD знать какие блоки уже не используются (были стерты) и могут быть переиспользованы


Соответственно когда Вы принуждаете диск к работе в random write - фактически он оперирует большими блоками, даже если вы пишете по байту.

На Вашем месте я бы положился на кэширование - вычитать все данные, модифицировать, записать на диск.
Ответ написан
Neuroware
@Neuroware
Программист в свободное от работы время
не знаю точно суть задачи, но если нужна высокая скорость можно смонтировать RAM диск нужного размера и гадить в него со скростью 8Гб в секунду и практически нулевыми задержками(ибо оно висит в RAM), после того как издевательства над файлом заканчиваются его можно сбросить на диск (уже последовательно и максимально быстро, насколько возможно диску)
Ответ написан
Комментировать
@Oxoron
Шарпер
Seek() назад гораздо медленнее Seek() вперед. Попробуйте заменить
fs.Seek(rnd.Next(0, len / 4) * 4, SeekOrigin.Begin);
на
fs.Seek(len-4*i, SeekOrigin.Begin);
, совсем разочаруетесь.

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

Войдите, чтобы написать ответ

Войти через центр авторизации
Похожие вопросы