- PVSM.RU - https://www.pvsm.ru -
Привет!
Некоторое время назад меня заинтересовал вопрос: как эффективнее всего читать данные с диска (при условии, что у вас .Net)? Задача чтения кучи файлов встречается во множестве программ, которые при самом старте начинают вычитывать конфигурации, некоторые самостоятельно подгружают модули и т.д.
В интернете я не нашел подобных сравнений (если не считать тюнинга под определенные конфигурации).
Результаты можно посмотреть на Github [1]: SSD [2], HDD [3].
Есть несколько основных способов:
Тестировал я все на SSD и HDD (в первом случае был компьютер с Xeon 24 cores и 16 Гб памяти и Intel SSD [10], во втором — Mac Mini MGEM2LL/A с Core i5, 4 Гб RAM и HDD 5400-rpm). Системы такие, чтобы по результатам можно было бы понять, как лучше вести себя на относительно современных системах и на не очень новых.
Проект можно посмотреть здесь [11], он представляет собой один главный исполняемый файл TestsHost [12] и кучу проектов с названиями Scenario*. Каждый тест это:
Подготовка к тесту более хитрая. Итак, перед запуском:
И не забываем про обработку ошибок:
И плюс стандартные моменты про нагрузочное тестирование:
Как я уже написал в шапке, результаты есть на Github [1]: SSD [2], HDD [3].
Минимальный размер файла (байты): 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 |
Увеличив минимальный размер файла, я получил:
Минимальный размер файла (байты): 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 Кб, их число меньше, однако сами они больше. И как результат:
Если с 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. Так как результат не был стабильным от запуска к запуску, подобное тестирование я уже счел нецелесообразным.
Какой же результат можно почерпнуть из этих тестов?
Автор: imanushin
Источник [15]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/programmirovanie/258905
Ссылки в тексте:
[1] Github: https://github.com/imanushin/AsyncIOComparison/tree/master/Results
[2] SSD: https://github.com/imanushin/AsyncIOComparison/blob/master/Results/SSD.md
[3] HDD: https://github.com/imanushin/AsyncIOComparison/blob/master/Results/HDD.md
[4] читать с помощью ReadAllText на ThreadPool: https://github.com/imanushin/AsyncIOComparison/blob/master/ScenarioReadAllAsParallel/StartClass.cs
[5] читать с помощью Stream'ов синхронно на Thread Pool: https://github.com/imanushin/AsyncIOComparison/blob/master/ScenarioSyncAsParallel/StartClass.cs
[6] читать с помощью Stream'ов синхронно и на отдельном потоке для каждого чтения, время на запуск нового потока тоже учитывается: https://github.com/imanushin/AsyncIOComparison/blob/master/ScenarioNewThread/StartClass.cs
[7] читать с помощью Stream'ов асинхронно (т.е. async/await, если файловая система долго отвечает, то параллельно может запуститься множество операций): https://github.com/imanushin/AsyncIOComparison/blob/master/ScenarioAsync/StartClass.cs
[8] асинхронное чтение, однако и старт происходит тоже во много потоков (а не последовательно на одном и том же потоке Main, как в предыдущем тесте): https://github.com/imanushin/AsyncIOComparison/blob/master/ScenarioAsync2/StartClass.cs
[9] читать с помощью Stream'ов асинхронно (т.е. async/await), но не больше, чем в N параллельных операций: https://github.com/imanushin/AsyncIOComparison/blob/master/ScenarioAsyncWithMaxParallelCount/StartClass.cs
[10] Intel SSD: http://ark.intel.com/products/81043/Intel-SSD-Pro-2500-Series-180GB-2_5in-SATA-6Gbs-20nm-MLC
[11] здесь: https://github.com/imanushin/AsyncIOComparison
[12] TestsHost: https://github.com/imanushin/AsyncIOComparison/tree/master/TestsHost
[13] Performance Counters: https://msdn.microsoft.com/en-us/library/windows/desktop/aa373083(v=vs.85).aspx
[14] Performance Counter «Concurrent Queue Length»: https://msdn.microsoft.com/en-us/library/zf749bat(v=vs.71).aspx
[15] Источник: https://habrahabr.ru/post/331668/
Нажмите здесь для печати.