Есть такой код ( заранее извинение за C ): Код (Text): HANDLE hFile = CreateFile( szFileName,GENERIC_READ,FILE_SHARE_READ|FILE_SHARE_WRITE, NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL ); if (hFile!=INVALID_HANDLE_VALUE) { DWORD dFileSize = GetFileSize(hFile,NULL); HANDLE hFileMapped = CreateFileMapping(hFile,NULL,PAGE_READONLY,0,0,NULL); if (hFileMapped) { char *pcBlkBegin = (char*)MapViewOfFile(hFileMapped,FILE_MAP_READ,0,0,0); /*-------------------------------------------------------------------- */ *dCRC = Crc32(pcBlkBegin,dFileSize,blAborted,szFileName); /*-------------------------------------------------------------------- */ UnmapViewOfFile(pcBlkBegin); CloseHandle (hFileMapped); } CloseHandle(hFile); Условия: 1. Этот код присутсвует в консольной проге 2. Проверка идет на файлах большего размера (фильмы, образы и т.д.) 3. При выполнении кода в Win2k SP4 скорость работы кода составляет 30-40 метров в секунду при нахождении консольного окна на переднем плане, около 1 метра в секунду на заднем плане. 4. На аналогичной по конфигурации машине с ОС XP SP1 скорость работы программы около 4-5 метров в секунду, в фоне где-то в 1.5 раза меньше. 5. Файл, на котором осуществляется проверка не фрагменти рован и проверка осуществляется после перезагрузки винды. 6. В процедуре CRC32 доступ к маппированному пространству осуществляется побайтно. Вопросы: 1. Почему так различается скорость маппинга в Win2k и Win XP, а также в фоне и на переднем плане в Win2k 2. Как увеличить скорость маппинга файла при условии, что доступ последовательный и только на чтение? 3. Может ли кто-либо предложить более быстрый способ работы с файлами?
file-mapping это в первую очередь commit виртуальной памяти, поэтому если ты мапишь гагабайтный файл, то системе надо выделить в твоем процессе гигабайт виртуальной памяти и отобразить туда файл. Видимо эта подсистема поразному реализована в разных операционках (или оптимизирована под разные цели). Думаю быстрее будет выдеить буфер мегабайта в четыре и последовательно читать туда файл с подсчетом crc - тебе же не нужен random-access к файлу, значит и от маппинга никаких плюсов не будет (а вот минусов - предостаточно).
Тема file-mapping'а обсуждалась не раз (см.например MapViewOfFile or ReadFile и Как правильно организовать работу с файлом?) Вывод простой: при последовательном доступе только для чтения маппинг ничего не дает, кроме тормозов. Быстрее работает обычный ReadFile, причем размер буфера брать большим тоже не имеет большого смысла (обычно достаточно 16-64кБ, хотя это может зависеть от сложности\времени обработки данных). Небольшой прирост может дать использование FILE_FLAG_NO_BUFFERING, а вот FILE_FLAG_SEQUENTIAL_SCAN обычно ничего не дает. Если время обработки достаточно большое по сравнению с загрузкой данных, то в NT-шных осях можно попробовать асинхронное чтение с FILE_FLAG_OVERLAPPED (в 9х не поддерживается)
спасибо всем, я после ответа Dr.Golovaвчера написал потоковую процедуру, которая читала все это дело кусочками по 64 кила -- оказалось работает одинаково быстро в разных OS leo Игрался я с FILE_FLAG_NO_BUFFERING, правда память выделял не VirtualAlloc'om -- почти ничего не дало, буду игратся дальше...
Tim Sobolev > "почти ничего не дало" По идее выигрыш должен зависеть от размера буфера. Если флаг NO_BUFFERING не используется, ось читает файл в файловый кэш и затем копирует данные в наш буфер. Но если буфер небольшой (порядка размера кэша L1 или по крайней мере меньше L2), то фактически никакой реальной пересылки данных из одной области ОЗУ в другую ИМХО не происходит, т.к. при копировании данные записываются в кэш, откуда ты их и получаешь тепленькими Поэтому для небольших блоков потери не превышают латентности rep movsd (для выравненных данных не более 1 тика на 4 байт), что значительно меньше по сравнению с чтением данных из файла и ОЗУ, особенно для гиговых процев. А вот для больших буферов в несколько мегов (> L2) пересылка данных будет тормозить, т.к. при исчерпании свободного места в кэше начнется их реальная запись в ОЗУ, откуда их придется повторно вытаскивать при чтении буфера - заметные потери. Поэтому по идее выигрыш от NO_BUFFERING должен расти с увеличением размера буфера. Но в любом случае при чтении рулят небольшие буферы, поэтому ИМХО и нет большой разницы между буферированным и небуферированным чтением.
А как же операции чтения с диска? Неужели они такие быстрые, что, читая маленькими порциями, получаем выигрыш в производительности?
CreateFile - открывает файл CreateFileMapping - открывает file-mapping object, как раз где указывается SEC_COMMIT (default) мапировать в память, или SEC_NOCACHE MapViewOfFile - отображение в пространство юзверя А вот дальше в книге Таненбаума описана работа с памятью... Если её мало, то часть мапированого файла (как ни крути) идёт в файл подкачки => надо найти компромис между dwSize и размером ОЗУ, т.к. у разных ОС свои представления о свободном количестве блоков ОЗУ, то и работает по разному, тем более, когда процесс выполняется в фоне. Судя по тому, что у CreateFileMapping свои флаги на кэширование, то CreateFile с BUFFERING, что без него - всё едино Мапирование в память, IMHO, нужно делать для малых размеров файла, иначе часть файла опять свалится в подкачку и смысла в такой работе нет Скорее по этому прямое чтение выполняется быстрее.
IceStudent > "Неужели они такие быстрые" Они потоковые. Стародавние времена, когда с дорожки считывался заданный сектор безвозвратно ушли. Современные диски ИМХО считывают целиком всю дорожку (или по кр.мере часть), заталкивая данные во внутренний буфер (2,4,8Mb), и уже из буфера передают их на шину пакетами по одному или несколько секторов. Поэтому вроде как и существенной разницы быть не должно запрашиваем мы сразу 1Мб или 16 раз по 64K (если конечно за это время посторонние запросы не вклинятся). Но разница все-таки есть, т.к. в винде рулит упреждающее чтение диска (как ни странно тоже блоками до 64К . Если мы запрашиваем мегабайт, то при синхронном чтении вынуждены ждать пока он целиком попадет в ОЗУ. А если кусками по 64К, то во время загрузки в кэш L1/L2 и обработки процем одного блока заботливая винда начинает упреждающее чтение следующего. Плюс эффективное использование кэшей L1/L2, как упоминалось выше. PS: это лишь ИМХО - может знающие люди поправят, уточнят, дополнят SteelRat Вопрос в том, что означает "малый размер файла". Как упоминалось в теме "MapViewOfFile or ReadFile" (см.ссылку выше) при маппинге система оставляет за собой право выбора сколько страниц загрузить в ОЗУ сразу и сколько оставить на потом. А подгрузка страниц осуществляется по PageFault, обработка которого, как и любого другого исключения приводит к большим дополнительным потерям.
leo Это как я понял, на физическом уровне? А на логическом? Вызов ReadFile спускается в ядро, к драйверу ФС, которому, вроде бы, не всё равно, читаем мы за раз мегабайт или 64К. Хотя, здесь работает файловый кэш, действительно, надо бы выслушать знающих.
leo мои эксперименты годовалой давности приводили к этой же цифре . Смотрел как быстро работает поиск в файле размером за 500м. Блоки были с 16к до 16м - читать по 64к оказалось быстрее всего, флаг NO_BUFFERING cущественно не влиял, как и версия виндов: nt,2k,xp. Есть подозрение что кроме виндового кэша может влиять физический кэш самого диска. При наличии различных (по производителям, дате выпуска, емкостям, сочетанием с процесором) винчестеров можно было бы практически посмотреть на разницу в скорости либо ее отсутсвие. И я бы тоже с интересом послушал про файловый кэш. Можно им хоть как-то управлять? в MSDN ничего толкового не нашел. и еще вопрос, сколько из этого мегабайта оседает в файловом кэше. Если читается сразу 8м - все 8 ложатся в кэш ? А если я захотел 32м за раз прочитать, они тоже _все_ лягут в кэше ? если нет, где та граница, вот кто бы рассказал...
IceStudent > "к драйверу ФС, которому, вроде бы, не всё равно" Поскольку знатоки молчат, попробую продолжить "измышлизмы" Ес-но, каждый вызов ReadFile требует определенных накладных расходов. Если считать, что эти расходы примерно одинаковы (хотя для больших блоков они могут быть нексколько больше), то их процентный вклад в общее время загрузки eс-но уменьшается с увеличением размера блока. Но на деле, когда этот вклад становится на уровне долей процента и ниже, происходит насыщение - разница теряется на фоне случайных факторов (поиск сектора, смена дорожки). Прикинем цифры: в режиме UDMA100 (25МГц*2*16-бит) блок 64Кб может быть передан не менее чем за 32K циклов 50МГц (0.66 мс). Время позиционирования головок (5-10 мс) не учитываем и считаем, что контроллер HDD берет данные из предварительно заполненного 2Мб буфера (иначе небольшие блоки должны были бы считываться гораздо дольше, но на практике это не так). Поэтому 1) несколько десятков или сотен циклов на иницализацию DMA уже можно не учитывать, 2) пересчитав время загрузки к частоте CPU > 1ГГц получим > 640K циклов, поэтому код может смело путешествовать в дебрях ОС не менее нескольких тысяч тиков CPU и в процентах мы этого не заметим. Вывод ИМХО такой - размера блока в 64К вполне достаточно для того чтобы накладные расходы на вызов ReadFile не были заметны на фоне времени передачи данных в режиме UDMA (ес-но при отсутствии конкурирующих запросов к HDD). Поэтому такие блоки читаются не медленее, а даже несколько быстрее чем мегабайтные, т.к. прочие факторы работают уже в пользу небольших блоков. Например, копирование данных из файлового кэша в буфер. Если буфер в L1\L2, то время тратится только на загрузку данных из файлового кэша. Если буфер больше половины L2, например 2-4Мб при L2=512K, то число пересылок ОЗУ-кэш вместо 1 становится = 3 или 4 - загрузка из файлового кэша, загрузка из буфера (если не movntX), запись в буфер, чтение из буфера при обработке. Если взять для примера память DDR400 и UDMA100 получим доп.затраты (size/8)*(2..3)/400 к (size/100), т.е.более 6..12%.
Похоже, что файловый кэш и тормозит при больших размерах буфера. Вот не поленился тест прогнать под XP SP2, правда для FAT32, но для NTFS д.б. тоже самое, т.к. похоже это винда со своим кэшем все портит. Проверял на P4 3.2GHz, L2 512Kb, FSB 800MHz, DDR400 512Mb, HDD Maxtor 80Gb 7200rpm UDMA100. По очереди читались два мультика по ~700Мб каждый (нефрагментированные, примерно в середине диска). Вот такая получилась картина: (старые ссылки не работают, см. рисунок в аттаче) Как видим NO_BUFFERING ведет себя как положено - при малых блоках велика доля накладных расходов при частом вызове ReadFile, но уже при 16К наступает насыщение и при дальнейшем увеличении размера блока прироста скорости практически нет. В обычном же режиме с буферизацией (dwFlagsAndAttributes = 0) ситуация противоположная. При малых блоках рулит виндовое упреждающее чтение (скорее не упреждающее, а чтение с запасом - теми же магическими 64К), поэтому скорость максимальна именно при малых блоках и медленно снижается при увеличении размера до 64К, а затем происходит резкое ухудшение. Системный монитор показывает всплеск "бестолковой" активности - очередь диска возрастает в 1.5 раза, загрузка процессора в 5 раз (с 1% до 5-6% (в среднем), но главное - ошибки кэш-памяти подпрыгивают от 0-2/сек до 7500/сек плюс всплески страничного обмена (слышно как диск начинает верещать от возмущения . А что в итоге - хотели как лучше, а получилось... ) PS: Загадочный какой-то провал на 128К и\или подъем на 256K - повторял несколько раз, результат один и тот же. С чего бы это ? PPS: Носом чую, что 64К и 256K это виндовые чанки страничного обмена, связанные с настройкой "типичной роли этого компьютера" - "настольный" или "сервер сети". Возможно при переключении на "сервер" точка облома сдвинется за 256K. Надо бы проверить.. Обновил картинку, т.к. старый линк сдох