FileStream очень медленный при холодном запуске приложения

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

Код, определяющий производительность, состоит из конструктора FileStream (для открытия файла) и хэша SHA1 (реализация платформы .Net). Код в значительной степени является версией С# того, что было задано в вопросе, на который я ссылался выше.

Случай 1: Приложение запускается либо в первый, либо в N-й раз, но с другим набором целевых файлов. Теперь приложению предлагается вычислить хеш-значения для файлов, к которым раньше никто не обращался.

  • ~50ms
  • 80% конструктор FileStream
  • 18% вычисление хэша

Вариант 2: приложение теперь полностью завершено и запущено снова с запросом на вычисление хэша для тех же файлов:

  • ~8ms
  • 90% вычисление хэша
  • 8% конструктор FileStream

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

Итак, мой этап определения скорости — это FileStream Constructor! Могу ли я что-нибудь сделать, чтобы ускорить этот вариант использования?

Спасибо.

P.S. Статистика собиралась с помощью профилировщика JetBrains.


person Alex K    schedule 02.11.2009    source источник
comment
Я наблюдаю точно такое же поведение. Использование ReadAllBytes и вычисление хэша занимает короткое время, но может быть ужасным в памяти (в зависимости от размера файла). Поэтому я попытался передать FileStream в вычисление хэша MD5 ($stream) и увидел совершенно неприемлемые результаты. Время результата отличается на порядки...   -  person thepip3r    schedule 28.03.2014
comment
@Alex K: просмотрев FileStream источник похоже, что существует множество директив препроцессора, и даже в самых простых путях инициализация кажется довольно сложной. Вы можете указать, с каким именно конструктором у вас возникли проблемы и как выглядит ваша среда, и, надеюсь, люди (намного) умнее меня смогут вам помочь.   -  person Griswald_911    schedule 03.08.2018
comment
Вы правильно используете многопоточность? Да, шпиндели ограничены, но при доступе к большому количеству небольших файлов один за другим вы можете потратить большую часть времени на ожидание данных, и вы МОЖЕТЕ немного ускорить это (или больше в SSD), используя многопоточность. Не экстремально, но 2-3 потока МОГУТ помочь сократить время простоя.   -  person TomTom    schedule 06.08.2018
comment
Нужно больше информации. Как именно вы вызываете конструктор? Каковы ваши аргументы и как они выглядят? Абсолютный путь или относительный путь? Вы разрабатываете свою программу для уменьшения/устранения накладных расходов JIT? Вы работаете в сценарии с ограниченным доверием или ваше приложение имеет полное доверие? Вы пытались открыть файл с помощью FileOptions.SequentialScan? Вы пытались включить многоядерный jit через ProfileOptimization.StartProfile?   -  person antiduh    schedule 06.08.2018
comment
@antiduh все, что занимает 10 секунд, очевидно, не является функцией этих параметров, а является ошибкой, которую я не ожидаю исправить.   -  person ExercisingMathematician    schedule 07.08.2018
comment
@DanielDonnelly - Вы отвечаете на вопрос, который не был задан. Вы не постер, и постер не указал, что у них проблема с 10-секундными задержками; их задержка составляет 50 мс. Пожалуйста, не пытайтесь изменить тему вопроса другого автора; вместо этого спросите у себя. Если у вас возникли проблемы с задаванием собственных вопросов из-за низкой репутации, я могу предложить вам прочитать справочный центр о том, как задавать продуктивные вопросы.   -  person antiduh    schedule 07.08.2018


Ответы (5)


... но с другим набором целевых файлов.

Ключевая фраза: ваше приложение не сможет использовать кеш файловой системы. Как это было во втором измерении. Информация о каталоге не может поступать из ОЗУ, потому что она еще не была прочитана, ОС всегда должна возвращаться к жесткому диску, а это медленно.

Только лучшее оборудование может ускорить его. 50 мс — это примерно стандартное время, необходимое для привода шпинделя, 20 мс — это минимальное время, на которое такие приводы могут работать. Время поиска головки считывателя является жестким механическим ограничением. Сегодня это легко превзойти, твердотельные накопители широко доступны и доступны по цене. Единственная проблема с ним в том, что, когда вы к нему привыкнете, вы никогда не отойдете назад :)

person Community    schedule 06.08.2018

Файловая система и/или контроллер диска будут кэшировать недавно использованные файлы/сектора.

Шаг, определяющий скорость, — это чтение файла, а не создание объекта FileStream, и совершенно нормально, что второй запуск будет значительно быстрее, когда данные находятся в кэше.

person Joe    schedule 02.11.2009
comment
Я не верю, что это так. Конструктор FileStream не читает весь файл, для этого вызывается хеш-функция. Но именно конструктор занимает 80% времени. - person Alex K; 03.11.2009

Неправильное предложение, но это то, что я много сделал и получил наши анализы на 30% - 70% быстрее:

Кэширование


Напишите еще один фрагмент кода, который будет:

  • перебрать все файлы;
  • вычислить хэш; а также,
  • сохраните его в другом файле index.

Теперь не вызывайте конструктор FileStream для вычисления хэша при запуске приложения. Вместо этого откройте (ожидаемо намного) меньший индексный файл и прочитайте из него предварительно вычисленный хэш.

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

Таким образом, ваше приложение всегда сможет прочитать хэш только из индексного файла.


Я согласен с предложением @HansPassant использовать твердотельные накопители, чтобы ускорить чтение с диска. Этот ответ и его ответ являются бесплатными. Вы можете реализовать оба, чтобы максимизировать производительность.

person displayName    schedule 06.08.2018
comment
Моя проблема в том, что загрузка простого png размером 8 КБ занимает 15 секунд. - person ExercisingMathematician; 06.08.2018
comment
@DanielDonnelly: данные размером 8 КБ ни в коем случае не достаточно велики, чтобы загружать их 15 секунд. Возможно, к вашему жесткому диску обращаются несколько программ. Для этого попробуйте поместить свои данные на другой диск и посмотрите, сократится ли время доступа почти до мгновенного. Кроме того, ваш диск в сети? - person displayName; 06.08.2018
comment
нет не в сети. Странно, что все остальные программы на моей машине работают, кроме одной, пытающейся загрузить небольшую текстуру, которую я написал на C# с помощью Monogame. Это заставило меня остановить проект и больше не использовать C#, если я начну его снова. Изображения, которые мне нужно генерировать на лету, представляют собой визуализацию небольшого количества текста в LaTeX. Даже LaTeX рендерит быстрее: 2 секунды, что приемлемо для моего приложения при кэшировании, как вы говорите, но ожидание 10-15 секунд нецелесообразно. Я только начал использовать моноигру, поэтому моя среда на 100% самая последняя версия всего. - person ExercisingMathematician; 07.08.2018
comment
@DanielDonnelly: Пожалуйста, отредактируйте вопрос и добавьте в него свой полный код ... в качестве примера ситуации. Это крайне странное поведение. Может ли это быть связано с какой-либо родной библиотекой, которую вы используете? - person displayName; 07.08.2018
comment
Они заблокировали мне возможность задавать вопросы. Каждый раз, когда меня разбанивают, я задаю идеальный вопрос, а затем снова получаю бан. Смотрите мою историю. Поэтому я не могу задать вопрос. Хотя я на 100% способен создать минимальный рабочий пример и отлично объяснить его на английском языке. - person ExercisingMathematician; 07.08.2018
comment
Могу ли я отправить вам минимальный рабочий пример? Я пробовал 5 разных фрагментов кода, но общность и неудача каждого из них заключаются в вызове FileStream ctor. - person ExercisingMathematician; 07.08.2018
comment
@DanielDonnelly: Хотя вы, возможно, не сможете задать новый вопрос, вы можете отредактировать этот вопрос и добавить свой код. Это возможно? - person displayName; 07.08.2018
comment
@displayName — это не очень хорошая идея. Даниэль не должен изменять чужой вопрос, чтобы служить себе. Если он не может задать вопрос из-за низкой репутации после неоднократных взаимодействий с SO, то это признак того, что он не добросовестно участвует. - person antiduh; 07.08.2018

Как было сказано ранее, файловая система имеет собственный механизм кэширования, который мешает вашим измерениям.

Однако конструктор FileStream выполняет несколько задач, которые в первый раз являются дорогостоящими и требуют доступа к файловой системе (поэтому то, чего может не быть в кеше данных). Для пояснения вы можете взглянуть на код и увидеть, что классы CompatibilitySwitches используются для обнаружения использования подфункций. Вместе с этим классом активно используется Reflection как напрямую (для доступа к текущей сборке), так и косвенно (для разделов, защищенных CAS, требуется ссылка безопасности). Движок Reflection имеет собственный кеш и требует доступа к файловой системе, когда его собственный кеш пуст.

Немного странно, что эти два измерения настолько разные. В настоящее время у нас есть что-то подобное на наших машинах, оснащенных антивирусным программным обеспечением, настроенным с защитой в реальном времени. В этом случае антивирусное программное обеспечение находится посередине, и кеш попадает или не попадает в первый раз в зависимости от реализации такого программного обеспечения.

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

Профилирование с использованием собственных символов и/или отладки ядра должно дать вам больше информации.

Исходя из моего опыта, то, что вы описываете, не может быть смягчено, поскольку существует несколько скрытых уровней, которые мы не можем контролировать. В зависимости от вашего использования, которое мне сейчас не совсем понятно, вы можете превратить приложение в службу, поэтому вы можете быстрее обслуживать все последующие запросы. В качестве альтернативы вы можете объединить несколько запросов в один вызов, чтобы получить амортизированную сниженную стоимость.

person Yennefer    schedule 06.08.2018

Вам следует попробовать использовать собственный FILE_FLAG_SEQUENTIAL_SCAN, придется вызвать CreateFile, чтобы получить дескриптор и передать его FileStream

person Shay Erlichmen    schedule 02.11.2009
comment
Не использует конструктор, который принимает FileOptions сделать это уже с потоком, если вы передадите его FileOptions.SequentialScan? РЕДАКТИРОВАТЬ: да, это так, просматривая справочный источник, параметр перечисления превращается в dwFlagsAndAttributes из CreateFile, а значение перечисления равно 0x08000000, что является тем же значением, что и FILE_FLAG_SEQUENTIAL_SCAN - person Scott Chamberlain; 29.03.2014