
Artikel ini adalah tentang bagaimana sekali kami memutuskan untuk sedikit meningkatkan alat internal SelfTester, yang digunakan untuk memeriksa kualitas penganalisa PVS-Studio. Peningkatannya sederhana dan terlihat bermanfaat, tetapi menciptakan banyak masalah bagi kami, dan kemudian ternyata akan lebih baik jika kami tidak melakukannya.
Selftester
Kami mengembangkan dan mempromosikan penganalisis kode statis PVS-Studio untuk C, C ++, C # dan Java. Untuk memeriksa kualitas penganalisa, kami menggunakan alat internal yang secara kolektif disebut SelfTester. Setiap bahasa yang didukung memiliki versi SelfTester sendiri. Ini karena fitur pengujian, dan itu hanya lebih nyaman. Dengan demikian, saat ini, perusahaan kami menggunakan tiga alat SelfTester internal untuk C \ C ++, C # dan Java, masing-masing. Selanjutnya, saya akan berbicara tentang versi Windows dari SelfTester untuk proyek-proyek Visual Studio C \ C ++, menyebutnya hanya SelfTester. Penguji ini adalah yang pertama dalam garis alat internal tersebut, ini adalah yang paling canggih dan paling kompleks dari semuanya.
Bagaimana cara kerja SelfTester? Idenya sederhana: ambil kumpulan proyek uji (kami menggunakan proyek open source nyata) dan menganalisanya menggunakan PVS-Studio. Akibatnya, log peringatan penganalisis dihasilkan untuk setiap proyek. Log ini dibandingkan dengan log
referensi untuk proyek yang sama. Saat membandingkan log, SelfTester membuat
log perbandingan log dalam bentuk yang nyaman untuk dilihat oleh pengembang.
Setelah mempelajari buku catatan, pengembang membuat kesimpulan tentang perubahan perilaku penganalisa: jumlah dan sifat peringatan, kecepatan operasi, ada kesalahan penganalisa internal, dll. Semua informasi ini sangat penting, memungkinkan Anda untuk memahami seberapa baik alat analisis itu melakukan tugasnya.
Berdasarkan log perbandingan log, pengembang membuat perubahan pada inti penganalisa (misalnya, ketika membuat aturan diagnostik baru), segera mengendalikan efek suntingannya. Jika pengembang tidak lagi memiliki pertanyaan tentang perbandingan log berikutnya, maka ia
membuat log peringatan
saat ini untuk proyek sebagai
referensi . Kalau tidak, pekerjaan terus berlanjut.
Jadi, tugas SelfTester adalah bekerja dengan kumpulan proyek uji (ngomong-ngomong, sudah ada lebih dari 120 di antaranya untuk C / C ++). Proyek-proyek untuk kumpulan dipilih sebagai solusi Visual Studio. Ini dilakukan untuk menguji penganalisis tambahan pada berbagai versi Visual Studio yang didukung penganalisa (dari Visual Studio 2010 ke Visual Studio 2019 saat ini).
Catatan : Saya selanjutnya akan memisahkan konsep
solusi dan
proyek , memahami proyek sebagai bagian dari solusi, seperti kebiasaan di Visual Studio.
Antarmuka SelfTester terlihat seperti:
Di sebelah kiri adalah daftar solusi, di sebelah kanan adalah hasil tes untuk setiap versi Visual Studio.
Tanda abu-abu "Tidak didukung" menunjukkan bahwa solusi tidak mendukung versi Visual Studio yang dipilih atau belum dikonversi untuk versi ini. Beberapa solusi di kumpulan memiliki pengaturan yang menunjukkan versi spesifik Visual Studio untuk diperiksa. Jika versi tidak ditentukan, solusi akan diperbarui ke semua versi Visual Studio berikutnya. Contoh dari solusi seperti itu dalam tangkapan layar adalah "smart_ptr_check.sln" (verifikasi dilakukan untuk semua versi Visual Studio).
Tanda "OK" berwarna hijau menunjukkan bahwa pemeriksaan berikutnya tidak mengungkapkan perbedaan dengan log referensi. Tanda “Diff” berwarna merah menunjukkan perbedaan. Label-label inilah yang harus diperhatikan pengembang. Untuk melakukan ini, ia perlu mengklik dua kali pada label yang diinginkan. Solusi yang dipilih akan dibuka dalam versi Visual Studio yang diinginkan, dan jendela dengan log peringatan juga akan dibuka di sana. Tombol kontrol di bawah ini memungkinkan Anda untuk memulai kembali analisis dari keputusan yang dipilih atau semua, menetapkan log yang dipilih (atau sekaligus) ke yang standar, dll.
Hasil karya SelfTester yang disajikan selalu diduplikasi dalam laporan html (log perbedaan).
Selain GUI, SelfTester juga memiliki mode otomatis untuk menjalankan selama pembangunan malam hari. Namun, pola penggunaan yang biasa adalah peluncuran berulang oleh pengembang selama hari kerja. Oleh karena itu, salah satu karakteristik penting untuk SelfTester adalah
kecepatannya .
Mengapa kecepatan itu penting:
- Untuk menjalankan selama tes malam, waktu yang dibutuhkan untuk menyelesaikan setiap langkah sangat penting. Jelas, semakin cepat tes lulus, semakin baik. Dan waktu operasi rata-rata SelfTester saat ini melebihi 2 jam;
- Ketika meluncurkan SelfTester di siang hari, pengembang harus menunggu lebih sedikit untuk hasilnya, yang meningkatkan produktivitas tenaga kerja.
Adalah keinginan untuk mempercepat pekerjaan SelfTester yang menyebabkan peningkatan kali ini.
Multithreading di SelfTester
SelfTester pada awalnya dibuat sebagai aplikasi multi-utas dengan kemampuan untuk memeriksa beberapa solusi secara paralel. Satu-satunya batasan adalah bahwa Anda tidak dapat secara bersamaan memeriksa solusi yang sama untuk versi Visual Studio yang berbeda, karena banyak solusi perlu diperbarui ke versi Visual Studio tertentu sebelum memeriksa. Selama ini, perubahan dilakukan langsung ke
file proyek
.vcxproj , yang mengarah ke kesalahan saat berjalan secara paralel.
Untuk membuat pekerjaan lebih efisien, SelfTester menggunakan penjadwal tugas cerdas yang memungkinkan Anda untuk menetapkan nilai yang sangat terbatas untuk utas paralel dan memeliharanya.
Penjadwal digunakan pada dua level. Yang pertama adalah level
solusi , yang digunakan untuk mulai memeriksa solusi
.sln menggunakan utilitas PVS-Studio_Cmd.exe . Di dalam
PVS-Studio_Cmd.exe (pada tingkat memeriksa
file kode sumber) penjadwal yang sama digunakan, tetapi dengan
tingkat pengaturan
paralelisme yang berbeda.
Tingkat paralelisme adalah parameter yang benar-benar menunjukkan berapa banyak thread paralel yang harus dijalankan secara bersamaan. Untuk nilai derajat paralelisme pada tingkat keputusan dan file, nilai default
empat dan
delapan dipilih, masing-masing. Dengan demikian, jumlah utas paralel untuk implementasi ini harus sama dengan 32 (empat solusi yang diuji secara bersamaan dan delapan file). Menurut kami, pengaturan ini optimal bagi penganalisis untuk bekerja pada prosesor delapan inti.
Pengembang dapat secara mandiri menetapkan nilai lain dari tingkat paralelisme, dengan fokus pada kinerja komputernya atau tugas saat ini. Jika dia tidak mengatur parameter ini, maka secara default jumlah prosesor logis dari sistem akan dipilih.
Catatan : selanjutnya kami akan mempertimbangkan bahwa pekerjaan dilakukan dengan tingkat standar nilai paralelisme.
Penjadwal LimitedConcurrencyLevelTaskScheduler diwarisi dari
System.Threading.Tasks.TaskScheduler dan disempurnakan untuk memberikan tingkat paralelisme maksimum saat bekerja di atas
ThreadPool . Hierarki Warisan:
LimitedConcurrencyLevelTaskScheduler : PausableTaskScheduler { .... } PausableTaskScheduler: TaskScheduler { .... }
PausableTaskScheduler memungkinkan
Anda untuk menghentikan sementara pelaksanaan tugas, dan
LimitedConcurrencyLevelTaskScheduler , di samping itu, memberikan kontrol cerdas terhadap antrian tugas dan penjadwalan pelaksanaannya, dengan mempertimbangkan tingkat paralelisme, jumlah tugas terjadwal, dan faktor lainnya. Penjadwal digunakan ketika memulai tugas
System.Threading.Tasks.Task .
Prasyarat untuk peningkatan
Implementasi pekerjaan yang dijelaskan di atas memiliki kelemahan: itu tidak optimal ketika bekerja dengan solusi dari berbagai ukuran. Dan ukuran solusi dalam kumpulan uji
sangat berbeda: dari 8 KB hingga 4 GB untuk ukuran folder dengan solusinya, dan dari satu hingga beberapa ribu file kode sumber di masing-masingnya.
Penjadwal membuat antrian keputusan secara berurutan, tanpa komponen intelektual. Biarkan saya mengingatkan Anda bahwa secara default, lebih dari empat solusi tidak dapat diperiksa pada saat yang sama. Jika saat ini empat solusi besar sedang diperiksa (jumlah file di masing-masing lebih dari delapan), diasumsikan bahwa kami bekerja secara efisien, karena kami menggunakan jumlah utas yang maksimum (32).
Tetapi bayangkan situasi yang cukup umum ketika beberapa solusi kecil diuji. Misalnya, satu solusi besar dan berisi 50 file (maksimum delapan utas akan terlibat), dan tiga lainnya berisi masing-masing tiga, empat dan lima file. Dalam hal ini, kami hanya menggunakan 20 utas (8 + 3 + 4 + 5). Kami kurang memanfaatkan waktu prosesor dan penurunan kinerja secara keseluruhan.
Catatan : pada kenyataannya, bottleneck, sebagai suatu peraturan, masih merupakan subsistem disk, bukan prosesor.
Perbaikan
Peningkatan yang dengan sendirinya menunjukkan dirinya dalam hal ini adalah peringkat daftar solusi yang diajukan untuk verifikasi. Penting untuk mencapai penggunaan optimal dari sejumlah utas yang dijalankan secara simultan (32) dengan mengirimkan proyek dengan jumlah file yang "benar" untuk verifikasi.
Mari kita lihat contoh kita lagi, ketika empat solusi diuji dengan jumlah file berikut di masing-masing: 50, 3, 4, dan 5. Tugas yang memeriksa solusi dari
tiga file mungkin akan segera bekerja. Dan alih-alih itu, akan lebih optimal untuk menambahkan solusi di mana ada delapan file atau lebih (untuk menggunakan maksimal delapan stream yang tersedia untuk solusi ini). Maka secara total kita akan menggunakan sudah 25 utas (8 +
8 + 4 + 5). Tidak buruk. Namun, tujuh utas masih belum digunakan. Dan di sini muncul gagasan perbaikan lain, terkait dengan penghapusan pembatasan pada empat utas untuk memeriksa solusi. Memang, dalam contoh di atas, Anda dapat menambahkan tidak hanya satu, tetapi beberapa solusi, menggunakan sebanyak mungkin semua 32 utas. Mari kita bayangkan bahwa kita memiliki dua solusi lagi, masing-masing tiga dan empat file. Menambahkan tugas-tugas ini akan sepenuhnya menutup "celah" di utas yang tidak digunakan, dan akan ada 32 (8 + 8 + 4 + 5 +
3 +
4 ).
Saya pikir idenya jelas. Padahal, implementasi perbaikan ini juga tidak membutuhkan banyak upaya. Semuanya dilakukan dalam satu hari.
Itu perlu untuk memperbaiki kelas tugas: warisan dari
System.Threading.Tasks.Task dan menambahkan bidang "berat". Untuk mengatur bobot solusi, digunakan algoritma sederhana: jika jumlah file dalam solusi kurang dari delapan, maka bobotnya disetel sama dengan nilai ini (misalnya, 5), jika jumlah file lebih dari atau sama dengan delapan, maka bobot yang dipilih sama dengan delapan.
Itu juga perlu untuk memperbaiki penjadwal: untuk mengajarnya memilih solusi dengan bobot yang tepat untuk mencapai nilai maksimum 32 utas. Itu juga perlu untuk memungkinkan alokasi lebih dari empat utas untuk verifikasi simultan solusi.
Akhirnya, dibutuhkan langkah awal untuk menganalisis semua solusi kumpulan (evaluasi menggunakan MSBuild API) untuk menghitung dan mengatur bobot solusi (dapatkan jumlah file dengan kode sumber).
Hasil
Saya pikir, setelah perkenalan yang begitu panjang, Anda sudah menebak bahwa hasilnya adalah nol.
Sangat baik bahwa peningkatannya sederhana dan cepat.
Nah, sekarang, pada kenyataannya, bagian dari artikel tentang "menciptakan banyak masalah bagi kita" dimulai, dan itu saja.
Efek samping
Jadi, hasil negatif juga merupakan hasil. Ternyata jumlah solusi besar di kolam
secara signifikan melebihi jumlah yang kecil (kurang dari delapan file). Dalam kondisi ini, perbaikan yang dilakukan tidak memiliki efek yang nyata, karena mereka praktis tidak terlihat: verifikasi mereka membutuhkan waktu yang mikroskopis dibandingkan dengan proyek besar.
Namun demikian, diputuskan untuk meninggalkan revisi sebagai "tidak mengganggu" dan berpotensi bermanfaat. Selain itu, kumpulan solusi pengujian terus diisi ulang, sehingga di masa depan, mungkin, situasinya akan berubah.
Dan kemudian ...
Salah satu pengembang mengeluh tentang "jatuhnya" SelfTester. Ya itu terjadi. Untuk mencegah kesalahan ini agar tidak hilang, insiden internal (tiket) diluncurkan dengan nama "Pengecualian ketika bekerja dengan SelfTester". Kesalahan terjadi selama evaluasi proyek. Benar, jendela yang begitu banyak memberi kesaksian tentang masalah ini juga pada penangan kesalahan. Tapi ini dengan cepat dihilangkan, dan selama minggu berikutnya tidak ada yang rusak. Tiba-tiba, pengguna lain mengeluh tentang SelfTester. Dan lagi ke kesalahan evaluasi proyek:
Tumpukan kali ini berisi informasi yang lebih berguna - kesalahan dalam format xml. Mungkin, saat memproses file proyek
Proto_IRC.vcxproj (perwakilan xml-nya), sesuatu terjadi pada file itu sendiri, sehingga
XmlTextReader tidak dapat memprosesnya.
Kehadiran dua kesalahan dalam waktu yang cukup singkat membuat kami melihat lebih dekat pada masalahnya. Selain itu, seperti yang saya katakan di atas, SelfTester sangat aktif digunakan oleh pengembang.
Untuk mulai dengan, analisis dibuat dari tempat terakhir musim gugur. Sayangnya, tidak ada yang mencurigakan yang dapat diidentifikasi. Untuk berjaga-jaga, mereka meminta pengembang (pengguna SelfTester) waspada dan melaporkan kemungkinan kesalahan.
Poin penting: kode tempat kesalahan terjadi digunakan kembali di SelfTester. Awalnya, ini digunakan untuk mengevaluasi proyek-proyek dalam penganalisa itu sendiri (
PVS-Studio_Cmd.exe ). Itu sebabnya perhatian terhadap masalah telah tumbuh. Namun, tidak ada tetes yang sama terjadi pada penganalisa.
Sementara itu, tiket tentang masalah dengan SelfTester diisi dengan kesalahan baru:
Dan lagi
XmlException . Jelas, di suatu tempat ada utas bersaing yang bekerja dengan membaca dan menulis file proyek. SelfTester bekerja dengan proyek dalam kasus-kasus berikut:
- Evaluasi proyek selama perhitungan awal bobot keputusan: langkah baru yang awalnya menimbulkan kecurigaan;
- Memutakhirkan proyek ke versi yang diperlukan dari Visual Studio: dilakukan segera sebelum verifikasi (proyek tidak berpotongan dengan cara apa pun) dan tidak boleh memengaruhi pekerjaan;
- Evaluasi proyek selama verifikasi: mekanisme aman thread debug, yang digunakan kembali dari PVS-Studio_Cmd.exe ;
- Memulihkan file proyek (mengganti file .vcxproj yang diubah dengan file referensi asli) saat keluar dari SelfTester, karena file proyek dapat diperbarui ke versi Visual Studio yang diperlukan dalam proses: langkah terakhir, yang juga tidak mempengaruhi mekanisme lain.
Kecurigaan jatuh pada kode baru yang ditambahkan untuk optimisasi (perhitungan bobot). Tetapi studi kode ini menunjukkan bahwa jika pengguna memulai analisis segera setelah dimulainya SelfTester, tester selalu benar menunggu akhir evaluasi pendahuluan. Tempat ini tampak aman.
Sekali lagi, kami tidak dapat mengidentifikasi sumber masalah.
Nyeri
Selama bulan berikutnya, SelfTester terus turun dari waktu ke waktu. Tiket diisi ulang dengan data, tetapi tidak jelas apa yang harus dilakukan dengan data ini. Sebagian besar crash semua dengan
XmlException yang sama. Kadang-kadang ada sesuatu yang lain, tetapi pada kode yang digunakan kembali yang sama dari
PVS-Studio_Cmd.exe .
Secara tradisional, persyaratan yang tidak terlalu tinggi dikenakan pada alat internal, jadi meskipun demikian, kesalahan SelfTester dilakukan berdasarkan residual. Dari waktu ke waktu, orang-orang yang berbeda saling terhubung (sepanjang waktu kejadian, enam orang menangani masalah ini, termasuk dua orang magang yang dilatih) Namun demikian, tugas itu harus dialihkan.
Kesalahan pertama kami. Bahkan, di sini sudah mungkin untuk menyelesaikan masalah sekali dan untuk semua. Bagaimana? Jelas bahwa kesalahan itu disebabkan oleh optimasi baru. Bagaimanapun, sebelum itu, semuanya bekerja dengan baik, dan kode yang digunakan kembali jelas tidak bisa seburuk itu. Apalagi optimasi ini tidak membawa manfaat apa pun. Jadi apa yang harus dilakukan?
Hapus pengoptimalan ini . Seperti yang Anda tahu, ini tidak dilakukan. Kami terus mengerjakan masalah yang telah kami ciptakan sendiri. Pencarian dilanjutkan untuk jawaban atas pertanyaan: "BAGAIMANA ???" Bagaimana itu jatuh? Namun sepertinya ditulis dengan benar.
Kesalahan kedua kami. Orang lain terhubung dengan solusi masalah. Kesalahan yang sangat, sangat besar. Sayangnya, ini tidak hanya tidak menyelesaikan masalah, tetapi sumber daya tambahan dikeluarkan. Ya, orang-orang baru membawa ide-ide baru, tetapi untuk implementasi mereka butuh (benar-benar terbuang) banyak waktu kerja. Pada tahap tertentu, program pengujian ditulis (oleh peserta yang sama), meniru evaluasi proyek yang sama di utas berbeda dengan modifikasi paralel proyek di utas lain. Itu tidak membantu. Selain apa yang sudah kami ketahui sebelumnya, API MSBuild aman di dalamnya, mereka belum menemukan sesuatu yang baru. Dan di SelfTester, dump mini ditambahkan ketika
XmlException dilempar. Lalu semua orang ini hancur lebur, ngeri. Diskusi diadakan, banyak hal lain yang tidak perlu dilakukan.
Akhirnya, kesalahan ketiga kami . Tahukah Anda berapa banyak waktu telah berlalu sejak masalah dengan SelfTester muncul dan sampai diselesaikan? Meski tidak, hitung sendiri. Insiden ini dibuat pada 09/17/2018 dan ditutup pada 02/20/2019, dan ada lebih dari 40 (empat puluh!) Pesan di sana. Guys, ini banyak waktu! Kami
mengizinkan diri untuk melakukan IT
selama lima bulan. Pada saat yang sama (secara paralel), kami terlibat dalam mendukung Visual Studio 2019, menambahkan bahasa Jawa, mulai menerapkan standar MISRA C / C ++, meningkatkan penganalisa C #, secara aktif berpartisipasi dalam konferensi, menulis banyak artikel, dll. Dan semua karya ini tidak menerima waktu pengembang karena kesalahan bodoh SelfTester.
Warga negara, belajarlah dari kesalahan kita dan jangan pernah melakukan itu. Dan kita tidak akan melakukannya.
Saya memiliki segalanya.
Tentu saja, ini adalah lelucon, dan saya akan memberi tahu Anda apa masalahnya dengan SelfTester :)
Bingo!
Untungnya, di antara kami adalah orang dengan kesadaran yang paling tidak suram (kolega saya Sergey Vasiliev), yang hanya melihat masalah dari sudut yang sama sekali berbeda (dan juga dia sedikit beruntung). Bagaimana jika di dalam SelfTester benar-benar baik-baik saja, dan proyek merusak sesuatu dari luar? Sejalan dengan SelfTester, biasanya tidak ada yang dimulai, dalam beberapa kasus kami mengontrol runtime secara ketat. Dalam hal ini, "sesuatu" ini hanya bisa merupakan SelfTester itu sendiri, tetapi contoh lain dari itu.
Setelah keluar dari SelfTester, aliran memulihkan file proyek dari standar terus bekerja selama beberapa waktu. Pada titik ini, Anda dapat memulai kembali tester. Perlindungan terhadap menjalankan beberapa instance SelfTester pada saat yang sama ditambahkan
kemudian dan sekarang terlihat seperti ini:
Tapi kemudian dia pergi.
Luar biasa, selama hampir setengah tahun siksaan tidak ada yang memperhatikannya. Memulihkan proyek dari standar adalah prosedur latar belakang yang cukup cepat, tetapi, sayangnya, tidak cukup cepat agar tidak mengganggu restart SelfTester. Dan apa yang terjadi saat startup? Itu benar, menghitung bobot keputusan. Satu proses menimpa file
.vcxproj , sementara yang lain mencoba membacanya. Hai,
XmlException .
Sergey mengetahui semua ini ketika dia menambahkan ke tester kemampuan untuk beralih ke mode bekerja dengan satu set log standar. Kebutuhan untuk ini muncul setelah menambahkan aturan MISRA yang ditetapkan ke penganalisa. Anda dapat beralih langsung di antarmuka, sementara pengguna melihat jendela:
Setelah itu SelfTester
memulai kembali . Nah, sebelumnya, tampaknya, pengguna entah bagaimana meniru masalah itu sendiri, memulai penguji lagi.
Tanya jawab dan kesimpulan
Tentu saja, kami menghapus, atau lebih tepatnya, menonaktifkan optimasi yang dibuat sebelumnya. Selain itu, itu jauh lebih mudah daripada melakukan semacam sinkronisasi antara sisa tester itu sendiri. Dan semuanya mulai bekerja dengan baik, seperti sebelumnya. Dan sebagai langkah tambahan, perlindungan yang dijelaskan di atas terhadap peluncuran tester secara bersamaan telah ditambahkan.
Saya sudah menulis di atas tentang kesalahan utama kami selama mencari masalah, jadi self-flagellation sudah cukup. Kita adalah manusia juga, dan karena itu kita salah. Penting untuk belajar dari kesalahan Anda dan menarik kesimpulan. Kesimpulan di sini cukup sederhana:
- Penting untuk melacak dan mengevaluasi pertumbuhan kompleksitas tugas;
- Berhenti tepat waktu;
- Cobalah untuk melihat masalahnya secara lebih luas, karena seiring berjalannya waktu pandangan "kabur", dan sudut pandangnya menyempit;
- Jangan takut untuk menghapus kode lama atau tidak perlu.
Sekarang pasti - itu saja. Terima kasih sudah membaca. Untuk semua kode yang tidak ada harapan!

Jika Anda ingin berbagi artikel ini dengan audiens yang berbahasa Inggris, silakan gunakan tautan ke terjemahan: Sergey Khrenov.
Yang terbaik adalah musuh dari yang baik .