C++ streams compatibility...

Граждане программисты,

Я - человек темный, граблями причесываюсь на фортране-77 все еще программирую. Но мне пишут, что дескать в современных C++-стримах все сделано по уму в смысле буферизации и работают они временами сильно быстрее, чем любимый мой FILE*

Собственно, сомневаться причин нет, в тестах так оно и получилось (и разница заметная), но мучает меня вопрос с совместимостью.

Мою LibRaw на чем только не собирают, вот даже на Maemo, а давеча я проблемы с Visual Studio 2003 правил.

Отсюда вопросы (сам я за всем этим не слежу, проще спросить):

  • Интерфейс то стримовый за последние лет 7-10 - он вообще как, стабилизировался?
  • Следует ли ожидать всяких открытий чудных, вроде того что метод есть, но не работает?
  • С Linux/Mac проблем нет, я вижу что в gcc 4.x все (на первый взгляд) нормально. А что с виндами, причем как в ипостаси Visual Studio, так и cygwin/MinGW? Может есть какая-то табличка по совместимости хотя бы по Visual C++

Comments

Мы под себя stream адаптировали (подменяли std::basic_filebuf), граблями по лбу не поймали. Правда, мы и под экзотику всякую не компилируем, только MSVC 2005 и старше да GCC 4.x под Linux и Mac OS X.

Ну насколько я понимаю, вы компилируете всегда сами, не опенсорс....

Буду пробовать, но ожидаю открытий чудных....

Я - тоже человек темный, тестов не читал. При своем очень немаленьком опыте разработки на C++ в превосходство *производительности* работы std-шных потоков над операциями через FILE* не особо верю, там другие плюшки - в основном типизированный и безопасный ввод-вывод.

Буферизация ввода-вывода - вещь довольно банальная, сильно улучшить её на потоковых операциях крайне затруднительно. Так что единственный реальный ресурс ускорения потоковых операций над FILE* - это переход с вызовов функций на подстановку inline-ов. В каких-то, особенно патологических случаях (тестах) может сыграть.

Насколько я вижу, в FILE* имеются лишние локи. В результате у меня жалкий 37-мегабайтный файлик fgetc()-ем читается (т.е. посимвольно) почти две секунды. Про getc_unlocked я знаю.

Не, что фишка быстрее работает - видно в тестах. Не на всех системах, вот на FreeBSD совсем уж лишних локов нет и разницы тоже почти нет.

Да, "лишние локи" - под Linux, если собирать мультитредную программу или с -fopenmp (которая тоже мультитредной становится).
Без этого - все хорошо, значимой разницы не вижу. Но openmp мне надо, от него счастье.

На счёт OpenMP - насколько я помню в VS он появился с 2005 (проверял на 2005,2008,2010).
Но, надо учесть что редакции Standard не поддерживают из коробки OpenMP. В компиляторе поддержка есть, но openmp либы(и другой stuff) не поставляются(можно просто скопировать из Professional редакции(проверено). Также где-то читал, что нужные файлы есть в Windows SDK, но я не проверял).

Когда появился в gcc не уверен, но проверял на gcc34 и более новых - полёт нормальный.

Если необходима поддержка как можно большего числа компиляторов и сред, от OpenMP придётся отказаться в пользу какой-нибудь другой либы.
(конечно там где OpenMP не поддерживается, не трудно за-ifdef'ить код OpenMP
(тем более при использовании OpenMP этого кода не так много (прагмы можно не глушить)),
но тогда пропадает многопоточность )

Не, ну естественно, там где OpenMP не поддерживается, там без него. Без него тоже работает

буферизацию надо свою.
независимо.
и инлайнить.

А более правильных вызовов, типа fread(дофига) религия использовать не позволяет? Жалкие 37 метров одним вызовом усосать можно, или есть какие-то очень странные ограничения по памяти?

fgetc() вообще штука крайне специфическая.
Обычно стараюсь этим вызовом не пользоваться, благо ему есть простая замена - fread() + цикл по буферу.

Очень не хочется переписывать почти полный набор примитивов FILE* самостоятельно. Велосипед же ж.

Переписать "это место" руки чешутся, конечно, но очень хочется обойтись без этого....

Сорри, я тёмный, а что там в LibRAW такого, кроме чтения и записи файлов? Оно ж сразу целиком в память читается одним куском, не? Да и писать тоже можно сразу блоками, типа writev. К чему там буфера, собственно?

Увы. Код распаковщика передает вам привет от Dave Coffin. У меня тормозит вот это вот место:
  while (!reset && vbits < nbits && (c = fgetc(ifp)) != EOF &&
    !(reset = zero_after_ff && c == 0xff && fgetc(ifp))) {
    bitbuf = (bitbuf << 8) + (uchar) c;
    vbits += 8;
  }
Да, а между его вызовами может быть, гы-гы, fseek(). И хотя fgetc давно уже сделан так:
#define fgetc(stream)            stream->get_char()
Остается или использовать свою буферизацию, написав ее целиком, или брать готовую. Сейчас get_char() выглядит так:
int LibRaw_file_datastream::get_char()
{
    CHK();
    return substream?substream->get_char():fgetc(f);

}

А использовать mmap() в таком месте не проще? Оно и буферизоваться будет по 4 кб замечательно.
Правда под виндой оно несколько иначе вызывается.

Ну у меня естественно есть интерфейс поверх буфера в памяти, где fgetc развернется в buf[bufp++]
А буфер можно и mmap-ом получить.

Но бывают очень большие файлы, которые не замэпать. Не говоря о всяких maemo и прочих мобильных девайсах, где памяти мало и есть ли там mmap - тоже вопрос.

Дошли руки это место побенчмаркать.

FreeBSD, single thread, файл на 20+ мегабайт.

mmap() гораздо медленнее, чем файловый доступ. Т.е. на 20-25 мегабайтном файле - лишние миллисекунд 200-300. Это если просто MAP_PRIVATE. MADV_WILLNEED и MAP_NOCORE несколько улучшают.

Интересно. Значит у меня устаревшие воспоминания.
Видимо ему advise не хватает основательно. А вы проверяли на SSD или на обычном диске?

Да, это SSD. На обычном (хм. обычном массиве, у меня одиночных HDD нету) тоже померяю.

UPDATE: а диск не должен никак влиять. Файл маленький, кэш большой, все тайминги - несколько запусков, но первый запуск (с поднятием в кэш) не считается.

Ну и конечно мой код, который с preallocated-буфером работает, может быть не самым оптимальным (зато компактный).

И еще :), раз уж вы читаете комментарии, значит подробности могут быть интересны.

Если код читает по байтам (для FILE это getc_unlocked), то байтовое чтение и чтение через mmap ( return buf[bufp++]) - примерно одинаково по времени (а основное время там уходит в свисток - в распаковку хаффмана, более гранулярно я уже не бенчмаркал). Т.е. fgetc/getc_unlocker действительно сильно неэффективны.

А если код читает кусками (по строкам RAW т.е. несколько килобайт) - то те самые 200-300 миллисекунд разницы на 25 мегабайтах.

распаковка хоффмана - это у вас ZFS наверное, там много чего может оказаться если цепочка буферов настроена неоптимально. А при работе с SSD - у него вроде большой внутренний размер страницы, кто-то говорил что 64К, и оно может плохо ложиться в маленький буфер файла. Но первичны конечно именно ваши экспериментальные замеры.

Сферически в вакууме, мапирование в виртуальной памяти - это самый естественный и дешевый доступ, поскольку достигается zero-copy и размер окрестности выбирается автоматически исходя из наличия свободной памяти, кроме того о распараллеливании чтения думает вместо нас операционная система (с тем или иным успехом;) А при чтении файла буфера копируются с места на место, проверяются постоянно границы буфера, указатель (который tell/seek) надо постоянно двигать - масса лишней работы.

В яндексе еще больше 10 лет назад индекс читался кусочками с помощью
ptr = mmap((caddr_t)NULL, size, PROT_READ, MAP_SHARED, fd, offset)
а потом сразу munmap(), поскольку целиком файл не умещался в 32bit.
При нескольких словах в запросе и множестве параллельных запросов - оно как-то все распараллеливалось внутри FreeBSD+SCSI вполне замечательно. При явном чтении получалось хуже.

Не знаю как сейчас с mmap64.

Не, вы вероятно не так поняли. То есть ZFS конечно есть, но он на этой машине нежатый.

А речь идет о распаковке RAW-файлов от цифровых фотокамер. Там чужой код (dcraw), под который подложен собственный I/O layer. И я сегодня приделал туда тайминг, чтобы видеть сколько времени занимает собственно распаковка, а сколько - постпроцессинг, а то профайлером это смотреть мучительно.

Так вот, lossless jpeg (который huffman внутри) распаковывается этим чужим кодом с помощью fgetc() в цикле. Который у меня превращается или в getc_unlocked() или в buf[buf++] (во втором случае buf сделан mmap-ом).
А дальше я уже все выше написал, fgetc() в виде fgetc() ничуть не хуже чем buf[buf++], а вот fread() эффективнее чем memmove(). А zero copy я не могу, у меня семантика fread().
С zero copy вполне возможно что будет и эффективнее.

А, да, если Linux и многопоточная программа, то FILE I/O весь в локах (ненужных) и скорее всего mmap выиграет (потому что реально тормозит на локах). И в винде тем более выиграет. А в 8-й FreeBSD локи в этом месте сделаны лучше.

Да, про Хаффмана я погорячился, забыл контекст исходного поста.
У меня кстати обнаружилась красивая задача для поисковика, вам наверное будет любопытно. Попробую написать письмом.

Да, а с чтением одним куском всего файла - тоже облом. помнится я все файловые офсеты менял на 64-битные. Потому что бывает кино в raw

Я, конечно, ненастоящий сварщик, и давно не смотрел на тему, но вроде как стандарт в области потоков давно не трогали?

вопрос в том. нет ли таких форм жизни, где это место неживое. Одно я уже нашел, это gcc44 на FreeBSD (допускаю, что у меня порт был криво поставлен, завтра переставлю и перепроверю), но ведь наверное cygwin/MinGW - это тоже вероятный кандидат. Не говоря о всяких Visual C 6

м-м-м, ну шестой вс брать ... неужели им кто-то еще пользуется? :) он же совсем инвалид был с шаблонами и прочим ... да и то как-то с STLport вроде собирали и работало ...

Ну не я же беру.

Опенсорс, пользователи бывают всякие....

Ты еще скажи, что у тебя камеры выдают файлики более 100 метров и засосать их одним вызовом - религия не позволяет. Или ты на мобильники ориентируешься?

Во-первых, это чужой код, "под который" я подкладываю свой I/O

Во-вторых, видеокамера (выдающая RAW) больше 2 гигов выдает спокойно, а какой там лимит - я не знаю, но 64-битный file offset появился именно поэтому.

Естественно, с такими файлами - покадровая работа.

Я вот тоже темный и FILE* милее всяких цплюсплюсовых наворотов.
По-любому быстрее всего - тянуть файл в память и потом быстро-быстро препарировать.
Что касается больших многокадровых RAW, наверняка ведь кадры друг от друга независимы - закачиваем каждый по отдельности в память и препарируем.

В общем случае это +25% к потребной памяти (для 16-битных нежатых raw). Если пользователь такое может позволить себе - то для этого у него имеется отдельный интерфейс на эту тему "читать RAW из буфера в памяти".

Ха, думал фортран - это уже история. Ещё в техникуме пятнадцать лет назад о нём вспоминали.