Припокриване на IO или съпоставяне на файлове?

В Windows приложение имам клас, който обвива име на файл и буфер. Конструирате го с име на файл и можете да направите запитване към обекта, за да видите дали буферът вече е запълнен, връщайки nullptr, ако не, и адреса на буфера, ако е така. Когато обектът излезе извън обхвата, буферът се освобождава:

class file_buffer
{
public:
    file_buffer(const std::string& file_name);
    ~file_buffer();
    void* buffer();

private:
    ...
}

Искам да поставя данните в паметта асинхронно и доколкото виждам, имам два избора: или да създам буфер и да използвам припокрит IO чрез ReadFileEx, или да използвам MapViewOfFile и да докосна адреса в друга нишка.

В момента използвам ReadFileEx, което създава някои проблеми, тъй като заявки, по-големи от около 16MB, са предразположени към неуспех: мога да опитам да разделя заявката, но след това получавам проблеми със синхронизирането и ако обектът изпадне извън обхвата преди IO завършен Имам проблеми с почистването на буфера. Освен това, ако множество екземпляри на класа са създадени в бърза последователност, нещата стават много трудни.

Картографирането и докосването на данните в друга нишка изглежда значително по-лесно, тъй като няма да имам проблеми с горната граница: също така, ако клиентът абсолютно трябва да има данните точно сега, той може просто да дереферира адреса, нека операционната система да се тревожи грешка на страницата и поемете блокиращия удар.

Това приложение трябва да поддържа едноядрени машини, така че въпросът ми е: грешките на страницата в друга софтуерна нишка ще бъдат ли по-скъпи от припокриването на IO в текущата нишка? Ще забавят ли процеса? Припокритият IO спира ли процеса по същия начин или има някаква магия на ОС, която не разбирам? Извършват ли се грешки в страницата с помощта на припокрит IO?

Прочетох добре тези теми: http://msdn.microsoft.com/en-us/library/aa365199(v=vs.85).aspx (IO концепции в управлението на файлове) http://msdn.microsoft.com/en-us/library/windows/desktop/aa366556(v=vs.85).aspx (съпоставяне на файлове), но изглежда не мога да заключа как да направя компромис с производителността.


person hatcat    schedule 23.02.2013    source източник
comment
Уауууу! Имам емблемата на тумбелите. Може би трябва да добавя C++ тага...   -  person hatcat    schedule 04.03.2013


Отговори (2)


Вие определено ще искате да отидете с карти с памет файлове. Припокритият IO (с FILE_FLAG_NO_BUFFERING) се препоръчва като "най-бързият начин за получаване на данни в RAM" от някои хора от години, но това е вярно само в много измислени случаи с много специфични условия. В нормалния, среден случай, изключването на буферния кеш е сериозна анти-оптимизация.

Сега припокритият IO без FILE_FLAG_NO_BUFFERINGима всички странности на припокрития IO и е около 50% по-бавен (по причина, която все още не мога да разбера).

Направих доста обширен сравнителен анализ преди година. Изводът е: файловете с карта на паметта са по-бързи, по-добри, по-малко изненадващи.

Припокритият IO използва повече CPU, е много по-бавен при използване на буферния кеш, асинхронният се връща към синхронен при някои добре документирани и някои недокументирани условия (напр. криптиране, компресиране и... чиста случайност? размер на заявката? брой заявки?), забавяне на вашето приложение в непредсказуеми моменти.
Изпращането на заявки понякога може да отнеме "смешно" много време и CancelIO понякога не анулира нищо, а изчаква завършването. Процесите с неизпълнени заявки не могат да бъдат унищожени. Управлението на буфери с изключителни припокрити записи е нетривиална допълнителна работа.

Картографирането на файлове просто работи. Точка. И работи добре. Без изненади, без смешни неща. Докосването на всяка страница има много малко излишни разходи и се доставя толкова бързо, колкото дискът може да достави, и се възползва от буферния кеш. Загрижеността ви за едноядрен процесор не е проблем. Ако сензорната нишка се повреди, тя блокира и както винаги, когато една нишка блокира, друга нишка получава процесорно време вместо това.

Сега дори използвам картографиране на файлове за запис, когато имам повече от няколко байта за запис. Това е донякъде нетривиално (трябва ръчно да се разрастват/предварително разпределят файлове и съпоставяния и да се съкращава до действителната дължина при затваряне), но с някои помощни класове е напълно изпълнимо. Запишете 500 MiB данни и това отнема „нула време“ (по принцип правите memcpy, действителният запис се случва на заден план, по всяко време по-късно, дори след като програмата ви е приключила). Зашеметяващо е колко добре работи това, дори ако знаете, че това е нещо естествено за една операционна система.
Разбира се, по-добре е да не прекъсвате захранването, преди операционната система да изпише всички страници, но това е вярно за всяка вид писане. Това, което все още не е на диска не е на диска - наистина няма какво повече да се каже за него от това. Ако трябва да сте сигурни в това, трябва да изчакате синхронизирането на диска да завърши и дори тогава не можете да сте сигурни, че светлините не изгасват, докато чакате синхронизирането. Това е животът.

person Damon    schedule 04.03.2013

Не твърдя, че разбирам това по-добре от вас, тъй като изглежда, че сте направили някакво изобретение. И за да сте напълно сигурни, ще трябва да експериментирате. Но това е моето разбиране на проблемите в обратен ред:

  1. Съпоставянето на файлове и припокритият IO в Windows са различни реализации и нито едно от тях не разчита на другото под капака. Но и двете използват слоя на асинхронното блоково устройство. Както си го представям, в ядрото всеки IO всъщност е асинхронен, но някои потребителски операции чакат да приключи и така създават илюзията за синхронност.
  2. От точка 1, ако нишка направи IO, други нишки от същия процес няма да спрат. Това, освен ако системните ресурси не са оскъдни или тези други нишки сами правят IO и са изправени пред някакъв вид спорове. Това ще е вярно, независимо от вида на IO, който прави първата нишка: блокираща, неблокираща, припокриваща се, картографирана в паметта.
  3. Във файловете с карта на паметта данните се четат поне една страница наведнъж, вероятно повече поради предварителното четене, но не можете да сте сигурни в това. Така че сондиращата нишка ще трябва да докосне картографираната памет поне една на всяка страница. Това ще бъде нещо като сонда/блок-сонда-сонда-сонда-сонда/блок-сонда... Това може да е малко по-малко ефективно от голямо припокриващо се четене на няколко MB. Или може би програмистите на ядрото са били умни и то е дори по-ефективно. Ще трябва да направите малко профилиране... Хей, можете дори да минете без нишката за сондиране и да видите какво ще се случи.
  4. Cancelling overlapping operations is a PITA, so my recommendation will be to go with the memory-mapped files. That is way easier to set up and you get extra functionality:
    1. the memory is usable even before it is fully in memory
    2. паметта може/ще бъде споделена от няколко инстанции на процеса
    3. ако паметта е в кеша, тя ще бъде готова моментално, вместо просто бързо.
    4. ако данните са само за четене, можете да защитите паметта от запис, улавяне на грешки.
person rodrigo    schedule 04.03.2013
comment
Благодаря за вашия принос; отговаря на моята представа за ситуацията. Бих предпочел подхода за картографиране на паметта, но отстраних грешки, тествах кода и продажбата на замяната изисква подобрение на производителността. - person hatcat; 04.03.2013
comment
@hatcat: Можете също да опитате да използвате FILE_FLAG_SEQUENTIAL_SCAN, когато отваряте файла, ако вече не го използвате. Може да подобри производителността както на припокриващи се, така и на mmaped IO. - person rodrigo; 04.03.2013