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

12

Eu escrevi um módulo do PowerShell que extrai definições de funções de arquivos de origem diferentes (por exemplo, um arquivo .PS1 por função). Isso nos permite (como 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 puxa cada definição de função via sourcing de ponto:

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

Problema: Percebemos que a velocidade com a qual isso é concluído pode variar muito, de 10 a 180 segundos para cerca de 50 arquivos de origem, dependendo de qual máquina testamos. Não podemos explicar a grande variação no tempo gasto, e acreditamos ter controlado variáveis como tipo de máquina, SO, 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.

Perguntamos se isso era um problema com o acesso ao disco e testamos a velocidade com que poderíamos simplesmente ler do disco. Acontece que a execução de Get-Content em todos esses arquivos foi muito rápida, o que aproveitamos em uma solução alternativa para o problema:

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

Pergunta: Minha pergunta aqui é: alguém pode explicar por que adicionar essas funções via sourcing de ponto é muito mais lento do que ler e executar o conteúdo do arquivo?

    
por Charlie Joynt 23.01.2017 / 16:39

1 resposta

12

Configurando ciência

Primeiro, alguns scripts nos ajudam 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 o suficiente para tornar a sobrecarga normal de inicialização não importa muito. Você pode adicionar mais, se quiser. Isso carrega todos eles usando dot-sourcing:

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

Isso carrega todos 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 de JetBrains dotPeek para um descompilador. Se você já tentou incorporar o PowerShell em um .NET aplicação , você verá que a montagem que inclui a maioria das coisas relevantes é System.Management.Automation . Decompile aquele em um projeto e um PDB.

Para ver onde todo esse tempo misterioso está sendo gasto, usaremos um profiler. Eu gosto do criado no Visual Studio. É muito fácil de usar . Adicione a pasta que contém o PDB para os locais dos símbolos . Agora, podemos fazer uma execução de 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 para usar -File com o caminho completo do primeiro script a ser testado. Defina o local de inicialização para a pasta que contém todos os minúsculos scripts.) Depois disso, abra as Propriedades no powershell.exe entry sob Targets e altere os argumentos para usar o outro script. Em seguida, clique com o botão direito do mouse no item mais acima no Performance Explorer e escolha Iniciar criação de perfil . O profiler é executado novamente usando o outro script. Agora podemos comparar. Certifique-se de clicar em "Mostrar todos os códigos" se tiver a opção; para mim, que aparece em uma área de Notificações na visualização Resumo do Relatório de Criação de Perfil de Amostra.

Os resultados vêm em

Na minha máquina, a versão Get-Content 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 que Get-Content leia o conteúdo do disco, e temos que esperar que Invoke-Expression faça uso desses conteúdos.

Na versão de código-fonte, minha máquina gastou um pouco mais de 15 segundos para trabalhar nesses 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 é tão vago quanto 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 material sofisticado de segurança para o PowerShell e minha política de execução de scripts é Unrestricted .

O que isso significa

Em suma, você está esperando que cada arquivo seja verificado de alguma forma, provavelmente verificado por uma assinatura, mesmo que isso não seja necessário quando você não restringir os scripts que podem ser executados. Quando você usa gc e iex do conteúdo, é como se tivesse digitado as funções no console, portanto, não há recursos para verificar.

    
por 25.01.2017 / 02:03