Точечный поиск медленнее, чем просто чтение содержимого файла?

13

Я написал модуль PowerShell, который извлекает определения функций из разных исходных файлов (т.е. по одному файлу .ps1 на функцию). Это позволяет нам (как команде) параллельно работать над различными функциями. Модуль (файл .psm1) получает список доступных файлов .ps1 ...

$Functions = Get-ChildItem -Path $FunctionPath *.ps1

... затем перебирает список и извлекает каждое определение функции через точечный источник:

foreach($Function in $Functions) {
  . $Function.Fullname                                     # Can be slow
}

Проблема: мы заметили, что скорость, с которой это завершается, может сильно варьироваться, от 10 до 180 секунд для примерно 50 исходных файлов, в зависимости от того, на какой машине мы тестируем. Мы не можем объяснить большую разницу во времени и считаем, что мы контролировали такие переменные, как тип машины, ОС, учетная запись пользователя, права администратора, профиль PS, версия PS и т. Д. Время, затрачиваемое на один и тот же хост для одного и того же хоста Пользователь от одного дня до следующего.

Мы действительно задавались вопросом, была ли это проблема с доступом к диску, и проверяли, насколько быстро мы можем просто читать с диска. Оказывается, что работа со Get-Contentвсеми этими файлами была очень быстрой, что мы использовали в качестве обходного пути к проблеме:

foreach($Function in $Functions) {
  Invoke-Expression (Get-Content $Function.Fullname -Raw)  # Is quick
}

Почему добавление этих функций с помощью точечного поиска происходит намного медленнее, чем чтение и выполнение содержимого файла?

Чарли Джойнт
источник

Ответы:

17

Настройка науки

Сначала несколько скриптов, которые помогут нам проверить это. Это создает 2000 файлов сценариев, каждый из которых имеет одну маленькую функцию:

1..2000 | % { "Function Test$_(`$someArg) { Return `$someArg * $_ }" > "test$_.ps1" }

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

dir test*.ps1 | % {. $_.FullName}

Это загружает их всех, сначала читая их содержимое:

dir test*.ps1 | % {iex (gc $_.FullName -Raw)}

Теперь нам нужно провести серьезную проверку работы PowerShell. Мне нравится JetBrains dotPeek для декомпилятора. Если вы когда-либо пытались встроить PowerShell в приложение .NET , вы обнаружите, что сборка, которая включает в себя большинство соответствующих материалов, является System.Management.Automation. Декомпилируйте это в проект и PDB.

Чтобы увидеть, где проводится все это таинственное время, мы будем использовать профилировщик. Мне нравится тот, который встроен в Visual Studio. Это очень просто в использовании . Добавьте папку, содержащую PDB, в расположение символов . Теперь мы можем выполнить профилирование экземпляра PowerShell, который просто запускает один из тестовых сценариев. (Установите параметры командной строки для использования -Fileс полным путем первого сценария, который нужно попробовать. Установите место запуска для папки, содержащей все крошечные сценарии.) Как только это будет сделано, откройте Свойства в powershell.exeзаписи в разделе Цели и измените аргументы для использования другого скрипта. Затем щелкните правой кнопкой мыши самый верхний элемент в Performance Explorer и выберите Start Profiling., Профилировщик снова запускается с использованием другого сценария. Теперь мы можем сравнить. Убедитесь, что вы нажали «Показать весь код», если вы выбрали эту опцию; для меня это отображается в области уведомлений в сводном представлении примера отчета о профилировании.

Результаты приходят в

На моей машине Get-Contentверсия заняла 9 секунд, чтобы просмотреть файлы сценариев 2000 года. Важными функциями на «Горячем пути» были:

Microsoft.PowerShell.Commands.GetContentCommand.ProcessRecord
Microsoft.PowerShell.Commands.InvokeExpressionCommand.ProcessRecord

Это имеет большой смысл: нам нужно ждать, Get-Contentчтобы прочитать контент с диска, и нам нужно ждать, Invoke-Expressionчтобы использовать это содержимое.

В версии с точечным источником моя машина потратила чуть более 15 секунд на обработку этих файлов. На этот раз функции на Hot Path были нативными методами:

WinVerifyTrust
CodeAuthzFullyQualifyFilename

Второй из них, по-видимому, недокументирован, но WinVerifyTrust«выполняет действие проверки доверия с указанным объектом». Это настолько неопределенно, насколько это возможно, но, другими словами, эта функция проверяет подлинность данного ресурса с помощью данного поставщика. Обратите внимание, что я не включил какие-либо необычные средства безопасности для PowerShell, и моя политика выполнения сценариев такова Unrestricted.

Что это значит

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

Бен Н
источник
2
Бен, спасибо за этот превосходный ответ. Впечатлило, что вы дошли до декомпиляции, что является шагом за пределы всего, что я пробовал. Я посмотрю, смогу ли я как-нибудь следовать вашему методу тестирования на одной из машин, где эта проблема наиболее остра. Это может занять много времени, поэтому не задерживайте дыхание!
Чарли Джойнт