- PVSM.RU - https://www.pvsm.ru -

Почему для открытия меню Windows читает один файл сто тысяч раз?

Почему для открытия меню Windows читает один файл сто тысяч раз? - 1

«Проводник тратит 700 мс на то, чтобы открыть контекстное меню панели задач. 75% этого времени он выполняет 114 801 операцию считывания из одного файла, средний объём считываемых данных 68 байт.

Мне стоит написать пост об этом, или достаточно саркастичного твита?»

За компьютером я работаю быстро, и поэтому меня раздражает, когда приходится ждать завершения операции, которая должна выполняться мгновенно. Постоянной помехой на моём сверхмощном домашнем ноутбуке стало медленное закрывание окон на панели задач. Я нажимаю правой клавишей на значок, жду, пока откроется меню, а затем выбираю «Закрыть окно». Самым медленным в этом процессе должны быть перемещения мыши, но выясняется, что наиболее долгим компонентом оказывается задержка перед появлением меню.

Это напрягало меня уже давно, но я проявлял нехарактерный мне самоконтроль и удерживался от раздражения. Так было до сегодняшнего дня, когда я наконец сорвался и схватился за трассировщик ETW [1].

Этот пост написан как проверка скоростного блогинга. От момента нахождения проблемы и саркастичного твита [2] о ней до публикации поста прошло примерно 90 минут.


Трассировщик ETW фиксировал, как я нажимаю правой клавишей мыши на панели задач и закрываю два окна «Проводника». Я использовал функцию UIforETW [3] Tracing to file с опциями по умолчанию, получив в результате лог диагностики на 20,9 МБ [4].

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

Первым сигналом были события ввода. UIforETW содержит интегрированный логгер ввода [5] (достаточно анонимизированный, чтобы я случайно не украл пароли или личную информацию), поэтому мне достаточно было подробно изучить события MouseUp и Button Type со значением 2, соответствующим правой клавише мыши. При этом на временной шкале WPA [6] появляются отметки моментов, когда происходят эти события — см. вертикальные линии на скриншоте:

Почему для открытия меню Windows читает один файл сто тысяч раз? - 2

Это дало мне понять, что когда я отпускаю правую клавишу мыши, то спустя 600 мс происходит смена фокуса окна, что, по моей догадке, соответствует моменту отображения меню. Кроме того, в RuntimeBroker.exe присутствует чёткий блок активности ЦП между событиями отпускания клавиши мыши и смены фокуса окна.

Не доказано, что смена фокуса окна и активность ЦП связаны, но сделав измерения при помощи приложения записи экрана, я увидел, что на отображение меню тратится примерно 660 мс, поэтому я склонен этому доверять.

Следующим шагом будет изучение того, чем занят RuntimeBroker.exe. Хотя CPU Usage (Precise) [7] отлично помогает увидеть, сколько времени ЦП использует процесс, и почему он простаивает, подходящим инструментом для выяснения того, на что тратится время ЦП, является таблица CPU Usage (Sampled) [8]. Я внимательно её изучил и быстро обнаружил, что 264 сэмпла приходится на KernelBase.dll!ReadFile:

Почему для открытия меню Windows читает один файл сто тысяч раз? - 3

Поискав ещё немного, я нашёл другие стеки вызовов, которые тоже затрагивали эту функцию, поэтому я нажал правой клавишей мыши и выбрал View Callers-> By Function. Включившийся при этом режим (с инвертированными стеками) показал, что из 899 сэмплов этого процесса 628 сэмплов, или 70%, из разных стеков вызовов проходили через эту функцию:

Почему для открытия меню Windows читает один файл сто тысяч раз? - 4

271 сэмпла в этом потоке не проходили через функцию, а оставшиеся сэмплы (не показаны) находились в других потоках.

Заметьте, что 899 сэмплов в потоке 10 252 обозначают два щелчка мыши, то есть примерно 450 сэмплов или 450 мс (при стандартной частоте сэмплирования 1 кГц) на один щелчок мыши.

Иногда файловый ввод-вывод — это время ЦП

CPU Usage (Sampled) показывает время ЦП, поэтому дисковый ввод-вывод здесь обычно не отображается, ведь в эти моменты поток засыпает и ждёт диск. То, что операции ввода-вывода отображаются как время ЦП, означает, что все считывания пришлись на кэш системы и время ЦП было лишними ресурсами ядра (см. ntoskrnl.exe в первом сэмплированном стеке вызовов), потраченными на получение данных из кэша.

Теперь, когда под подозрение попал файловый ввод-вывод, нам необходимо перейти в Graph Explorer-> Storage-> File I/O. Немного настроив внешний вид столбцов, мы получили следующий впечатляющий результат:

Почему для открытия меню Windows читает один файл сто тысяч раз? - 5

Он сообщает нам, что в потоке 10 252 за два щелчка мыши RuntimeBroker.exe создал 229 604 вызова ReadFile, считав в сумме 15 686 586 байт. То есть в среднем каждый раз считывалось по 68 байт.

Задумайтесь об этом на секунду.

Не забывайте, что это вызовы к операционной системе — вызовы ядра. Это значит, что между RuntimeBroker.exe и этим файлом отсутствует кэширование. На самом деле сам файл имеет длину всего 4 027 904 байт, то есть каждый раз, когда я нажимаю на значок проводника в панели задач, файл считывается 1,9 раза самым медленным из возможных способов.

Подстрекаемый своими читателями, я решил удалить этот файл и теперь меню списка переходов проводника появляются почти мгновенно. Я не рекомендую этого делать, но удаление файла может помочь и вам (ценой потери истории проводника):

%appdata%MicrosoftWindowsRecentAutomaticDestinationsf01b4d95cf55d32a.automaticDestinations-ms

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

Image

Или же «можно воспользоваться официальным прибамбасом из „Параметров“, чтобы очистить (нажать дважды) или отключить (нажать один раз) списки переходов в меню „Пуск“/панели задач. Чтобы ещё больше повысить скорость, отключите также анимации». Спасибо, Рафаэль [9]!

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

Подведём итог

  • Не вызывайте ReadFile, чтобы прочитать 68 байт. Или, по крайней мере, не делайте это сотню тысяч раз. Меня бесит, когда базы данных выполняют считывание по 4 кибибайта [10], но этот случай просто удивителен.
  • Надеюсь, кто-нибудь устранит эту проблему, но пока у меня есть обходное решение — удаление проблемного файла.
  • Надеюсь, кто-нибудь возьмётся за исследование и выяснит, почему вообще тормозят контекстные меню. Эй, я ведь даже поделился трассировкой ETW [4] и всем остальным!
  • Отчёт по официальным каналам передан в https://aka.ms/AA60dfg [11]
  • Если вы хотите научиться проводить подобный анализ, тогда скачайте мой инструмент UIforETW [3] и прочитайте туториалы, ссылки на которые выложены здесь [1]
  • Не забывайте, что можно открыть контекстное меню гораздо быстрее (это проще, чем стандартное меню списка переходов), нажав shift+правой клавишей мыши на значки [12]. Я буду пользоваться этим трюком, когда нужно будет закрывать много окон.
  • Обсуждение на Hacker news находится здесь [13]
  • Обсуждение на Reddit находится здесь [14]
  • Темы в Twitter находятся здесь [2] и здесь [15]

Автор: PatientZero

Источник [16]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/optimizatsiya-koda/329623

Ссылки в тексте:

[1] ETW: https://randomascii.wordpress.com/2015/09/24/etw-central/

[2] саркастичного твита: https://twitter.com/BruceDawson0xB/status/1170924849004367873

[3] UIforETW: https://randomascii.wordpress.com/2015/09/01/xperf-basics-recording-a-trace-the-ultimate-easy-way/

[4] лог диагностики на 20,9 МБ: https://github.com/randomascii/blogstuff/tree/master/TaskbarLatency

[5] логгер ввода: https://github.com/google/UIforETW/blob/master/UIforETW/KeyLoggerThread.cpp

[6] WPA: https://randomascii.wordpress.com/2012/06/19/wpaxperf-trace-analysis-reimagined/

[7] CPU Usage (Precise): https://randomascii.wordpress.com/2012/05/05/xperf-wait-analysisfinding-idle-time/

[8] таблица CPU Usage (Sampled): https://randomascii.wordpress.com/2013/04/23/xperf-for-excess-cpu-consumption-wpa-edition/

[9] Спасибо, Рафаэль: https://twitter.com/WithinRafael/status/1171228479251025920

[10] базы данных выполняют считывание по 4 кибибайта: https://randomascii.wordpress.com/2012/08/19/fixing-another-photo-gallery-performance-bug/

[11] https://aka.ms/AA60dfg: https://aka.ms/AA60dfg

[12] shift+правой клавишей мыши на значки: https://twitter.com/ericlaw/status/1171030922755530753

[13] здесь: https://news.ycombinator.com/item?id=20916072#20919612

[14] здесь: https://www.reddit.com/r/programming/comments/d1ofql/taskbar_latency_and_kernel_calls/

[15] здесь: https://twitter.com/BruceDawson0xB/status/1171107806373539841

[16] Источник: https://habr.com/ru/post/466941/?utm_campaign=466941&utm_source=habrahabr&utm_medium=rss