12

Я написал модуль 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
}

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

1 ответ1

14

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

Сначала несколько скриптов, которые помогут нам проверить это. Это создает 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 содержания, это как вы ввели функции на консоли, так что нет ресурсов для проверки.

Всё ещё ищете ответ? Посмотрите другие вопросы с метками .