A fonte de pontos é mais lenta do que apenas ler o conteúdo do arquivo?

13

Eu escrevi um módulo do PowerShell que extrai definições de funções de diferentes arquivos de origem (ou seja, um arquivo .ps1 por função). Isso nos permite (em equipe) trabalhar em diferentes funções em paralelo. O módulo (arquivo .psm1) obtém a lista de arquivos .ps1 disponíveis ...

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

... então percorre a lista e extrai cada definição de função via fonte de pontos:

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

Problema: Percebemos que a velocidade com que isso é concluído pode variar bastante, de 10 a 180 segundos para cerca de 50 arquivos de origem, dependendo da máquina em que testamos. Não podemos explicar a grande variação no tempo gasto e acreditamos que controlamos variáveis ​​como tipo de máquina, sistema operacional, conta de usuário, permissões de administrador, perfil PS, versão PS etc. O tempo gasto pode variar no mesmo host para o mesmo usuário de um dia para o outro.

Nós nos perguntamos se isso era um problema com o acesso ao disco e testamos a rapidez com que poderíamos simplesmente ler do disco. Acontece que a execução de Get-Contenttodos esses arquivos foi muito rápida, da qual aproveitamos uma solução alternativa para o problema:

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

Por que adicionar essas funções através da fonte de pontos é muito mais lenta do que ler e executar o conteúdo do arquivo?

Charlie Joynt
fonte

Respostas:

17

Configurando a ciência

Primeiro, alguns scripts para nos ajudar a testar isso. Isso gera 2000 arquivos de script, cada um com uma única função pequena:

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

Isso deve ser suficiente para fazer com que a sobrecarga normal da inicialização não importe muito. Você pode adicionar mais, se quiser. Isso carrega todos eles usando a fonte de pontos:

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

Isso carrega todos eles, lendo primeiro o conteúdo:

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

Agora precisamos fazer uma inspeção séria de como o PowerShell funciona. Eu gosto do JetBrains dotPeek para um descompilador. Se você já tentou incorporar o PowerShell em um aplicativo .NET , verá que o assembly que inclui a maioria dos itens relevantes é System.Management.Automation. Descompile esse em um projeto e um PDB.

Para ver onde todo esse tempo misterioso está sendo gasto, usaremos um criador de perfil. Eu gosto daquele incorporado ao Visual Studio. É muito fácil de usar . Adicione a pasta que contém o PDB aos locais dos símbolos . Agora, podemos executar uma criação de perfil de uma instância do PowerShell que apenas executa um dos scripts de teste. (Defina os parâmetros da linha de comando a serem usados -Filecom o caminho completo do primeiro script a tentar. Defina o local de inicialização como a pasta que contém todos os pequenos scripts.) Depois que esse for feito, abra as Propriedades na powershell.exeentrada em Destinos e altere os argumentos para usar o outro script. Em seguida, clique com o botão direito do mouse no item mais alto no Performance Explorer e escolha Iniciar criação de perfil. O criador de perfil é executado novamente usando o outro script. Agora podemos comparar. Certifique-se de clicar em "Mostrar todo o código" se for dada a opção; para mim, isso aparece em uma área de Notificações na exibição Resumo do Relatório de criação de perfil de amostra.

Os resultados vêm em

Na minha máquina, a Get-Contentversão levou 9 segundos para percorrer os arquivos de script de 2000. As funções importantes no "Hot Path" foram:

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

Isso faz muito sentido: temos que esperar para Get-Contentler o conteúdo do disco, e temos que esperar para Invoke-Expressionfazer uso desses conteúdos.

Na versão fonte de pontos, minha máquina passou um pouco mais de 15 segundos para trabalhar com esses arquivos. Desta vez, as funções no Hot Path eram métodos nativos:

WinVerifyTrust
CodeAuthzFullyQualifyFilename

O segundo parece não estar documentado, mas WinVerifyTrust"executa uma ação de verificação de confiança em um objeto especificado". Isso é o mais vago possível, mas em outras palavras, essa função verifica a autenticidade de um determinado recurso usando um determinado provedor. Observe que não habilitei nenhum recurso sofisticado de segurança para o PowerShell, e minha política de execução de scripts é Unrestricted.

O que isso significa

Em resumo, você está aguardando a verificação de cada arquivo de alguma forma, provavelmente marcada por uma assinatura, mesmo que isso não seja necessário quando você não restringe os scripts que podem ser executados. Quando você gce iexo conteúdo são como se você tivesse digitado as funções no console, não há recurso para verificar.

Ben N
fonte
2
Ben, obrigado por esta excelente resposta. Impressionado que você chegou ao ponto de descompilar, o que é um passo além de qualquer coisa que tentei. Vou ver se há alguma maneira de seguir seu método de teste em uma das máquinas em que esse problema é mais grave. Isso pode demorar um pouco, então não prenda a respiração!
precisa saber é o seguinte