Задать вопрос
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);
        }
  • Вопрос задан
  • 2577 просмотров
Подписаться 2 Оценить 2 комментария
Решения вопроса 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 длиннее обычного последовательного прогона (в худшем случае).
Ответ написан
Ваш ответ на вопрос

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

Похожие вопросы