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();
}
}
....
Здравствуйте, Sharov, Вы писали:
S>2) Как лучше считывать огромный файл( допустим надо посчитать кол-во вхождений определенного байта), памяти предостаточно.
Здравствуйте, Sharov, Вы писали:
S>Ничего лучше чем аллоцировать 2гб буффера и читать его через ReadByte я не придумал. Может бить его на чанки, и кажды поток будет обрабатывать свой чанк?
Тут ты упираешься в скорость чтения с диска, а не подсчёт байтиков. Будет ли чтение быстрее в многопотоке — большой вопрос, скорее нет.
Здравствуйте, ltc, Вы писали:
ltc>Тут ты упираешься в скорость чтения с диска, а не подсчёт байтиков. Будет ли чтение быстрее в многопотоке — большой вопрос, скорее нет.
Здравствуйте, ltc, Вы писали:
ltc>Здравствуйте, Sharov, Вы писали:
S>>2) Как лучше считывать огромный файл( допустим надо посчитать кол-во вхождений определенного байта), памяти предостаточно.
ltc>Memory mapped file.
Вариант, но тогда уж MemoryStream прокатил бы. Предполагалось, что весь файл в память грузить не желательно.
Здравствуйте, Sharov, Вы писали:
S>>>2) Как лучше считывать огромный файл( допустим надо посчитать кол-во вхождений определенного байта), памяти предостаточно.
ltc>>Memory mapped file.
S>Вариант, но тогда уж MemoryStream прокатил бы. Предполагалось, что весь файл в память грузить не желательно.
Ты почитай про memory mapped file, там все уже сделано.
Здравствуйте, Sharov, Вы писали:
S>Вот тут можно подробнее, что значит читать в несколько потоков, если у меня fs (currentpos в потоке ) не разделяемый?
Здравствуйте, romangr, Вы писали:
R>Здравствуйте, Sharov, Вы писали:
S>>Вот тут можно подробнее, что значит читать в несколько потоков, если у меня fs (currentpos в потоке ) не разделяемый?
R>MemoryMappedFile.CreateViewStream
Ясно, т.е. через filestream быстрее не сделать? А mmf уже отдельный разговор.
Здравствуйте, Sharov, Вы писали:
S>Ясно, т.е. через filestream быстрее не сделать? А mmf уже отдельный разговор.
Я думаю, что используя один FileStream быстрее не получится.
Но может и не нужно? Подобрать размер буфера, при котором скорость достаточна и все.
Зато алгоритм проще и меньше шансов накосячить.
Здравствуйте, Sharov, Вы писали:
S>Ясно, т.е. через filestream быстрее не сделать? А mmf уже отдельный разговор.
Ну конкретно в твоем примере можно как минимум заменить ReadByte на ReadAsync(Byte[], Int32, Int32, CancellationToken) с каким-то достаточно большим буфером и разнести чтение из файла и подсчет байтов на разные потоки.
Здравствуйте, 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 Гб/с.
Вероятно, где-то присутствуют какие-то задержки. Может быть, нет упреждающего чтения и его можно как-то включить.
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 на максимальной скорости. Он может в два с лишним раза быстрее.
Здравствуйте, 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 в тестах, но для такого тупого алгоритма нормально.
Здравствуйте, 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.
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);
Что-то я не понял этот момент. Они же тут в параллель двигают позицию в потоке туда-сюда. Как они не мешают друг другу?
В смысле работать-то оно будет, но точно ли оно считывает то, что нужно и почему так?
Здравствуйте, 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;
}
Здравствуйте, blp, Вы писали:
A>>Пока не знаю, как заставить читаться данные с моего SSD на максимальной скорости. blp>Пробовали FileOptions.SequentalScan (до кучи — FileOptions.Asynchronous)?
Не помогло. Оказалось, что если в CrystalDiskMark в параметрах теста выставить Queues=1, Threads=1, то накопитель выдаёт не больше гигабайта в секунду. Если либо queues, либо threads увеличивать, то скорость растёт и где-то в районе 16 достигает максимума.
Здравствуйте, alexzzzz, Вы писали: A>>Вероятно, где-то присутствуют какие-то задержки. Может быть, нет упреждающего чтения и его можно как-то включить. A>Или 4096-байтные страницы памяти дают слишком большой оверхед и большие двухмегабайтные помогли бы, но не знаю, как ОС об этом сообщить. A>Следующий вариант программы работает стабильно быстрее 800 Мб/с. Два буфера ― пока один заполняется данными, во втором ищутся нужные байты, потом буферы меняются местами. Оптимальный размер буферов почему-то в районе сотен килобайт. Если уменьшать или увеличивать, скорость падает. Функцию поиска ускорять нет смысла. Если её вообще убрать, скорость работы не увеличивается. A>
Скрытый текст
using System;
A>using System.Diagnostics;
A>using System.IO;
A>using System.Threading.Tasks;
[q]
A>internal class Program
A>{
A> private static async Task Main()
A> {
A> var filename = @"C:\Users\alexz\Desktop\H.VHDX";
A> var length = new FileInfo(filename).Length;
A> var stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read);
A> long result = 0;
A> var timer = Stopwatch.StartNew();
A> {
A> const int BUFFER_SIZE = 256 * 1024;
A> var buffers = new[] { new byte[BUFFER_SIZE], new byte[BUFFER_SIZE] };
A> var activeBufferIndex = 0;
A> var bytesRead = 0;
A> do
A> {
A> var task = stream.ReadAsync(buffers[activeBufferIndex], 0, buffers[activeBufferIndex].Length);
A> result += CountBytes(0, buffers[1 - activeBufferIndex], bytesRead);
A> bytesRead = await task;
A> activeBufferIndex = 1 - activeBufferIndex;
A> } while (bytesRead > 0);
A> }
A> timer.Stop();
A> Console.WriteLine(result);
A> Console.WriteLine(length * 1000 / timer.ElapsedMilliseconds / 1_000_000 + " Mb/s");
A> }
A> private static long CountBytes(byte reference, byte[] buffer, int size)
A> {
A> var count = 0;
A> for (var i = 0; i < size; i++)
A> {
A> if (buffer[i] == reference) count++;
A> }
A> return count;
A> }
A>}
[/q] A>Пока не знаю, как заставить читаться данные с моего SSD на максимальной скорости. Он может в два с лишним раза быстрее.
Неплохо, в два раза быстрее моей наивной реалзиации --
Два вопроса:
1)правильно ли я понимаю, что первый раз CountBytes будет итерироваться в холостую по пустому буферу?
2) а если мне надо посчитать кол-во нулевых байтов в файле, что-то типа 0x00? Мне тогда надо будет делать поправку на размер буфера?
Здравствуйте, Sharov, Вы писали:
S>Два вопроса: S>1)правильно ли я понимаю, что первый раз CountBytes будет итерироваться в холостую по пустому буферу? S>2) а если мне надо посчитать кол-во нулевых байтов в файле, что-то типа 0x00? Мне тогда надо будет делать поправку на размер буфера?
1. Нет, при первом проходе будет bytesRead=0 и холостой итерации не будет.
2. Никаких поправок делать не нужно.
Здравствуйте, romangr, Вы писали: R>Здравствуйте, romangr, Вы писали: R>>выдает на моем 1TB Samsung 970 EVO+ скорость в 2700 Mb/s R>>что, конечно, меньше чем 3500 в тестах, но для такого тупого алгоритма нормально.
Скрытый текст
R>
Скрытый текст
R>
R>class Program
R>{
R> static async Task Main(string[] args)
R> {
R> const int FILE_FLAG_NO_BUFFERING = 0x20000000;
R> const int BufferSize = 256 * 1024;
R> const string Path = @"c:\iso\myiso.iso";
R> const int TaskNumber = 16;
R> const byte Pattern = 42;
R> var totalLength = new FileInfo(Path).Length;
R> var timer = Stopwatch.StartNew();
R> var tasks = new Task<int>[TaskNumber];
R> for (int i = 0; i < TaskNumber; i++)
R> {
R> var options = (FileOptions) FILE_FLAG_NO_BUFFERING | FileOptions.SequentialScan;
R> var fs = new FileStream(Path, FileMode.Open, FileAccess.Read, FileShare.Read, BufferSize, options);
R> var position = i * BufferSize;
R> tasks[i] = Task.Run(() => Worker(fs, position, TaskNumber * BufferSize, BufferSize, Pattern));
R> }
R> var result = await Task.WhenAll(tasks);
R> var speed = totalLength * 1000 / timer.ElapsedMilliseconds / 1_000_000;
R> Console.WriteLine($"Read {totalLength} bytes for {timer.ElapsedMilliseconds / 1000.0} seconds. Speed is {speed} Mb/s");
R> Console.WriteLine($"Found {result.Sum()} occurences of byte {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);
R> var count = (int) Math.Min(size, totalLength - position);
R> while (count > 0)
R> {
R> var read = fs.Read(buffer, 0, buffer.Length);
R> for (int i = 0; i < read; i++)
R> {
R> if (buffer[i] == pattern)
R> {
R> result++;
R> }
R> }
R> count -= read;
R> }
R> position += step;
R> }
R> return result;
R> }
R>}
R>
Этот в 4 раза лучше моего -- Вылавливал косяк с переполнением в Worker, ибо у меня буффер может быть Int32.maxval, соотв. position и step должны быть long.
И почему Task.Run, а не Factory.StartNew ?
Здравствуйте, Sharov, Вы писали:
S>Этот в 4 раза лучше моего -- Вылавливал косяк с переполнением в Worker, ибо у меня буффер может быть Int32.maxval, соотв. position и step должны быть long. S>И почему Task.Run, а не Factory.StartNew ?
Безо всякого умысла, что первое в голову пришло, то и поставил. Таме еще косяк есть — нужно счетчик байт long делать, у меня там int, что не есть гуд для больших файлов.
Здравствуйте, romangr, Вы писали:
R>Здравствуйте, Sharov, Вы писали:
S>>Этот в 4 раза лучше моего -- Вылавливал косяк с переполнением в Worker, ибо у меня буффер может быть Int32.maxval, соотв. position и step должны быть long. S>>И почему Task.Run, а не Factory.StartNew ?
R>Безо всякого умысла, что первое в голову пришло, то и поставил.
Я сейчас не помню, но советую Factory использовать, а не Run. Run может не стартовать таску, или что-то вроде этого. Лень гуглить.
R>Таме еще косяк есть — нужно счетчик байт long делать, у меня там int, что не есть гуд для больших файлов.
Я завозился с переполнение position и step, это не увидел.
Здравствуйте, Sharov, Вы писали:
S>>>И почему Task.Run, а не Factory.StartNew ? R>>Безо всякого умысла, что первое в голову пришло, то и поставил. S>Я сейчас не помню, но советую Factory использовать, а не Run. Run может не стартовать таску, или что-то вроде этого. Лень гуглить.
Task.Run просто создаёт и запускает таск в пуле потоков и всё. У фабрики же имеется куча дополнительных настроек. Если они не нужны, рекомендуют Task.Run.
Ага, значит попутал с экземплярным методом Run (или Start?), когда таска может и не стартоваться. Были какие-то свои грабли, не рекомендовали использовать это API.
Здравствуйте, Sharov, Вы писали:
S>Ага, значит попутал с экземплярным методом Run (или Start?), когда таска может и не стартоваться. Были какие-то свои грабли, не рекомендовали использовать это API.
Может быть, имелось в виду, что фабрика и экземплярный Start позволяют явно задать планировщик, под которым таск будет выполняться, а у планировщиков может быть сложная логика по отправке тасков на исполнение и результата можно не дождаться? Какой-нибудь кастомный планировщик может копить задания в очереди до очередного новолуния с четверга на пятницу, а штатный TaskScheduler.Default отправляет таски на исполнение в пул потоков (хотя пул тоже не резиновый).
Или ещё можно соединять таски в иерархии родительских-дочерних. Тогда если кто-то из них выбросит исключение, другие могут не выполниться. В этом я не разбираюсь.
Здравствуйте, alexzzzz, Вы писали:
A>Может быть, имелось в виду, что фабрика и экземплярный Start позволяют явно задать планировщик, под которым таск будет выполняться, а у планировщиков может быть сложная логика по отправке тасков на исполнение и результата можно не дождаться? Какой-нибудь кастомный планировщик может копить задания в очереди до очередного новолуния с четверга на пятницу, а штатный TaskScheduler.Default отправляет таски на исполнение в пул потоков (хотя пул тоже не резиновый).
Была проблема, что я ожидал заверешния таска, т.е. был код с ContinueWith, а таска даже не начиналась.
A>Или ещё можно соединять таски в иерархии родительских-дочерних. Тогда если кто-то из них выбросит исключение, другие могут не выполниться. В этом я не разбираюсь.