BinaryWriter
От: tapatoon  
Дата: 02.11.24 06:25
Оценка:
Пишу в файл данные несколько гигабайт. Пишу небольшими порциями по нескольку байт
using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite))
{
    var bw = new BinaryWriter(fileStream);
    while (byte[] data = get_data())
    {
        bw.Write(data);
    }
}


Файл растёт максимум до 100Мб, дальше рост прекращается. В дебаге размер стрима в это время больше гигабайта. Где остальное? В свопе чтоль?
fileStream.Flush(true) добавил на каждый мегабайт. Но поведение по умолчанию удивило.
Центр ИПсО Сил Специальных Операций
Re: BinaryWriter
От: Sinclair Россия https://github.com/evilguest/
Дата: 02.11.24 15:32
Оценка: 6 (1)
Здравствуйте, tapatoon, Вы писали:

T>Пишу в файл данные несколько гигабайт. Пишу небольшими порциями по нескольку байт

T>
T>using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite))
T>{
T>    var bw = new BinaryWriter(fileStream);
T>    while (byte[] data = get_data())
T>    {
T>        bw.Write(data);
T>    }
T>}
T>

Обратите внимание на то, что у вас здесь нет ни Dispose, ни flush для bw. Возможно, в момент fileStream.Dispose bw ещё не все данные переложены из его внутреннего буфера в стрим.
T>Файл растёт максимум до 100Мб, дальше рост прекращается. В дебаге размер стрима в это время больше гигабайта. Где остальное? В свопе чтоль?
Я бы поставил на отставание обновления атрибутов файла. ФС не обязана обновлять их до того, как файл закроется.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re: BinaryWriter
От: gandjustas Россия http://blog.gandjustas.ru/
Дата: 02.11.24 22:03
Оценка: 1 (1)
Здравствуйте, tapatoon, Вы писали:

T>Файл растёт максимум до 100Мб, дальше рост прекращается. В дебаге размер стрима в это время больше гигабайта. Где остальное? В свопе чтоль?


Я обращал внимание на такое поведение даже не в дотнете. Если смотреть лог-файлы, которые пишутся постоянно и много, что часто случается так, что в файле уже миллионы строк и даже блокнот с vscode оказываются его открывать, а проводник все еще показывает размер меньше мегабайта.

Возможно это особенность именно проводника, а не записи. Так как в тот же момент получение размера файла в powershell работало корректно
Re: BinaryWriter
От: Слава  
Дата: 03.11.24 02:22
Оценка: +1 -1 :)
Здравствуйте, tapatoon, Вы писали:

T>Пишу в файл данные несколько гигабайт. Пишу небольшими порциями по нескольку байт

T>Файл растёт максимум до 100Мб, дальше рост прекращается. В дебаге размер стрима в это время больше гигабайта. Где остальное? В свопе чтоль?
T>fileStream.Flush(true) добавил на каждый мегабайт. Но поведение по умолчанию удивило.

Я бы заранее аллоцировал файл, насколько это возможно. Чтобы избежать фрагментации.
Re[2]: BinaryWriter
От: tapatoon  
Дата: 03.11.24 04:55
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Обратите внимание на то, что у вас здесь нет ни Dispose, ни flush для bw. Возможно, в момент fileStream.Dispose bw ещё не все данные переложены из его внутреннего буфера в стрим.

Действительно, ох уже мне эти disposable... спасибо

S>Я бы поставил на отставание обновления атрибутов файла. ФС не обязана обновлять их до того, как файл закроется.

Если флэшить файловый стрим, всё данные о размере обновляются, хоть файл и открыт. Может дисковый кэш используется
Переделал на винапи — поведение с размером файла такое же. Только быстрее раз в 100)
Центр ИПсО Сил Специальных Операций
Re[2]: BinaryWriter
От: Pavel Dvorkin Россия  
Дата: 03.11.24 05:08
Оценка: +1
Здравствуйте, gandjustas, Вы писали:

G>Я обращал внимание на такое поведение даже не в дотнете. Если смотреть лог-файлы, которые пишутся постоянно и много, что часто случается так, что в файле уже миллионы строк и даже блокнот с vscode оказываются его открывать, а проводник все еще показывает размер меньше мегабайта.


G>Возможно это особенность именно проводника, а не записи. Так как в тот же момент получение размера файла в powershell работало корректно


Нет, не проводника.

Я в FAR иногда наблюдаю, как идет запись в файл. FAR показывает, что размер не растет, хотя процесс идет благополучно.
А вот если нажать F3 и тут же выйти, но FAR показывает резко увеличившийся размер.
With best regards
Pavel Dvorkin
Re: BinaryWriter
От: Qulac Россия  
Дата: 03.11.24 07:36
Оценка:
Здравствуйте, tapatoon, Вы писали:

T>Пишу в файл данные несколько гигабайт. Пишу небольшими порциями по нескольку байт

T>
T>using (var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.ReadWrite))
T>{
T>    var bw = new BinaryWriter(fileStream);
T>    while (byte[] data = get_data())
T>    {
T>        bw.Write(data);
T>    }
T>}
T>


T>Файл растёт максимум до 100Мб, дальше рост прекращается. В дебаге размер стрима в это время больше гигабайта. Где остальное? В свопе чтоль?

T>fileStream.Flush(true) добавил на каждый мегабайт. Но поведение по умолчанию удивило.

Воспроизвел у себя, все ок — файл пишется, более 1 гб
Программа – это мысли спрессованные в код
Re[3]: BinaryWriter
От: Sinclair Россия https://github.com/evilguest/
Дата: 04.11.24 15:23
Оценка: 2 (1) +3
Здравствуйте, tapatoon, Вы писали:

T>Действительно, ох уже мне эти disposable... спасибо

1. Можно ставить using сразу на BinaryWriter. Для удобства он автоматически закрывает stream (чтобы это отключить, нужно использовать специальный конструктор):
using (var bw = new BinaryWriter(new FileStream(filePath, FileMode.Create, FileAccess.Write)))
    while (byte[] data = get_data())
        bw.Write(data);

2. А зачем вам вообще BinaryWriter? Вы же пишете сырые байты. BinaryWriter нужен для записи структурированных данных.
using (var stream = new FileStream(filePath, FileMode.Create, FileAccess.Write))
    while (byte[] data = get_data())
        stream.Write(data);


T>Если флэшить файловый стрим, всё данные о размере обновляются, хоть файл и открыт.

Ну вот как раз флэш, помимо сброса внутреннего буфера FileStream в системный file handle, ещё и делает FlushOSBuffer. Под виндой это вызов FlushFileBuffers, который как раз не только сбрасывает внутренний буфер, но и обновляет метаданные.
T>Переделал на винапи — поведение с размером файла такое же. Только быстрее раз в 100)
Винапи делает программу непортабельной.
Такая разница в скорости означает, что вы как-то неправильно применяете классы из System.IO. Попробуйте применить их правильно, и получить перформанс сравнимый с винапи.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[4]: BinaryWriter
От: tapatoon  
Дата: 05.11.24 10:11
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Такая разница в скорости означает, что вы как-то неправильно применяете классы из System.IO. Попробуйте применить их правильно, и получить перформанс сравнимый с винапи.

Да, перемудрил, сначала разные типы приходили
Без BinaryWriter медленнее в 1,5 раз. Многовато для передачи указателя
Центр ИПсО Сил Специальных Операций
Re[5]: BinaryWriter
От: Sinclair Россия https://github.com/evilguest/
Дата: 08.11.24 04:19
Оценка:
Здравствуйте, tapatoon, Вы писали:
T>Без BinaryWriter медленнее в 1,5 раз. Многовато для передачи указателя
Странно. Показывайте код, что с чем сравниваете.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[6]: BinaryWriter
От: tapatoon  
Дата: 13.11.24 05:31
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Странно. Показывайте код, что с чем сравниваете.

В идеальном мире, где мы передаём оптимальный размер буфера (у меня это 4КБ) FileStream даже быстрее в пределах погрешности. Но в разы просаживается если писать по нескольку байт.
В общем ничего неового, стримы и сериализаторы — роскошь при работе с большими объёмами данных. В моём случае можно легко вручную собрать байтовый массив и это прекрасно
Центр ИПсО Сил Специальных Операций
Re[7]: BinaryWriter
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 13.11.24 07:16
Оценка: 81 (2)
Здравствуйте, tapatoon, Вы писали:

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


S>>Странно. Показывайте код, что с чем сравниваете.

T>В идеальном мире, где мы передаём оптимальный размер буфера (у меня это 4КБ) FileStream даже быстрее в пределах погрешности. Но в разы просаживается если писать по нескольку байт.
T>В общем ничего неового, стримы и сериализаторы — роскошь при работе с большими объёмами данных. В моём случае можно легко вручную собрать байтовый массив и это прекрасно

C# FileStream : Optimal buffer size for writing large files?
и солнце б утром не вставало, когда бы не было меня
Re[7]: BinaryWriter
От: Sinclair Россия https://github.com/evilguest/
Дата: 13.11.24 11:24
Оценка: +2
Здравствуйте, tapatoon, Вы писали:

T>В идеальном мире, где мы передаём оптимальный размер буфера (у меня это 4КБ) FileStream даже быстрее в пределах погрешности. Но в разы просаживается если писать по нескольку байт.

T>В общем ничего неового, стримы и сериализаторы — роскошь при работе с большими объёмами данных. В моём случае можно легко вручную собрать байтовый массив и это прекрасно
Повторюсь: покажите код. Что у вас там быстрее чего в полтора раза?
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[8]: BinaryWriter
От: tapatoon  
Дата: 14.11.24 04:44
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Повторюсь: покажите код. Что у вас там быстрее чего в полтора раза?

Я ж написал — при оптимальном размере буфера в 4КБ разницы нет
Центр ИПсО Сил Специальных Операций
Re[9]: BinaryWriter
От: Sinclair Россия https://github.com/evilguest/
Дата: 14.11.24 05:39
Оценка:
Здравствуйте, tapatoon, Вы писали:
T>Я ж написал — при оптимальном размере буфера в 4КБ разницы нет
Это не "оптимальный" размер буфера, а размер по умолчанию. Вот мне и интересно, что вы такое там намерили, что переделка его "на винапи" ускорила работу в 100 раз, или хотя бы в полтора.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Re[10]: BinaryWriter
От: tapatoon  
Дата: 14.11.24 06:17
Оценка:
Здравствуйте, Sinclair, Вы писали:

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

T>>Я ж написал — при оптимальном размере буфера в 4КБ разницы нет
S>Это не "оптимальный" размер буфера, а размер по умолчанию. Вот мне и интересно, что вы такое там намерили, что переделка его "на винапи" ускорила работу в 100 раз, или хотя бы в полтора.

В коде используются внутренние либы, если оставить суть, получится то, что я уже приводил. Но раз Вы настаиваете, приведу ешё раз:
using (FileStream file = new FileStream(filePath...)) // или Kernel32.CreateFile
{
    long bytesWritten = 0;
    do
    {
    var data = getData();
        file.Write(data, 0, data.Length); // или Kernel32.WriteFile
        bytesWritten += data.Length;
    }
    while (continueWrite(bytesWritten));
}


Когда я переписал на винапи, я накапливал по 4Кб и только тогда вызывал WriteFile. А как я уже писал, данные приходили небольшими порциями, а сначала и вовсе разных типов, поэтому была связка FileStream + BinaryWriter. Разница — сотни раз.
Потом Вы мне указали на лишний BinaryWriter — убрал. Разница стала полтора раза.
Потом Вы меня попросили код. Тут до меня дошло, что в FileStream передаются небольшие порции, а так не честно. Сделал размер буферов одинаковыми и FileStream молодцом, даже победил) О чём я и написал.

Итог, разница WriteFile с переданным буфером в 4Кб и
1) FileStream + BinaryWriter + запись небольшими порциями — сотни раз
2) FileStream + запись небольшими порциями — в разы (до 10 если отдавать по 1 байту)
3) FileStream + запись в него по 4Кб — одинаковая скорость
Центр ИПсО Сил Специальных Операций
Отредактировано 14.11.2024 6:20 tapatoon . Предыдущая версия .
Re[7]: BinaryWriter
От: Философ Ад http://vk.com/id10256428
Дата: 14.11.24 08:29
Оценка:
Здравствуйте, tapatoon, Вы писали:

T>В идеальном мире, где мы передаём оптимальный размер буфера (у меня это 4КБ) FileStream даже быстрее в пределах погрешности. Но в разы просаживается если писать по нескольку байт.


У меня знакомый когда-то (во времена Pentium-4) тоже придумал записывать по несколько байт. Делали это с помощью CreateFile() и WriteFile(). Ему тогда не приходило в голову, что вызов функции — это дорого, и тем более дорого в тысячи раз дороже, если это вызов системной функции, которая SysCall или SysEnter делает. Тогда у нас вроде-бы килобайты в секунду получались.

Дело не в стримах, а в том, что именно ты делаешь. Тащемта и стрим может быть сильно разным, и даже создан с разными параметрами.
Всё сказанное выше — личное мнение, если не указано обратное.
Re[2]: BinaryWriter
От: Философ Ад http://vk.com/id10256428
Дата: 14.11.24 08:37
Оценка:
Здравствуйте, gandjustas, Вы писали:

G>...а проводник все еще показывает размер меньше мегабайта.

G>Возможно это особенность именно проводника, а не записи. Так как в тот же момент получение размера файла в powershell работало корректно

Проводник к этому не имеет отношения. Дело в том, как именно получается размер файла: можно с помощью FindFirst()/FindNext() читать директорию, тем самым получая размеры файлов, а можно открыть файл для чтения атрибутов. Проводник делает первое.
Чтобы убедиться, что дело не в проводнике, скачай FAR — он сделает ровно тоже самое что и проводник.
Всё сказанное выше — личное мнение, если не указано обратное.
Re[8]: BinaryWriter
От: tapatoon  
Дата: 14.11.24 09:44
Оценка:
Здравствуйте, Философ, Вы писали:

Ф>У меня знакомый когда-то (во времена Pentium-4) тоже придумал записывать по несколько байт. Делали это с помощью CreateFile() и WriteFile(). Ему тогда не приходило в голову, что вызов функции — это дорого, и тем более дорого в тысячи раз дороже, если это вызов системной функции, которая SysCall или SysEnter делает. Тогда у нас вроде-бы килобайты в секунду получались.

Тогда придумали стримы, которые должны просто копить данные и в нужное время флэшить. И сериализаторы над ними, чтобы разные типы удобно обрабатывать. Но блин, почему так медленно
Центр ИПсО Сил Специальных Операций
Отредактировано 14.11.2024 9:45 tapatoon . Предыдущая версия .
Re[11]: BinaryWriter
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 14.11.24 10:19
Оценка:
Здравствуйте, tapatoon, Вы писали:


T>В коде используются внутренние либы, если оставить суть, получится то, что я уже приводил. Но раз Вы настаиваете, приведу ешё раз:

T>
T>using (FileStream file = new FileStream(filePath...)) // или Kernel32.CreateFile
T>{
T>    long bytesWritten = 0;
T>    do
T>    {
T>    var data = getData();
T>        file.Write(data, 0, data.Length); // или Kernel32.WriteFile
T>        bytesWritten += data.Length;
T>    }
T>    while (continueWrite(bytesWritten));
T>}
T>


T>Когда я переписал на винапи, я накапливал по 4Кб и только тогда вызывал WriteFile. А как я уже писал, данные приходили небольшими порциями, а сначала и вовсе разных типов, поэтому была связка FileStream + BinaryWriter. Разница — сотни раз.

T>Потом Вы мне указали на лишний BinaryWriter — убрал. Разница стала полтора раза.
T>Потом Вы меня попросили код. Тут до меня дошло, что в FileStream передаются небольшие порции, а так не честно. Сделал размер буферов одинаковыми и FileStream молодцом, даже победил) О чём я и написал.

T>Итог, разница WriteFile с переданным буфером в 4Кб и

T>1) FileStream + BinaryWriter + запись небольшими порциями — сотни раз
T>2) FileStream + запись небольшими порциями — в разы (до 10 если отдавать по 1 байту)
T>3) FileStream + запись в него по 4Кб — одинаковая скорость

Вообще то для буферизации стрима есть BufferedStream

Ну и оптимальным у народа получалось порядка 256Кб
и солнце б утром не вставало, когда бы не было меня
Re[11]: BinaryWriter
От: Sinclair Россия https://github.com/evilguest/
Дата: 14.11.24 10:22
Оценка:
Здравствуйте, tapatoon, Вы писали:

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


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

T>>>Я ж написал — при оптимальном размере буфера в 4КБ разницы нет
S>>Это не "оптимальный" размер буфера, а размер по умолчанию. Вот мне и интересно, что вы такое там намерили, что переделка его "на винапи" ускорила работу в 100 раз, или хотя бы в полтора.

T>В коде используются внутренние либы, если оставить суть, получится то, что я уже приводил. Но раз Вы настаиваете, приведу ешё раз:

T>
T>using (FileStream file = new FileStream(filePath...)) // или Kernel32.CreateFile
T>{
T>    long bytesWritten = 0;
T>    do
T>    {
T>    var data = getData();
T>        file.Write(data, 0, data.Length); // или Kernel32.WriteFile
T>        bytesWritten += data.Length;
T>    }
T>    while (continueWrite(bytesWritten));
T>}
T>


T>Когда я переписал на винапи, я накапливал по 4Кб и только тогда вызывал WriteFile:

https://github.com/microsoft/referencesource/blob/master/mscorlib/system/io/filestream.cs#L398
Это как раз то место, где FileStream накапливает по 4кб, и только тогда вызывает WriteFile.
T>Потом Вы меня попросили код. Тут до меня дошло, что в FileStream передаются небольшие порции, а так не честно. Сделал размер буферов одинаковыми
Что значит "сделал размеры буферов одинаковыми"? Вы сконструировали FileStream с нестандартным размером буфера?

T>2) FileStream + запись небольшими порциями — в разы (до 10 если отдавать по 1 байту)

T>3) FileStream + запись в него по 4Кб — одинаковая скорость
Очень, очень странно.
Уйдемте отсюда, Румата! У вас слишком богатые погреба.
Отредактировано 14.11.2024 11:24 Sinclair . Предыдущая версия .
Re[11]: BinaryWriter
От: pilgrim_ Россия  
Дата: 14.11.24 11:33
Оценка: 6 (1) +1
Здравствуйте, tapatoon, Вы писали:

T>Итог, разница WriteFile с переданным буфером в 4Кб и

T>1) FileStream + BinaryWriter + запись небольшими порциями — сотни раз
T>2) FileStream + запись небольшими порциями — в разы (до 10 если отдавать по 1 байту)

BinaryWriter в методе Write принимающий массив байтов просто делегирует вызов в основной поток (в данном случае FileStream.Write), т.е. получается что эта разница на порядки между 1) и 2) только за счёт косвенного вызова FileStream.Write? Выглядит фантастикой.


T>3) FileStream + запись в него по 4Кб — одинаковая скорость


Так то разница между 2) и 3), при записи небольшими порциями (менее внутреннего буфера FileStream'а, дефолтный размер которого 4k), конечно есть — FileStream сначала наполняет свой внутренний буфер (т.е. происходит копирование из переданного буфера во внутренний; при записи одного байта в .NET Fw создавался временный массив байтов размером 1b (при записи 1gb — "гигабайт" временных объектов) который уже передавался во Write(byte[]...), в .NET Core оптимизировали — сразу пишут байт во внутренний буфер). При записи массива байт размером не менее внутреннего буфера — запись сразу уходит в нейтив.

Наблюдаемый мной итог — при записи буферов одинакового размера используя FileStream и WriteFile (Win32) разницы между ними практически нет (что подтверждает 3) ), а при записи огромного количества маленьких буферов (1b в пределе) WriteFile может быть и проиграет (он тоже имеет внутренний буфер, если специально не указано при открытии файла) за счёт стоимости системного вызова.
Re[12]: BinaryWriter
От: pilgrim_ Россия  
Дата: 14.11.24 11:35
Оценка:
Здравствуйте, Serginio1, Вы писали:

S>Вообще то для буферизации стрима есть BufferedStream


S> Ну и оптимальным у народа получалось порядка 256Кб



FileStream уже имеет встроенную поддержку буферизации, ему этот класс не нужен.
Re[13]: BinaryWriter
От: Serginio1 СССР https://habrahabr.ru/users/serginio1/topics/
Дата: 14.11.24 12:13
Оценка:
Здравствуйте, pilgrim_, Вы писали:

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


S>>Вообще то для буферизации стрима есть BufferedStream


S>> Ну и оптимальным у народа получалось порядка 256Кб



_>FileStream уже имеет встроенную поддержку буферизации, ему этот класс не нужен.


Я знаю. Он использует BinaryWriter для буферизации стрима.
А 256кб это буфер FileStream у людей получилось C# FileStream : Optimal buffer size for writing large files?
и солнце б утром не вставало, когда бы не было меня
Re[14]: BinaryWriter
От: pilgrim_ Россия  
Дата: 14.11.24 12:25
Оценка: +2
Здравствуйте, Serginio1, Вы писали:

_>>FileStream уже имеет встроенную поддержку буферизации, ему этот класс не нужен.


S> Я знаю. Он использует BinaryWriter для буферизации стрима.


BinaryWriter не занимается буферизацией, FileStream этим занимается сам.
Re: BinaryWriter
От: Teolog  
Дата: 14.11.24 12:42
Оценка:
Здравствуйте, tapatoon, Вы писали:
T>Пишу в файл данные несколько гигабайт. Пишу небольшими порциями по нескольку байт

Самый суровый и былинный способ записи который знаю — создать файл, указать ему размер с запасом,
мапнуть в память и дальше писать в кусок памяти,
при окончании отмапнуть и поменять размер файла на актуальный.
Быстрее только не писать вообще.

А вообще самый главный косяк тут — возврат микробуферов из getdata вместо фунции writeData(Stream)
Re[12]: BinaryWriter
От: tapatoon  
Дата: 15.11.24 05:37
Оценка:
Здравствуйте, Sinclair, Вы писали:

S>Что значит "сделал размеры буферов одинаковыми"? Вы сконструировали FileStream с нестандартным размером буфера?

Размер буфера, передаваемого во Write(File). var data = getData();
getData отдаёт по 4КБ
Центр ИПсО Сил Специальных Операций
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.