Comments 34
if (!file)
{
file.release(); // тут же моментом выскочит null pointer exception коли уж file == nullptr (проверка на !file успешна)
return 1;
}
int main()
{
std::unique_ptr<FILE, int(*)(FILE*)> file(nullptr, fclose);
if (!file)
{
file.release();
return 1;
}
return 0;
}
и посмотрите как он отработает.
ru.cppreference.com/w/cpp/memory/unique_ptr/release — вернётся nullptr в никуда и что? Вообще std::unique_ptr::release() noexcept метод
1. Зачем так сложно? Зачем там unique_ptr?
2. Почему в случае ошибки перед завершением программы делается file.release(), а при нормальном завершении — нет?
Это идиоматическое применение unique_ptr
в качестве RAII-обёрток над легаси/C-примитивами:
- При создании
unique_ptr<FILE*, ...>
указывается кастомный удалитель, который вызываетfclose(file_ptr_)
в деструкторе при штатной работе программы (или при генерации любого исключения). - В случае ошибки
fopen
по стандарту возвращаетnullptr
, поэтому в таком случае нет необходимости вызыватьfclose
. Deamhan, скорее всего, здесьrelease
вообще не нужен, потому что deleterunique_ptr
'а и так не вызовется надnullptr
.
Вау! Не знал, что с помощью умных указателей так легко оборачивать С-шные ресурсы в RAII. Я всегда для этого классы городил. Спасибо!
Но побайтное чтение/запись в stdio все равно слишком медленные и лучше менять алгоритм что бы было линейное чтение/запись большими кусками.
Хотел спросить как вы замеряли скорости что бы избежать побочных эффектов от кеширования файла в памяти после первого прогона, но судя по низким скоростям это не имеет большого смысла в данном случае.
Но у вас все равно получается, что измеряется время когда вы отдали файл в кеш ОС, а не когда он по факту был записан на диск.
В любом случае переделывание алгоритма на последовательную запись большими кусками позволит ускориться в несколько раз, потому что даже для HDD на 7200 rpm скорость линейной записи обычно > 200 МБ/с.
Но для бинарных патчей это наверное не актуально :)
Собственно вот что я имел ввиду:
C:\>write-byte.exe
Time: 16242738
C:\>write-block.exe
Time: 234701
Версия записывающая байт за байтом чудовищно медленная и грузит ядро на 100%. И кэширование записи в ОС не заметно только потому, что бенчмарк пишет в файл медленнее чем идет запись на диск.
В случае записи блоками все заканчивается практически мгновенно и бенчмарк завершается еще до того, как файл физически будет записан на диск.
Поэтому даже реальную скорость записи на диск таким простым тестом не получится узнать.
А влияния в данном случае не будет, потому что бутылочное горлышко где то в другом месте.
Тут даже близко не подошли к пределу по линейной записи.
И вы точно не напутали с размерностью МБ/ГБ? :)
Так кому верить? «Близко не подошли к пределу линейной записи» или «Скорости тут порядка обычной линейной записи», если верить предыдущему оратору?
Вы еще не отметили, что hdd не только размером буфера отличаются, но и скоростью вращения, 5400, 7200, 10000 rpm точно дадут разную скорость записи. Но в целом, буфер играет роль только в сценариях, когда происходит например копирование неск. гигабайт данных из одной папки в другую, а когда на диск пишутся данные, которые генерирует какая-то программа, размер буфера не должен влиять на скорость записи, т.к. как только он заполнится, мы упремся в сам диск (вращение+головка).
Писать в файл по байту за вызов так себе вариант, чудовищно медленный даже в случае использования буферизующей прослойки в виде stdio.
В данном случае это просто пример как можно за бесплатно ускорить легаси, но тот же самый код пишущий большими блоками справляется с задачей за доли секунды.
Это нужно учитывать на уровне алгоритма.
Почему бы просто не мапить файл в память? По идее, это будет ещё быстрее.
Думаю, что mmap() все же будет быстрее fopen()/fread(), но возможно медленнее open(..., O_DIRECT)/read(). В некоторых случаях.
HDD WDC WD20EZRZ — 5400 rpm буфер 64М.
$ ./fwrite_test.fwrite_nosetbuf
Time: 11718859
$ ./fwrite_test.fwrite_unlock_nosetbuf
Time: 9806416
$ ./fwrite_test.fwrite_unlock_setbuf
Time: 9803208
Разница заметна, но не так как в виртуалке.
прогнал несколько раз, погрешность ~1% картины особенно не меняет.
g++ -o2 -s -static-libgcc -static-libstdc++ fwrite_test.cpp -o fwrite_test
и только после вашего поста задумался — файло-то и правда, 512М а не Г, почему-же так медленно? Попробовал на tmpfs — та-же картина.
Дело в -o2, с -O2 — совсем другое дело. На hdd
$ ./fwrite_test.unlock_nobuf
Time: 4637970
$ ./fwrite_test.unlock_buf
Time: 4636879
$ ./fwrite_test.fwrite
Time: 6129556
Почти то-же самое на ssd/tmpfs.
И еще, вместо fwrite_unlocked(&b, 1, sizeof(b), file.get());
видимо, должно быть fwrite_unlocked(&b, sizeof(b), 1, file.get());
PS: linux gentoo x86_64 gcc 8.2
а еще — какая виртуалка у вас, на vbox/win7 разница получилась менее заметной.
Ускорение файлового ввода-вывода C/C++, не особо напрягаясь