Apakah dot-sourcing lebih lambat dari sekadar membaca konten file?

13

Saya telah menulis modul PowerShell yang menarik definisi fungsi dari file sumber yang berbeda (yaitu satu file .ps1 per fungsi). Ini memungkinkan kami (sebagai tim) untuk bekerja pada fungsi yang berbeda secara paralel. Modul (file .psm1) mendapatkan daftar file .ps1 yang tersedia ...

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

... kemudian lewati daftar dan tarik di setiap definisi fungsi melalui dot-sourcing:

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

Masalah: Kami telah memperhatikan bahwa kecepatan penyelesaian ini dapat sangat bervariasi, dari 10 hingga 180 detik untuk sekitar 50 file sumber, tergantung pada mesin apa yang kami uji. Kami tidak dapat menjelaskan variasi luas dalam waktu yang diambil, dan percaya kami telah mengendalikan variabel seperti jenis mesin, OS, akun pengguna, izin admin, profil PS, versi PS, dll. Waktu yang dibutuhkan dapat bervariasi pada host yang sama untuk yang sama pengguna dari satu hari ke hari berikutnya.

Kami bertanya-tanya apakah ini merupakan masalah dengan akses disk dan menguji seberapa cepat kita bisa membaca dari disk. Ternyata menjalankan Get-Contentsemua file itu sangat cepat, yang telah kami manfaatkan dalam penyelesaian masalah:

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

Mengapa menambahkan fungsi-fungsi ini melalui dot-sourcing jauh lebih lambat daripada membaca dan mengeksekusi konten file?

Charlie Joynt
sumber

Jawaban:

17

Menyiapkan sains

Pertama, beberapa skrip untuk membantu kami menguji ini. Ini menghasilkan 2000 file skrip, masing-masing dengan fungsi kecil tunggal:

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

Itu seharusnya cukup untuk membuat overhead startup normal tidak terlalu penting. Anda dapat menambahkan lebih banyak jika mau. Ini memuat semuanya menggunakan dot-sourcing:

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

Ini memuat semuanya dengan membaca kontennya terlebih dahulu:

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

Sekarang kita perlu melakukan beberapa pemeriksaan serius tentang cara kerja PowerShell. Saya suka JetBrains dotPeek untuk dekompiler. Jika Anda pernah mencoba menanamkan PowerShell di aplikasi .NET , Anda akan menemukan bahwa perakitan yang mencakup sebagian besar hal yang relevan adalah System.Management.Automation. Dekompilasi yang menjadi proyek dan PDB.

Untuk melihat di mana semua waktu misterius ini dihabiskan, kami akan menggunakan profiler. Saya suka yang terintegrasi dengan Visual Studio. Ini sangat mudah digunakan . Tambahkan folder yang berisi PDB ke lokasi simbol . Sekarang, kita bisa melakukan profiling instance dari PowerShell yang hanya menjalankan salah satu skrip pengujian. (Tetapkan parameter baris perintah untuk digunakan -Filedengan path lengkap dari skrip pertama yang akan dicoba. Tetapkan lokasi startup ke folder yang berisi semua skrip kecil.) Setelah itu selesai, buka Properties pada powershell.exeentri di bawah Target dan ubah argumen untuk menggunakan skrip lainnya. Kemudian klik kanan item paling atas di Performance Explorer dan pilih Mulai Profiling. Profiler berjalan lagi menggunakan skrip lain. Sekarang kita bisa membandingkan. Pastikan Anda mengklik "Tampilkan Semua Kode" jika diberi opsi; bagi saya, itu muncul di area Pemberitahuan di tampilan Ringkasan Laporan Profil Sampel.

Hasilnya masuk

Di komputer saya, Get-Contentversi ini membutuhkan waktu 9 detik untuk membaca 2000 file skrip. Fungsi penting di "Jalur Panas" adalah:

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

Ini sangat masuk akal: kita harus menunggu untuk Get-Contentmembaca konten dari disk, dan kita harus menunggu untuk Invoke-Expressionmemanfaatkan konten tersebut.

Pada versi dot-source, mesin saya menghabiskan lebih dari 15 detik untuk mengerjakan file-file itu. Kali ini, fungsi di Hot Path adalah metode asli:

WinVerifyTrust
CodeAuthzFullyQualifyFilename

Yang kedua di sana tampaknya tidak berdokumen, tetapi WinVerifyTrust"melakukan tindakan verifikasi kepercayaan pada objek tertentu." Itu hampir tidak jelas seperti yang Anda dapatkan, tetapi dengan kata lain, fungsi itu memverifikasi keaslian sumber daya yang diberikan menggunakan penyedia yang diberikan. Perhatikan bahwa saya belum mengaktifkan barang keamanan mewah untuk PowerShell, dan kebijakan eksekusi skrip saya adalah Unrestricted.

Apa itu artinya

Singkatnya, Anda menunggu setiap file untuk diverifikasi dengan suatu cara, mungkin memeriksa tanda tangan, meskipun itu tidak perlu ketika Anda tidak membatasi skrip yang diizinkan untuk dijalankan. Ketika Anda gcdan kemudian iexisinya, sepertinya Anda telah mengetik fungsi di konsol, jadi tidak ada sumber daya untuk memverifikasi.

Ben N
sumber
2
Ben, terima kasih atas jawaban yang luar biasa ini. Terkesan bahwa Anda melakukan de-compiling, yang merupakan langkah di atas apa pun yang saya coba. Saya akan melihat apakah ada cara saya dapat mengikuti metode pengujian Anda di salah satu mesin di mana masalah ini paling akut. Ini bisa memakan waktu lama jadi jangan menahan nafas!
Charlie Joynt