Struct and readonly: bagaimana cara menghindari penurunan kinerja

Menggunakan tipe Struct dan pengubah hanya baca terkadang dapat menyebabkan penurunan kinerja. Hari ini kita akan berbicara tentang bagaimana menghindari ini menggunakan satu penganalisa kode Open Source - ErrorProne.NET.



Seperti yang mungkin Anda ketahui dari publikasi saya sebelumnya “ The 'in'-modifier dan struct readonly di C # " ("The modifier in and readonly structure in C #") dan " Jebakan kinerja penduduk lokal dan pengembalian ref di C # " (" Perangkap kinerja saat menggunakan variabel lokal dan mengembalikan nilai dengan pengubah ref)), bekerja dengan struktur lebih sulit daripada yang terlihat. Mengesampingkan masalah mutabilitas, saya perhatikan bahwa perilaku struktur dengan readonly modifier (read-only) dan tanpa itu dalam konteks readonly sangat bervariasi.

Diasumsikan bahwa struktur digunakan dalam skrip pemrograman yang membutuhkan kinerja tinggi, dan untuk bekerja secara efektif dengannya, Anda harus mengetahui sesuatu tentang berbagai operasi tersembunyi yang dihasilkan oleh kompiler untuk memastikan bahwa struktur tetap tidak berubah.

Berikut adalah daftar peringatan yang harus Anda ingat:

  • Menggunakan struktur besar yang diteruskan atau dikembalikan dengan nilai dapat menyebabkan masalah kinerja pada jalur eksekusi program yang kritis.
  • xY menyebabkan salinan pelindung x dibuat jika:
    • x adalah bidang yang hanya bisa dibaca;
    • tipe x adalah struktur tanpa pengubah baca saja;
    • Y bukan bidang.

Aturan yang sama berfungsi jika x adalah parameter dengan in modifier, variabel lokal dengan ref readonly modifier, atau hasil memanggil metode yang mengembalikan nilai melalui referensi readonly.

Berikut adalah beberapa aturan yang perlu diingat. Dan, yang paling penting, kode yang bergantung pada aturan ini sangat rapuh (mis., Perubahan yang dilakukan pada kode segera menyebabkan perubahan signifikan pada bagian lain dari kode atau dokumentasi - kira-kira. Terjemahan.). Berapa banyak orang akan melihat bahwa mengganti public readonly int X ; di public int X { get; } public int X { get; } dalam struktur yang sering digunakan tanpa pengubah baca hanya secara signifikan mempengaruhi kinerja? Atau seberapa mudahnya untuk melihat bahwa melewati parameter menggunakan in modifier alih-alih lewat nilai dapat menurunkan kinerja? Ini sangat mungkin ketika menggunakan properti in dari parameter dalam satu loop, ketika salinan pelindung dibuat di setiap iterasi.

Sifat-sifat struktur seperti itu benar-benar menarik bagi pengembangan analisis. Dan panggilan itu terdengar. Meet ErrorProne.NET - seperangkat alat analisis yang memberi tahu Anda tentang kemungkinan mengubah kode program untuk meningkatkan desain dan kinerjanya saat bekerja dengan struktur.

Analisis kode dengan output pesan “Jadikan struktur X hanya baca”


Cara terbaik untuk menghindari kesalahan halus dan dampak kinerja negatif saat menggunakan struktur adalah dengan membuatnya dibaca setiap kali memungkinkan. Pengubah baca hanya dalam deklarasi struktur jelas menyatakan maksud pengembang (menekankan bahwa struktur tidak dapat diubah) dan membantu kompiler menghindari menelurkan salinan keamanan dalam banyak konteks yang disebutkan di atas.



Mendeklarasikan struktur readonly tidak melanggar integritas kode. Anda dapat menjalankan fixer (proses memperbaiki kode) dengan aman dalam mode batch dan mendeklarasikan semua struktur dari seluruh solusi perangkat lunak hanya-baca.

Keramahan untuk pengubah ref readonly


Langkah selanjutnya adalah mengevaluasi keamanan menggunakan fitur baru (dalam pengubah, variabel baca lokal, variabel ref, dll.). Ini berarti bahwa kompiler tidak akan membuat salinan pelindung tersembunyi yang dapat mengurangi kinerja.

Tiga jenis tipe dapat dipertimbangkan:

  • ref struktur ramah hanya baca yang penggunaannya tidak pernah mengarah pada penciptaan salinan pelindung;
  • struktur-struktur yang tidak ramah untuk dibaca kembali, penggunaannya yang dalam konteks baca-baca selalu mengarah pada penciptaan salinan pelindung;
  • struktur netral - struktur yang penggunaannya dapat menimbulkan salinan pelindung tergantung pada anggota yang digunakan dalam konteks hanya baca.

Kategori pertama mencakup struktur hanya baca dan struktur POCO. Kompiler tidak akan pernah menghasilkan salinan pelindung jika strukturnya hanya dapat dibaca. Juga aman untuk menggunakan struktur POCO dalam konteks hanya baca: akses ke bidang dianggap aman, dan tidak ada salinan pelindung yang dibuat.

Kategori kedua adalah struktur tanpa pengubah baca saja yang tidak berisi bidang terbuka. Dalam hal ini, setiap akses ke anggota publik dalam konteks hanya baca akan menyebabkan pembuatan salinan pelindung.

Kategori terakhir adalah struktur dengan bidang publik atau internal dan properti atau metode publik atau internal. Dalam hal ini, kompiler membuat salinan pelindung tergantung pada anggota yang digunakan.

Pemisahan ini membantu untuk secara instan menghasilkan peringatan jika struktur "tidak ramah" dilewatkan dengan modifier in, disimpan dalam variabel lokal ref readonly, dll.



Penganalisis tidak menampilkan peringatan jika struktur "tidak ramah" digunakan sebagai bidang yang hanya dapat dibaca, karena tidak ada alternatif dalam kasus ini. Pengubah baca masuk dan referensi dirancang untuk dioptimalkan secara khusus untuk menghindari pembuatan salinan yang berlebihan. Jika strukturnya "tidak ramah" sehubungan dengan pengubah ini, Anda memiliki opsi lain: meneruskan argumen dengan nilai atau menyimpan salinan dalam variabel lokal. Dalam hal ini, bidang hanya baca berperilaku berbeda: jika Anda ingin membuat tipe tidak berubah, Anda harus menggunakan bidang ini. Ingat: kodenya harus jelas dan elegan, dan hanya cepat kedua.

Analisis Bcc


Kompiler melakukan banyak tindakan yang disembunyikan dari pengguna. Seperti yang ditunjukkan pada posting sebelumnya, cukup sulit untuk melihat kapan salinan pelindung dibuat.

Penganalisis mendeteksi salinan tersembunyi berikut:

  1. Bcc bidang hanya baca.
  2. Bcc dalam.
  3. Bcc variabel lokal hanya baca.
  4. Bcc mengembalikan ref dibaca hanya.
  5. Bcc saat memanggil metode ekstensi yang mengambil parameter dengan pengubah ini dengan nilai untuk instance dari struktur.

 public struct NonReadOnlyStruct { public readonly long PublicField; public long PublicProperty { get; } public void PublicMethod() { } private static readonly NonReadOnlyStruct _ros; public static void Samples(in NonReadOnlyStruct nrs) { // Ok. Public field access causes no hidden copies var x = nrs.PublicField; // Ok. No hidden copies. x = _ros.PublicField; // Hidden copy: Property access on 'in'-parameter x = nrs.PublicProperty; // Hidden copy: Method call on readonly field _ros.PublicMethod(); ref readonly var local = ref nrs; // Hidden copy: method call on ref readonly local local.PublicMethod(); // Hidden copy: method call on ref readonly return Local().PublicMethod(); ref readonly NonReadOnlyStruct Local() => ref _ros; } } 

Harap dicatat bahwa alat analisa hanya menampilkan pesan diagnostik jika ukuran struktur ≥16 byte.

Menggunakan analisa dalam proyek nyata


Transfer struktur besar berdasarkan nilai dan, sebagai hasilnya, pembuatan salinan pelindung oleh kompiler secara signifikan mempengaruhi kinerja. Setidaknya ini ditunjukkan oleh hasil tes kinerja. Tetapi bagaimana fenomena ini akan mempengaruhi aplikasi nyata dalam hal waktu end-to-end?

Untuk menguji analisis menggunakan kode nyata, saya menggunakannya untuk dua proyek: proyek Roslyn dan proyek internal yang saat ini saya kerjakan di Microsoft (proyek ini adalah aplikasi komputer independen dengan persyaratan kinerja yang ketat); sebut saja "Proyek D" untuk kejelasan.

Inilah hasilnya:

  1. Proyek dengan persyaratan kinerja tinggi biasanya mengandung banyak struktur, dan sebagian besar dapat dibaca saja. Misalnya, dalam proyek Roslyn, penganalisa menemukan sekitar 400 struktur yang dapat dibaca hanya, dan dalam proyek D, sekitar 300.
  2. Dalam proyek dengan persyaratan kinerja tinggi, salinan buta hanya boleh dibuat dalam situasi luar biasa. Saya hanya menemukan beberapa kasus seperti itu di proyek Roslyn, karena sebagian besar struktur memiliki bidang publik, bukan properti publik. Ini menghindari membuat salinan pelindung dalam situasi di mana struktur disimpan di bidang yang hanya bisa dibaca. Ada lebih banyak salinan buta di Proyek D, karena setidaknya setengah dari mereka memiliki sifat hanya-dapatkan (akses baca-saja).
  3. Pemindahan struktur yang bahkan cukup besar dengan menggunakan modifier in cenderung memiliki efek yang sangat kecil (hampir tidak terlihat) pada waktu program.

Saya mengubah semua 300 struktur dalam proyek D, membuatnya hanya bisa dibaca, dan kemudian memperbaiki ratusan kasus penggunaannya, menunjukkan bahwa mereka dilewatkan dengan modifier in. Kemudian saya mengukur waktu transit ujung ke ujung untuk berbagai skenario kinerja. Perbedaannya tidak signifikan secara statistik.

Apakah ini berarti bahwa fitur yang dijelaskan di atas tidak berguna? Tidak semuanya.

Bekerja pada proyek dengan persyaratan kinerja tinggi (misalnya, pada Roslyn atau "Proyek D") menyiratkan bahwa sejumlah besar orang menghabiskan banyak waktu pada berbagai jenis optimasi. Bahkan, dalam beberapa kasus, struktur dalam kode kami diteruskan dengan pengubah ref, dan beberapa bidang dinyatakan tanpa pengubah baca hanya untuk mengecualikan pembuatan salinan pelindung. Kurangnya pertumbuhan produktivitas selama transfer struktur dengan modifier in dapat berarti bahwa kode telah dioptimalkan dengan baik dan tidak ada penyalinan berlebihan struktur pada jalur kritis dari jalurnya.

Apa yang harus saya lakukan dengan fitur-fitur ini?


Saya percaya bahwa masalah menggunakan pengubah readonly untuk struktur tidak memerlukan banyak pemikiran. Jika struktur tidak dapat diubah, maka pengubah baca hanya secara eksplisit memaksa kompiler untuk keputusan desain seperti itu. Dan kurangnya salinan pelindung untuk struktur semacam itu hanyalah bonus.

Hari ini rekomendasi saya adalah sebagai berikut: jika struktur dapat dibaca hanya, maka dengan segala cara membuatnya menjadi seperti itu.

Menggunakan opsi lain dianggap bernuansa.

Pra-optimasi versus pra-pesimisasi?


Herb Sutter memperkenalkan konsep "pesimisasi awal" dalam bukunya yang menakjubkan, C ++ Standar Pengkodean: 101 Aturan, Rekomendasi, dan Praktik Terbaik .

“Ceteris paribus, kompleksitas kode, dan keterbacaan, beberapa pola desain yang efektif dan idiom pengkodean secara alami akan mengalir dari ujung jari Anda. Kode seperti itu tidak lebih sulit untuk ditulis daripada alternatifnya yang pesimis. Anda tidak melakukan optimasi awal, tetapi hindari pesimisasi sukarela. "

Dari sudut pandang saya, parameter dengan modifier in adalah masalahnya. Jika Anda tahu bahwa strukturnya relatif besar (40 byte atau lebih), maka Anda selalu dapat meneruskannya dengan modifier in. Biaya menggunakan pengubah dalam relatif rendah, karena Anda tidak perlu menyesuaikan panggilan, dan manfaatnya bisa nyata.

Sebaliknya, untuk variabel lokal dan mengembalikan nilai dengan pengubah ref readonly, ini tidak terjadi. Saya akan mengatakan bahwa fitur-fitur ini harus digunakan ketika coding perpustakaan, dan lebih baik menolaknya dalam kode aplikasi (hanya jika profiling kode tidak mengungkapkan bahwa operasi penyalinan benar-benar masalah). Menggunakan fitur-fitur ini membutuhkan upaya tambahan, dan pembaca kode menjadi lebih sulit untuk memahaminya.

Kesimpulan


  1. Gunakan pengubah baca hanya untuk struktur jika memungkinkan.
  2. Pertimbangkan untuk menggunakan modifier in untuk struktur besar.
  3. Pertimbangkan menggunakan variabel lokal dan kembalikan nilai dengan pengubah readonly ref untuk mengkodekan pustaka atau dalam kasus di mana hasil pemrofilan kode menunjukkan bahwa ini bisa bermanfaat.
  4. Gunakan ErrorProne.NET untuk mendeteksi masalah kode dan membagikan hasilnya.

Source: https://habr.com/ru/post/id423053/


All Articles