Pull to refresh

Как эффективнее читать данные с диска (при условии, что у вас .Net)

Reading time 7 min
Views 12K


Привет, Хабр! Некоторое время назад меня заинтересовал вопрос: как эффективнее всего читать данные с диска (при условии, что у вас .Net)? Задача чтения кучи файлов встречается во множестве программ, которые при самом старте начинают вычитывать конфигурации, некоторые самостоятельно подгружают модули и т.д.

В интернете я не нашел подобных сравнений (если не считать тюнинга под определенные конфигурации).

Результаты можно посмотреть на GithubSSDHDD.

Способы чтения и алгоритм тестирования


Есть несколько основных способов:


Тестировал я все на SSD и HDD (в первом случае был компьютер с Xeon 24 cores и 16 Гб памяти и Intel SSD, во втором — Mac Mini MGEM2LL/A с Core i5, 4 Гб RAM и HDD 5400-rpm). Системы такие, чтобы по результатам можно было бы понять, как лучше вести себя на относительно современных системах и на не очень новых.

Проект можно посмотреть здесь, он представляет собой один главный исполняемый файл TestsHost и кучу проектов с названиями Scenario*. Каждый тест это:

  1. Запуск exe-файла, который посчитает чистое время.

  2. Раз в секунду проверяется нагрузка на процессор, потребление оперативной памяти, нагрузка на диск и еще ряд производных параметров (с помощью Performance Counters).

  3. Результат запоминается, тест повторяется несколько раз. Итоговый результат работы — это среднее время, без учета самых больших и самых малых значений.

Подготовка к тесту более хитрая. Итак, перед запуском:

  1. Определяемся с размером файлов и с их числом (я выбрал такие, чтобы суммарный объем был больше, чем объем RAM, чтобы подавить влияние дискового кеша);

  2. Ищем на компьютере файлы заданного размера (а заодно игнорируем недоступные файлы и еще ряд спецпапок, про которые написано ниже);

  3. Запускаем один из тестов на наборе файлов, игнорируем результат. Все это нужно для того, чтобы сбросить кеш ОС, убрать влияние от предыдущих тестов и просто прогреть систему.

И не забываем про обработку ошибок:

  1. Программа выдаст код возврата 0 только в случае, если все файлы были прочитаны.

  2. Иногда весь тест падает, если вдруг система начинает активно читать файл. Вздыхаем и перезапускаем еще раз, добавляя файл (или папку) в игнорируемые. Так как я использовал каталоги Windows & Program Files как хороший источник файлов, наиболее реалистично размазанный по диску, некоторые файлы могли быть ненадолго заблокированы.

  3. Иногда один Performance Counter мог выдать ошибку, так как процесс, например, уже начал завершаться. В этом случае игнорируются все счетчики за эту секунду.

  4. На больших файлах некоторые тесты стабильно выдавали Out Of Memory исключения. Их я убрал из результатов.

И плюс стандартные моменты про нагрузочное тестирование:

  1. Компиляция — в режиме Release в MSVS. Запуск идет как отдельное приложение, без отладчика и пр. Нет какого-то тюнинга, ведь суть проверок именно в том — как в обыкновенном ПО читать файлы быстрее.

  2. Антивирус отключен, обновление системы остановлено, активные программы остановлены тоже. Больше никаких тюнингов не было, по той же причине.

  3. Каждый тест — это запуск отдельного процесса. Overhead получился в рамках погрешности (т.е. jit, траты на старт процесса и пр.), а потому я оставил именно такую изоляцию.

  4. Некоторые Performance Counters выдавали нулевой результат всегда для HDD/SSD. Так как набор счетчиков вшит в программу, я их оставил.

  5. Все программы запускались как x64, попытка сделать swap означала неэффективность по памяти и сразу же уходила вниз в статистике из-за большого времени работы.

  6. Thread Priority и пр. тюнинги не использовались, так как не было попыток выжать именно максимум (который будет сильно зависеть от намного большего числа факторов).
  7. Технологии: .Net 4.6, x64

Результаты


Как я уже написал в шапке, результаты есть на GithubSSDHDD.

SSD диск


Минимальный размер файла (байты): 2, максимальный размер (байты): 25720320, средний размер (байты): 40953.1175
Сценарий
Время
ScenarioAsyncWithMaxParallelCount4
00:00:00.2260000
ScenarioAsyncWithMaxParallelCount8
00:00:00.5080000
ScenarioAsyncWithMaxParallelCount16
00:00:00.1120000
ScenarioAsyncWithMaxParallelCount24
00:00:00.1540000
ScenarioAsyncWithMaxParallelCount32
00:00:00.2510000
ScenarioAsyncWithMaxParallelCount64
00:00:00.5240000
ScenarioAsyncWithMaxParallelCount128
00:00:00.5970000
ScenarioAsyncWithMaxParallelCount256
00:00:00.7610000
ScenarioSyncAsParallel
00:00:00.9340000
ScenarioReadAllAsParallel
00:00:00.3360000
ScenarioAsync
00:00:00.8150000
ScenarioAsync2
00:00:00.0710000
ScenarioNewThread
00:00:00.6320000

Итак, при чтении множества мелких файлов два победителя — асинхронные операции. На деле в обоих случаях .Net использовал 31 поток.

По сути обе программы различались наличием или отсутствием ActionBlock для ScenarioAsyncWithMaxParallelCount32 (с ограничением), в итоге получилось, что чтение лучше не ограничивать, тогда будет использоваться больше памяти (в моем случае в 1,5 раза), а ограничение будет просто на уровне стандартных настроек (т.к. Thread Pool зависит от числа ядер и т.д.)

Минимальный размер файла (байты): 1001, максимальный размер (байты): 25720320, средний размер (байты): 42907.8608
Сценарий
Время
ScenarioAsyncWithMaxParallelCount4
00:00:00.4070000
ScenarioAsyncWithMaxParallelCount8
00:00:00.2210000
ScenarioAsyncWithMaxParallelCount16
00:00:00.1240000
ScenarioAsyncWithMaxParallelCount24
00:00:00.2430000
ScenarioAsyncWithMaxParallelCount32
00:00:00.3180000
ScenarioAsyncWithMaxParallelCount64
00:00:00.5100000
ScenarioAsyncWithMaxParallelCount128
00:00:00.7270000
ScenarioAsyncWithMaxParallelCount256
00:00:00.8190000
ScenarioSyncAsParallel
00:00:00.7590000
ScenarioReadAllAsParallel
00:00:00.3120000
ScenarioAsync
00:00:00.5080000
ScenarioAsync2
00:00:00.0670000
ScenarioNewThread
00:00:00.6090000

Увеличив минимальный размер файла, я получил:

  1. В лидерах остался запуск программы с числом потоков, близким к числу ядер процессоров.
  2. В ряде тестов один из потоков постоянно ждал освобождение блокировки (см. Performance Counter «Concurrent Queue Length»).
  3. Синхронный способ чтение с диска все еще в аутсайдерах.

Минимальный размер файла (байты): 10007, максимальный размер (байты): 62 444 171, средний размер (байты): 205102.2773
Сценарий
Время
ScenarioAsyncWithMaxParallelCount4
00:00:00.6830000
ScenarioAsyncWithMaxParallelCount8
00:00:00.5440000
ScenarioAsyncWithMaxParallelCount16
00:00:00.6620000
ScenarioAsyncWithMaxParallelCount24
00:00:00.8690000
ScenarioAsyncWithMaxParallelCount32
00:00:00.5630000
ScenarioAsyncWithMaxParallelCount64
00:00:00.2050000
ScenarioAsyncWithMaxParallelCount128
00:00:00.1600000
ScenarioAsyncWithMaxParallelCount256
00:00:00.4890000
ScenarioSyncAsParallel
00:00:00.7090000
ScenarioReadAllAsParallel
00:00:00.9320000
ScenarioAsync
00:00:00.7160000
ScenarioAsync2
00:00:00.6530000
ScenarioNewThread
00:00:00.4290000

И последний тест для SSD: файлы от 10 Кб, их число меньше, однако сами они больше. И как результат:

  1. Если не ограничивать число потоков, то время чтения становится ближе к синхронным операциям
  2. Ограничивать уже желательнее как (число ядер) * [2.5 — 5.5]

HDD диск


Если с SSD все было более-менее хорошо, здесь у меня участились падения, так что часть результатов с упавшими программами я исключил.

Минимальный размер файла (байты): 1001, максимальный размер (байты): 54989002, средний размер (байты): 210818,0652
Сценарий
Время
ScenarioAsyncWithMaxParallelCount4
00:00:00.3410000
ScenarioAsyncWithMaxParallelCount8
00:00:00.3050000
ScenarioAsyncWithMaxParallelCount16
00:00:00.2470000
ScenarioAsyncWithMaxParallelCount24
00:00:00.1290000
ScenarioAsyncWithMaxParallelCount32
00:00:00.1810000
ScenarioAsyncWithMaxParallelCount64
00:00:00.1940000
ScenarioAsyncWithMaxParallelCount128
00:00:00.4010000
ScenarioAsyncWithMaxParallelCount256
00:00:00.5170000
ScenarioSyncAsParallel
00:00:00.3120000
ScenarioReadAllAsParallel
00:00:00.5190000
ScenarioAsync
00:00:00.4370000
ScenarioAsync2
00:00:00.5990000
ScenarioNewThread
00:00:00.5300000

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

Минимальный размер файла (байты): 1001, максимальный размер (байты): 54989002, средний размер (байты): 208913,2665
Сценарий
Время
ScenarioAsyncWithMaxParallelCount4
00:00:00.6880000
ScenarioAsyncWithMaxParallelCount8
00:00:00.2160000
ScenarioAsyncWithMaxParallelCount16
00:00:00.5870000
ScenarioAsyncWithMaxParallelCount32
00:00:00.5700000
ScenarioAsyncWithMaxParallelCount64
00:00:00.5070000
ScenarioAsyncWithMaxParallelCount128
00:00:00.4060000
ScenarioAsyncWithMaxParallelCount256
00:00:00.4800000
ScenarioSyncAsParallel
00:00:00.4680000
ScenarioReadAllAsParallel
00:00:00.4680000
ScenarioAsync
00:00:00.3780000
ScenarioAsync2
00:00:00.5390000
ScenarioNewThread
00:00:00.6730000

Для среднего размера файлов асинхронное чтение продолжало показывать лучший результат, разве что число потоков желательно ограничивать еще меньшим значением.

Минимальный размер файла (байты): 10008, максимальный размер (байты): 138634176, средний размер (байты): 429888,6019
Сценарий
Время
ScenarioAsyncWithMaxParallelCount4
00:00:00.5230000
ScenarioAsyncWithMaxParallelCount8
00:00:00.4110000
ScenarioAsyncWithMaxParallelCount16
00:00:00.4790000
ScenarioAsyncWithMaxParallelCount24
00:00:00.3870000
ScenarioAsyncWithMaxParallelCount32
00:00:00.4530000
ScenarioAsyncWithMaxParallelCount64
00:00:00.5060000
ScenarioAsyncWithMaxParallelCount128
00:00:00.5810000
ScenarioAsyncWithMaxParallelCount256
00:00:00.5540000
ScenarioReadAllAsParallel
00:00:00.5850000
ScenarioAsync
00:00:00.5530000
ScenarioAsync2
00:00:00.4440000

Опять в лидерах асинхронное чтение с ограничением на число параллельных операций. Причем, рекомендуемое число потоков стало еще меньше. А параллельное синхронное чтение стабильно стало показывать Out Of Memory.

При большем увеличении размера файла сценарии без ограничения на число параллельных чтений чаще падали с Out Of Memory. Так как результат не был стабильным от запуска к запуску, подобное тестирование я уже счел нецелесообразным.

Итог


Какой же результат можно почерпнуть из этих тестов?

  • Почти во всех случаях асинхронное чтение, по сравнению с синхронным, давало лучший результат по скорости.

  • При росте размера файла целесообразно ограничивать число потоков, так как иначе чтение будет медленным, плюс повысится риск OOM.

  • Во всех случаях не было радикально большого прироста в производительности, максимум — в 2-3 раза. А потому возможно, что переписывать старое legacy приложение на асинхронное чтение не стоит.

  • Однако для новых программ async доступ к файлам как минимум уменьшит вероятность падений и увеличит скорость.
Tags:
Hubs:
If this publication inspired you and you want to support the author, do not hesitate to click on the button
+17
Comments 18
Comments Comments 18

Articles

Information

Website
dbtc-career.ru
Registered
Founded
2001
Employees
1,001–5,000 employees
Location
Россия