Mengukur performa kode dan membuat garis besar
Mengukur performa kode dan menetapkan garis besar adalah langkah penting dalam proses pengoptimalan. Ini menyediakan titik referensi untuk mengevaluasi hasil setiap perubahan yang Anda buat.
Menetapkan garis besar performa
Garis besar performa adalah serangkaian pengukuran yang menangkap performa kode sebelum Anda membuat perubahan apa pun. Ini adalah titik referensi Anda. Tanpa garis besar untuk perbandingan, Anda tidak tahu apakah pembaruan Anda meningkatkan performa kode atau memperburuk keadaan.
Berikut adalah beberapa aspek utama untuk diukur dalam garis besar:
Waktu eksekusi (latensi): Berapa lama waktu yang diperlukan untuk menjalankan operasi atau proses tertentu? Nilai ini dapat diukur untuk fungsi kecil (misalnya, dibutuhkan 5 milidetik untuk mengurutkan 10.000 item) atau skenario end-to-end (misalnya, layanan merespons permintaan dalam 250 milidetik).
Throughput: Berapa banyak operasi yang dapat dilakukan dalam waktu tertentu? Nilai ini relevan untuk aplikasi server (permintaan per detik), pemrosesan batch (rekaman diproses per menit), dll.
Penggunaan sumber daya: Konsumsi memori (set kerja, alokasi, frekuensi pengumpulan sampah), penggunaan CPU (apakah mematok inti pada 100%? untuk berapa lama?), I/O disk, I/O jaringan, dll., tergantung pada apa yang dilakukan kode.
Karakteristik skalabilitas: Bagaimana metrik di atas berubah seiring bertambahnya ukuran input atau saat beban (pengguna bersamaan) meningkat? Anda dapat mengukur bahwa fungsi membutuhkan waktu 0,1 milidetik untuk 100 item dan 10 milidetik untuk 10.000 item. Itu menunjukkan bagaimana skalanya (dalam hal ini, kira-kira linier dengan jumlah item). Garis besar semacam ini membantu Anda memahami skalabilitas dan menetapkan tujuan. Misalnya, Anda mungkin ingin mengurutkan 100k item dalam waktu kurang dari 200 milidetik, yang mungkin berarti Anda memerlukan algoritma atau pengurutan paralel yang lebih efisien.
Saat membuat garis besar:
Buat lingkungan se-konsisten mungkin. Jalankan pengujian pada komputer yang sama, dalam kondisi yang sama (tutup program latar belakang, gunakan build rilis, dll.). Kinerja dapat bervariasi dari satu pelaksanaan ke pelaksanaan berikutnya, dengan demikian meminimalkan kebisingan memberikan data yang lebih andal.
Gunakan build rilis dan pengaturan realistis. Build debug sering berjalan lebih lambat, karena pemeriksaan tambahan dan tidak adanya pengoptimalan. Jika Anda mengukur kecepatan algoritma, kompilasi dalam mode Rilis dengan pengoptimalan aktif. Demikian pula, jika kode Anda multi-utas atau asinkron, uji dalam skenario yang menyerupai produksi (misalnya, jumlah utas yang sama atau ukuran himpunan data yang sama).
Mengotomatiskan atau membuat skrip pengukuran. Anda harus menjalankan kode beberapa kali. Ini membantu memiliki kerangka tolok ukur yang sederhana atau menggunakan alat yang mengotomatiskan proses tersebut. Misalnya, untuk fungsi kecil, Anda mungkin menulis perulangan yang memanggilnya, mengukur waktu, dan menghitung rata-rata.
Catat angkanya. Simpan catatan metrik garis besar (waktu, memori, dll.). Jika Anda memiliki pengujian unit atau output log yang menampilkan informasi kinerja, simpanlah. Untuk kasus nontrivial, menggunakan spreadsheet atau tabel sederhana untuk membandingkan "sebelum vs sesudah" berguna.
Pengukuran garis besar menggunakan Stopwatch
C# menawarkan System.Diagnostics.Stopwatch kelas , yang berguna untuk mengukur waktu eksekusi blok kode.
Misalnya, Anda memiliki metode berikut ProcessData() yang Anda duga lambat:
var sw = Stopwatch.StartNew();
ProcessData();
sw.Stop();
Console.WriteLine($"ProcessData() completed in {sw.ElapsedMilliseconds} milliseconds");
Bayangkan bahwa kode ini secara konsisten mencetak nilai sekitar 120 milidetik. Nilai rata-rata tersebut adalah garis besar Anda untuk ProcessData() di bawah kondisi pengujian yang Anda gunakan.
Garis besar untuk penggunaan memori
Metode GC.GetTotalMemory adalah panggilan metode .NET yang mengambil jumlah memori terkini yang dialokasikan oleh garbage collector (GC). Metode ini GetTotalMemory menangkap status alokasi memori saat ini ketika dipanggil.
Anda dapat menangkap penggunaan memori dengan memeriksa GC.GetTotalMemory(false) sebelum dan sesudah operasi.
Misalnya, Anda memiliki kode berikut:
long memoryBefore = GC.GetTotalMemory(forceFullCollection: true);
ProcessData();
long memoryAfter = GC.GetTotalMemory(forceFullCollection: true);
Console.WriteLine($"Memory used by ProcessData(): {memoryAfter - memoryBefore} bytes");
Jika kode ini mencetak, "Memori yang digunakan oleh ProcessData(): 50.000 byte", itulah besarnya memori yang dialokasikan oleh operasi (bersih). Nilai ini dapat menjadi bagian dari garis besar Anda, terutama jika Anda khawatir tentang memori atau overhead pengumpulan sampah.
Garis besar kontekstual
Garis dasar seringkali bukan satu angka. Anda mungkin memiliki garis besar pada skala tertentu dan kemudian menguji pada skala yang lebih besar. Contohnya:
- Mengurutkan 10k item membutuhkan waktu 50 milidetik (patokan pada 10k).
- Mengurutkan 100k item membutuhkan waktu 800 milidetik.
Hasil ini menunjukkan bahwa performa menurun saat input tumbuh, yang diharapkan. Ini juga menunjukkan algoritma mungkin lebih buruk daripada O(n log n) karena meningkatkan item dengan faktor 10 tampaknya meningkatkan waktu dengan faktor 16. Jenis garis besar ini membantu Anda memahami skalabilitas dan menetapkan tujuan.
Alat dan teknik pengukuran performa
Memahami performa memerlukan pengukuran dan analisis karakteristik algoritma. Sebelum menyelami alat pengukuran, penting untuk memahami konsep performa utama:
Memahami kompleksitas algoritma
Kompleksitas waktu menjelaskan bagaimana runtime algoritma tumbuh saat ukuran input meningkat, biasanya dinyatakan menggunakan notasi Big O:
- O(1) - Waktu konstan: Performa tidak berubah dengan ukuran input (misalnya, pencarian kamus).
- O(n) - Waktu linier: Kinerja tumbuh secara proporsional dengan input (misalnya, iterasi melalui daftar satu kali).
- O(n²) - Waktu kuadrat: Performa tumbuh dengan kuadrat ukuran input (misalnya, perulangan berlapis).
- O(log n) - Waktu logaritmik: Performa tumbuh perlahan saat input meningkat (misalnya, pencarian biner).
Kompleksitas ruang mengukur bagaimana penggunaan memori tumbuh dengan ukuran input, menggunakan notasi Big O yang sama. Memahami konsep-konsep ini membantu mengidentifikasi mengapa pola kode tertentu menjadi hambatan dalam skala besar.
Mengenali pola performa dan pola antipola
Anti-pola performa yang umum termasuk:
- Masalah kueri N+1: Membuat satu kueri untuk mendapatkan daftar, lalu N lebih banyak kueri untuk data terkait.
- Struktur data yang tidak efisien: Menggunakan daftar untuk pencarian yang sering, bukan kamus atau kumpulan hash.
-
Penggabungan string prematur: Membangun string besar menggunakan
+=alih-alihStringBuilder. - Operasi sinkron: Memblokir utas dengan I/O sinkron ketika operasi asinkron akan lebih baik.
Strategi caching dapat secara dramatis meningkatkan performa dengan menyimpan data yang sering diakses dalam RAM, menghindari penghitungan ulang yang mahal atau operasi I/O.
Dari yang sederhana hingga canggih, berikut adalah alat dan metode untuk mengukur performa:
Pengaturan waktu manual dengan Stopwatch
Melinstrumentasi kode secara manual dengan menggunakan Stopwatch atau bahkan perbedaan DateTime.UtcNow dapat memberikan wawasan secara cepat. Pendekatan ini ad-hoc tetapi dapat diakses dan sering kali cukup untuk penyelidikan pertama. Anda dapat menaburkan log waktu di sekitar bagian kode (misalnya, mencatat berapa lama waktu yang dibutuhkan kueri database, berapa lama penguraian file berlangsung, dll.).
Pengelogan dan penghitung
Pengelogan bersifat ad-hoc tetapi dapat diakses dan sering kali cukup untuk penyelidikan pertama. Anda dapat menaburkan log waktu di sekitar bagian kode (misalnya, mencatat berapa lama waktu yang dibutuhkan kueri database, berapa lama penguraian file berlangsung, dll.).
Metrik pengelogan peristiwa dan performa
Menambahkan pengelogan strategis dapat mengungkapkan pola performa dan membantu mengidentifikasi hambatan:
- Jumlah operasi: Mencatat berapa banyak item yang diproses, kueri basis data dijalankan, atau hit/miss cache terjadi.
- Perincian waktu: Mengukur fase operasi yang berbeda (misalnya, "Kueri database: 50 milidetik, Pemrosesan: 20 milidetik, Serialisasi: 10 milidetik").
- Pemanfaatan sumber daya: Melacak alokasi memori, penggunaan kumpulan utas, atau metrik kumpulan koneksi.
- Metrik performa: Memantau latensi (waktu respons), throughput (operasi per detik), dan karakteristik skalabilitas.
Misalnya, jika log Anda menunjukkan "Mengambil 1000 rekaman dari database" saat Anda diharapkan melihat 100 rekaman, perbedaan mungkin menunjukkan masalah kueri N+1 atau logika kueri yang tidak efisien. Demikian pula, pencatatan tingkat hit cache dapat membantu mengevaluasi efektivitas strategi pencache-an.
Profiler bawaan (Visual Studio Diagnostics)
Visual Studio (edisi Enterprise, dan komunitas tertentu) memiliki alat pembuatan profil (Penggunaan CPU, Penggunaan Memori, Profiler Performa). Anda dapat menjalankan aplikasi Anda di bawah profiler dan menunjukkan kepada Anda, misalnya, perincian waktu CPU berdasarkan fungsi, atau daftar objek pada tumpukkan dan siapa yang mengalokasikannya.
- Profiler CPU biasanya menghasilkan pohon panggilan, di mana Anda dapat melihat metode mana yang paling banyak menggunakan waktu CPU.
- Profiler memori dapat mengambil rekam jepret untuk menunjukkan bagaimana penggunaan memori tumbuh, dan jenis objek apa yang mengambil ruang.
Menggunakan profiler seringkali sesederhana mengklik "Mulai Diagnostik" dan memilih jenis profil.
Alat .NET CLI
Untuk .NET Core dan .NET 5+, Microsoft menyediakan alat baris perintah seperti dotnet-counters, dotnet-trace, dotnet-dump, dotnet-gcdump.
-
dotnet-countersdapat menampilkan metrik performa real time dari aplikasi yang sedang berjalan (koleksi GC, pengecualian, penggunaan kumpulan utas, dll.). -
dotnet-tracedapat mengumpulkan jejak eksekusi aplikasi, yang dapat dianalisis untuk melihat metode mana yang berjalan.
Alat-alat ini lebih canggih, tetapi sangat berharga untuk penyelaman mendalam atau pembuatan profil produksi di mana Anda tidak dapat melampirkan profiler GUI.
Perpustakaan pengukuran kinerja mikro
Jika Anda ingin membandingkan dua implementasi fungsi (misalnya, versi asli vs yang dioptimalkan yang diusulkan) dengan cara yang ketat, BenchmarkDotNet adalah pustaka populer. Ini menjalankan fungsi berkali-kali, memanaskan kompilator just-in-time, mengukur dengan tepat, serta menyediakan statistik (seperti rata-rata, simpangan standar). Data ini digunakan untuk tolok ukur mikro (jalur kode kecil yang terisolasi) dengan presisi tinggi.
Pengujian kinerja dan beban
Untuk skenario yang lebih besar (aplikasi web, layanan), Anda dapat menulis uji beban atau menggunakan alat (seperti JMeter, k6, atau uji beban Visual Studio) untuk mensimulasikan banyak permintaan atau input besar. Pendekatan ini dapat mengungkapkan throughput dan stabilitas di bawah tekanan, dan membantu mengidentifikasi hambatan yang hanya muncul dalam skala besar.
Pemantauan sistem
Pantau perilaku sistem secara keseluruhan untuk mengidentifikasi hambatan sumber daya dan masalah skalabilitas:
- Pola penggunaan CPU: Penggunaan CPU yang tinggi mungkin menunjukkan inefisiensi algoritma atau hambatan komputasi.
- Konsumsi memori: Penggunaan memori yang berkembang dapat menyarankan kebocoran memori, struktur data yang tidak efisien, atau alokasi objek yang berlebihan.
- Metrik I/O: I/O disk atau jaringan yang tinggi dapat menunjukkan pola akses data yang tidak efisien atau caching yang buruk.
- Pengumpulan sampah: Pengumpulan sampah yang sering dapat memengaruhi performa, terutama dalam aplikasi throughput tinggi.
Alat seperti Task Manager, dotnet-counters, atau PerfMon di Windows dapat memberikan wawasan tingkat sistem. Memahami metrik ini membantu menghubungkan performa tingkat kode dengan pemanfaatan sumber daya sistem.
Memantau peningkatan dan menghindari regresi
Setelah membuat garis besar, Anda dapat mulai menerapkan peningkatan kode. Peningkatan kode harus dilakukan dalam siklus pembaruan kode diikuti dengan pengukuran untuk melihat apakah perubahan memiliki efek yang diinginkan.
Saat Anda membuat perubahan, selalu lakukan rekalibrasi menggunakan metode yang sama dengan tolok ukur Anda. Bandingkan pengukuran baru dengan garis besar.
Berikut adalah beberapa panduan yang direkomendasikan untuk proses ini:
Ubah satu per satu (jika memungkinkan): Jadi Anda tahu apa yang menyebabkan peningkatan atau regresi.
Jalankan pengujian/pengukuran yang sama dengan garis besar: Gunakan prosedur yang sama dan bandingkan metrik secara langsung.
Jika peningkatan tercapai: Bagus, pertimbangkan apakah itu memenuhi tujuan. Jika Anda membutuhkan peningkatan kecepatan 2x dan hanya mendapat 1,5x, Anda mungkin perlu melakukan iterasi lebih lanjut.
Jika tidak ada peningkatan atau performa yang lebih buruk: Selidiki alasannya. Bisa jadi perubahan Anda tidak mengatasi hambatan nyata atau mengganti satu biaya dengan yang lain.
Periksa efek samping: Efek samping dapat mencakup masalah ketepatan atau kinerja yang tidak terduga. Misalnya, Anda mungkin mengoptimalkan performa CPU tetapi melihat peningkatan besar penggunaan memori. Apakah efek samping itu dapat diterima?
Mengotomatiskan deteksi regresi: Menulis pengujian unit performa atau tolok ukur yang berjalan sebelum dan sesudah perubahan adalah teknik umum untuk deteksi regresi. Meskipun tidak setiap tim menulis pengujian performa, bukan ide yang buruk untuk memiliki serangkaian pengujian performa kecil (terutama untuk jalur kritis) yang memastikan perubahan baru tidak memperlambat semuanya secara drastis.
Menggunakan GitHub Copilot untuk membantu pengukuran
GitHub Copilot dapat membantu Anda menyiapkan teknik pengukuran performa:
- Minta contoh waktu: "Bagaimana cara mengukur waktu eksekusi metode C#?" GitHub Copilot dapat menyarankan
Stopwatchdengan kode sampel. - Dapatkan panduan pembuatan profil: "Alat apa yang dapat saya gunakan untuk membuat profil aplikasi .NET?" GitHub Copilot dapat mencantumkan alat pembuatan profil dan kasus penggunaannya.
- Menghasilkan kode tolok ukur: GitHub Copilot dapat membantu membuat kelas BenchmarkDotNet atau alat pengukuran lainnya.
GitHub Copilot berfungsi sebagai referensi cepat untuk menerapkan teknik pengukuran dengan benar.
Ringkasan
Mengukur performa kode dan menetapkan garis besar adalah langkah penting dalam proses pengoptimalan. Dengan menangkap karakteristik waktu eksekusi, penggunaan sumber daya, dan skalabilitas secara sistematis, Anda membuat titik referensi untuk mengevaluasi dampak perubahan Anda. Memanfaatkan berbagai teknik pengukuran—mulai dari pengaturan waktu sederhana hingga Stopwatch alat pembuatan profil tingkat lanjut—memungkinkan Anda mengidentifikasi hambatan secara akurat. Selalu pastikan bahwa peningkatan performa tidak membahayakan kebenaran atau memperkenalkan masalah baru. Dengan pendekatan pengukuran dan iterasi yang disiplin, Anda dapat secara efektif meningkatkan performa kode sambil mempertahankan integritasnya.