FileStream, buffer, оптимальное чтение
От: Sharov Россия  
Дата: 23.01.20 13:29
Оценка: :)
Здравствуйте.

Ряд вопросов, возможно банальные, по FileStream:

1) что будет, если я в качестве буффера в конструкторе попрошу аллоцировать недоступное системе кол-во памяти, т.е. скажем 2гб (int bufSize), а системе доступно только 1гб и файла подкачки нет?
Аллоцирует на свое усмотрение или выкинет что-то типа OOM исключения?

2) Как лучше считывать огромный файл( допустим надо посчитать кол-во вхождений определенного байта), памяти предостаточно.
Ничего лучше чем аллоцировать 2гб буффера и читать его через ReadByte я не придумал. Может бить его на чанки, и кажды поток будет обрабатывать свой чанк?
Но тогда я буду дважы проходить по буферу -- 1 раз при копировании для соотв таски, второй для подсчета байта, плюс доп. аллокации памяти.

.....
using(FileStream  
            fileStream = new FileStream(fileName, ..., maxBuf,...))
        {

       var counter = 0;         
       var    b = fileStream.ReadByte();
       while (b != -1)
       {
             if (b == neededByte) counter++;
             b = fileStream.ReadByte();        

       }        
            
        }
....


Код выше как-то можно ускорить?

Заранее благодарю.
Кодом людям нужно помогать!
Re: FileStream, buffer, оптимальное чтение
От: ltc  
Дата: 23.01.20 13:36
Оценка: 5 (1) +7
Здравствуйте, Sharov, Вы писали:

S>2) Как лучше считывать огромный файл( допустим надо посчитать кол-во вхождений определенного байта), памяти предостаточно.


Memory mapped file.
Re: FileStream, buffer, оптимальное чтение
От: ltc  
Дата: 23.01.20 13:39
Оценка: +1
Здравствуйте, Sharov, Вы писали:

S>Ничего лучше чем аллоцировать 2гб буффера и читать его через ReadByte я не придумал. Может бить его на чанки, и кажды поток будет обрабатывать свой чанк?


Тут ты упираешься в скорость чтения с диска, а не подсчёт байтиков. Будет ли чтение быстрее в многопотоке — большой вопрос, скорее нет.
Re[2]: FileStream, buffer, оптимальное чтение
От: romangr Россия  
Дата: 23.01.20 13:42
Оценка:
Здравствуйте, ltc, Вы писали:

ltc>Тут ты упираешься в скорость чтения с диска, а не подсчёт байтиков. Будет ли чтение быстрее в многопотоке — большой вопрос, скорее нет.


Для SSD чтение в несколько потоков имеет смысл.
... << RSDN@Home 1.3.110 alpha 5 rev. 62>>
Re[2]: FileStream, buffer, оптимальное чтение
От: Sharov Россия  
Дата: 23.01.20 13:42
Оценка:
Здравствуйте, ltc, Вы писали:

ltc>Здравствуйте, Sharov, Вы писали:


S>>2) Как лучше считывать огромный файл( допустим надо посчитать кол-во вхождений определенного байта), памяти предостаточно.


ltc>Memory mapped file.


Вариант, но тогда уж MemoryStream прокатил бы. Предполагалось, что весь файл в память грузить не желательно.
Кодом людям нужно помогать!
Re[3]: FileStream, buffer, оптимальное чтение
От: ltc  
Дата: 23.01.20 13:44
Оценка: +1
Здравствуйте, Sharov, Вы писали:

S>>>2) Как лучше считывать огромный файл( допустим надо посчитать кол-во вхождений определенного байта), памяти предостаточно.


ltc>>Memory mapped file.


S>Вариант, но тогда уж MemoryStream прокатил бы. Предполагалось, что весь файл в память грузить не желательно.


Ты почитай про memory mapped file, там все уже сделано.
Re[3]: FileStream, buffer, оптимальное чтение
От: Sharov Россия  
Дата: 23.01.20 13:46
Оценка:
Здравствуйте, romangr, Вы писали:

R>Для SSD чтение в несколько потоков имеет смысл.


Вот тут можно подробнее, что значит читать в несколько потоков, если у меня fs (currentpos в потоке ) не разделяемый?
Кодом людям нужно помогать!
Re[4]: FileStream, buffer, оптимальное чтение
От: romangr Россия  
Дата: 23.01.20 13:52
Оценка: 5 (1)
Здравствуйте, Sharov, Вы писали:

S>Вот тут можно подробнее, что значит читать в несколько потоков, если у меня fs (currentpos в потоке ) не разделяемый?


MemoryMappedFile.CreateViewStream
... << RSDN@Home 1.3.110 alpha 5 rev. 62>>
Re[5]: FileStream, buffer, оптимальное чтение
От: Sharov Россия  
Дата: 23.01.20 13:58
Оценка:
Здравствуйте, romangr, Вы писали:

R>Здравствуйте, Sharov, Вы писали:


S>>Вот тут можно подробнее, что значит читать в несколько потоков, если у меня fs (currentpos в потоке ) не разделяемый?


R>MemoryMappedFile.CreateViewStream


Ясно, т.е. через filestream быстрее не сделать? А mmf уже отдельный разговор.
Кодом людям нужно помогать!
Re[6]: FileStream, buffer, оптимальное чтение
От: romangr Россия  
Дата: 23.01.20 14:08
Оценка: 5 (1) +2
Здравствуйте, Sharov, Вы писали:

S>Ясно, т.е. через filestream быстрее не сделать? А mmf уже отдельный разговор.


Я думаю, что используя один FileStream быстрее не получится.
Но может и не нужно? Подобрать размер буфера, при котором скорость достаточна и все.
Зато алгоритм проще и меньше шансов накосячить.
... << RSDN@Home 1.3.110 alpha 5 rev. 62>>
Re[6]: FileStream, buffer, оптимальное чтение
От: ltc  
Дата: 23.01.20 14:19
Оценка: 5 (1)
Здравствуйте, Sharov, Вы писали:

S>Ясно, т.е. через filestream быстрее не сделать? А mmf уже отдельный разговор.


Ну конкретно в твоем примере можно как минимум заменить ReadByte на ReadAsync(Byte[], Int32, Int32, CancellationToken) с каким-то достаточно большим буфером и разнести чтение из файла и подсчет байтов на разные потоки.
Re[3]: FileStream, buffer, оптимальное чтение
От: alexzzzz  
Дата: 23.01.20 20:57
Оценка:
Здравствуйте, romangr, Вы писали:

ltc>>Тут ты упираешься в скорость чтения с диска, а не подсчёт байтиков. Будет ли чтение быстрее в многопотоке — большой вопрос, скорее нет.

R>Для SSD чтение в несколько потоков имеет смысл.

Что в один поток, что в несколько, у меня не получилось разогнаться больше 500 Мб/с.

  Скрытый текст
using System;
using System.Diagnostics;
using System.IO;
using System.IO.MemoryMappedFiles;

internal unsafe class Program
{
    private static void Main()
    {
        var filename = @"C:\Users\alexz\Desktop\H.VHDX"; // ~100 Гб
        var mmf = MemoryMappedFile.CreateFromFile(filename, FileMode.Open);
        var accessor = mmf.CreateViewAccessor();
        var length = accessor.Capacity;

        var handle = accessor.SafeMemoryMappedViewHandle;
        byte* start = default;
        handle.AcquirePointer(ref start);

        var timer = Stopwatch.StartNew();
        var result = CountBytes(0, start, start + length);
        timer.Stop();

        Console.WriteLine(result);
        Console.WriteLine(length * 1000 / timer.ElapsedMilliseconds / 1_000_000 + " Mb/s");
    }

    private static long CountBytes(byte reference, byte* startPointer, byte* endPointer)
    {
        long count = 0;
        byte* pointer = startPointer;
        while (pointer < endPointer)
        {
            if (*pointer++ == reference) count++;
        }
        return count;
    }
}

Если файл уже в кэше, то эта простая программа считает количество нулевых байт со скоростью ~1,1 Гб/с. А если данные приходится читать с накопителя, то загрузка процессора падает и средняя скорость оказывается в районе 400-500 Мб/с, хотя мой NVMe может отдавать 2 Гб/с.

Вероятно, где-то присутствуют какие-то задержки. Может быть, нет упреждающего чтения и его можно как-то включить.
Отредактировано 23.01.2020 21:19 alexzzzz . Предыдущая версия . Еще …
Отредактировано 23.01.2020 20:59 alexzzzz . Предыдущая версия .
Re[4]: FileStream, buffer, оптимальное чтение
От: alexzzzz  
Дата: 24.01.20 00:09
Оценка: 24 (2) +1
A>Вероятно, где-то присутствуют какие-то задержки. Может быть, нет упреждающего чтения и его можно как-то включить.
Или 4096-байтные страницы памяти дают слишком большой оверхед и большие двухмегабайтные помогли бы, но не знаю, как ОС об этом сообщить.

Следующий вариант программы работает стабильно быстрее 800 Мб/с. Два буфера ― пока один заполняется данными, во втором ищутся нужные байты, потом буферы меняются местами. Оптимальный размер буферов почему-то в районе сотен килобайт. Если уменьшать или увеличивать, скорость падает. Функцию поиска ускорять нет смысла. Если её вообще убрать, скорость работы не увеличивается.

  Скрытый текст
using System;
using System.Diagnostics;
using System.IO;
using System.Threading.Tasks;

internal class Program
{
    private static async Task Main()
    {
        var filename = @"C:\Users\alexz\Desktop\H.VHDX";
        var length = new FileInfo(filename).Length;
        var stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read);

        long result = 0;

        var timer = Stopwatch.StartNew();
        {
            const int BUFFER_SIZE = 256 * 1024;
            var buffers = new[] { new byte[BUFFER_SIZE], new byte[BUFFER_SIZE] };
            var activeBufferIndex = 0;
            var bytesRead = 0;
            do
            {
                var task = stream.ReadAsync(buffers[activeBufferIndex], 0, buffers[activeBufferIndex].Length);
                result += CountBytes(0, buffers[1 - activeBufferIndex], bytesRead);
                bytesRead = await task;
                activeBufferIndex = 1 - activeBufferIndex;
            } while (bytesRead > 0);
        }
        timer.Stop();

        Console.WriteLine(result);
        Console.WriteLine(length * 1000 / timer.ElapsedMilliseconds / 1_000_000 + " Mb/s");
    }

    private static long CountBytes(byte reference, byte[] buffer, int size)
    {
        var count = 0;
        for (var i = 0; i < size; i++)
        {
            if (buffer[i] == reference) count++;
        }
        return count;
    }
}


Пока не знаю, как заставить читаться данные с моего SSD на максимальной скорости. Он может в два с лишним раза быстрее.
Отредактировано 24.01.2020 0:10 alexzzzz . Предыдущая версия .
Re[5]: FileStream, buffer, оптимальное чтение
От: blp  
Дата: 24.01.20 00:34
Оценка:
]

A>Пока не знаю, как заставить читаться данные с моего SSD на максимальной скорости.


Пробовали FileOptions.SequentalScan (до кучи — FileOptions.Asynchronous)?
Re[5]: FileStream, buffer, оптимальное чтение
От: romangr Россия  
Дата: 24.01.20 06:08
Оценка: 5 (1)
Здравствуйте, alexzzzz, Вы писали:

A>Пока не знаю, как заставить читаться данные с моего SSD на максимальной скорости. Он может в два с лишним раза быстрее.


Такой код
  Скрытый текст
class Program
{
        static async Task Main(string[] args)
        {
                const int BufferSize = 128 * 1024;
                const string Path = @"c:\iso\myiso.iso";
                const int TaskNumber = 16;
                const byte Pattern = 42;

                var totalLength = new FileInfo(Path).Length;
                var timer = Stopwatch.StartNew();
                var tasks = new Task<int>[TaskNumber];
                for (int i = 0; i < TaskNumber; i++)
                {
                        var fs = new FileStream(Path, FileMode.Open, FileAccess.Read, FileShare.Read, BufferSize, FileOptions.SequentialScan);
                        var position = totalLength / TaskNumber * i;
                        fs.Seek(position, SeekOrigin.Begin);
                        var length = i == TaskNumber - 1 ? totalLength - position : totalLength / TaskNumber;
                        tasks[i] = Task.Run(() => Worker(fs, length, Pattern));
                }
                var result = await Task.WhenAll(tasks);
                var speed = totalLength * 1000 / timer.ElapsedMilliseconds / 1_000_000;
                Console.WriteLine($"Read {totalLength} bytes for {timer.ElapsedMilliseconds / 1000} seconds. Speed is {speed} Mb/s");
                Console.WriteLine($"Found {result.Sum()} occurences of byte {Pattern}");
        }

        static int Worker(FileStream fs, long length, byte pattern)
        {
                var count = 0;
                while (length > 0)
                {
                        if (fs.ReadByte() == pattern)
                        {
                                count++;
                        }
                        --length;
                }
                return count;
        }
}

выдает на моем 1TB Samsung 970 EVO+ скорость в 2700 Mb/s
что, конечно, меньше чем 3500 в тестах, но для такого тупого алгоритма нормально.
... << RSDN@Home 1.3.110 alpha 5 rev. 62>>
Re[6]: FileStream, buffer, оптимальное чтение
От: romangr Россия  
Дата: 24.01.20 08:36
Оценка: 18 (2)
Здравствуйте, romangr, Вы писали:

R>выдает на моем 1TB Samsung 970 EVO+ скорость в 2700 Mb/s

R>что, конечно, меньше чем 3500 в тестах, но для такого тупого алгоритма нормально.

  Скрытый текст
class Program
{
    static async Task Main(string[] args)
    {
        const int FILE_FLAG_NO_BUFFERING = 0x20000000;
        const int BufferSize = 256 * 1024;
        const string Path = @"c:\iso\myiso.iso";
        const int TaskNumber = 16;
        const byte Pattern = 42;

        var totalLength = new FileInfo(Path).Length;
        var timer = Stopwatch.StartNew();
        var tasks = new Task<int>[TaskNumber];
        for (int i = 0; i < TaskNumber; i++)
        {
            var options = (FileOptions) FILE_FLAG_NO_BUFFERING | FileOptions.SequentialScan;
            var fs = new FileStream(Path, FileMode.Open, FileAccess.Read, FileShare.Read, BufferSize, options);
            var position = i * BufferSize;
            tasks[i] = Task.Run(() => Worker(fs, position, TaskNumber * BufferSize, BufferSize, Pattern));
        }
        var result = await Task.WhenAll(tasks);
        var speed = totalLength * 1000 / timer.ElapsedMilliseconds / 1_000_000;
        Console.WriteLine($"Read {totalLength} bytes for {timer.ElapsedMilliseconds / 1000.0} seconds. Speed is {speed} Mb/s");
        Console.WriteLine($"Found {result.Sum()} occurences of byte {Pattern}");
    }

    private static int Worker(FileStream fs, long position, int step, int size, byte pattern)
    {
        var totalLength = fs.Length;
        var result = 0;
        var buffer = new byte[Math.Min(1024, size)];
        while (position < totalLength)
        {
            fs.Seek(position, SeekOrigin.Begin);
            var count = (int) Math.Min(size, totalLength - position);
            while (count > 0)
            {
                var read = fs.Read(buffer, 0, buffer.Length);
                for (int i = 0; i < read; i++)
                {
                    if (buffer[i] == pattern)
                    {
                        result++;
                    }
                }
                count -= read;
            }
            position += step;
        }
        return result;
    }
}

После небольшого допиливания алгоритма теперь выдает 3450 Mb/s.
... << RSDN@Home 1.3.110 alpha 5 rev. 62>>
Re[7]: FileStream, buffer, оптимальное чтение
От: fmiracle  
Дата: 24.01.20 09:03
Оценка:
Здравствуйте, romangr, Вы писали:


R> tasks[i] = Task.Run(() => Worker(fs, position, TaskNumber * BufferSize, BufferSize, Pattern));

R>...
R> private static int Worker(FileStream fs, long position, int step, int size, byte pattern)
R> {
R> var totalLength = fs.Length;
R> var result = 0;
R> var buffer = new byte[Math.Min(1024, size)];
R> while (position < totalLength)
R> {
R> fs.Seek(position, SeekOrigin.Begin);

Что-то я не понял этот момент. Они же тут в параллель двигают позицию в потоке туда-сюда. Как они не мешают друг другу?
В смысле работать-то оно будет, но точно ли оно считывает то, что нужно и почему так?
Re[8]: FileStream, buffer, оптимальное чтение
От: romangr Россия  
Дата: 24.01.20 09:24
Оценка: 5 (1) +1
Здравствуйте, fmiracle, Вы писали:

F>Что-то я не понял этот момент. Они же тут в параллель двигают позицию в потоке туда-сюда. Как они не мешают друг другу?

F>В смысле работать-то оно будет, но точно ли оно считывает то, что нужно и почему так?

Делаем X задач, для каждой устанавливаем начальную позицию
0, BufferSize, 2 * BufferSize, 3 * BufferSize и т.д.
каждая читает из стрима BufferSize байт.
когда каждая прочитает свой кусок, сдвигаем позицию для каждой на BufferSize * X.
В результате у каждой свой кусок, и они не пересекаются между собой.

Можно Worker упростить чтобы проще понять было, но работать будет медленнее.
private static int Worker2(FileStream fs, long position, int step, int size, byte pattern)
{
    var totalLength = fs.Length;
    var result = 0;
    while (position < totalLength)
    {
        fs.Seek(position, SeekOrigin.Begin);
        var count = (int) Math.Min(size, totalLength - position);
        while (count > 0)
        {
            if (fs.ReadByte() == pattern)
            {
                result++;
            }
            --count;
        }
        position += step;
    }
    return result;
}
... << RSDN@Home 1.3.110 alpha 5 rev. 62>>
Re[9]: FileStream, buffer, оптимальное чтение
От: fmiracle  
Дата: 24.01.20 09:36
Оценка:
Здравствуйте, romangr, Вы писали:

R>Делаем X задач, для каждой устанавливаем начальную позицию


Да, я пропустил что у каждой задачи свой поток. Показалось, что один общий
Re[6]: FileStream, buffer, оптимальное чтение
От: alexzzzz  
Дата: 24.01.20 12:40
Оценка:
Здравствуйте, blp, Вы писали:

A>>Пока не знаю, как заставить читаться данные с моего SSD на максимальной скорости.

blp>Пробовали FileOptions.SequentalScan (до кучи — FileOptions.Asynchronous)?

Не помогло. Оказалось, что если в CrystalDiskMark в параметрах теста выставить Queues=1, Threads=1, то накопитель выдаёт не больше гигабайта в секунду. Если либо queues, либо threads увеличивать, то скорость растёт и где-то в районе 16 достигает максимума.
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.