Yang terbaik adalah musuh dari yang baik

Gambar 6

Artikel ini adalah kisah bagaimana kami pernah memutuskan untuk meningkatkan alat SelfTester internal kami yang kami terapkan untuk menguji kualitas penganalisis PVS-Studio. Peningkatannya sederhana dan tampaknya bermanfaat, tetapi membuat kami mengalami beberapa masalah. Kemudian ternyata lebih baik kita menyerah saja.

Selftester


Kami mengembangkan dan mempromosikan penganalisis kode statis PVS-Studio untuk C, C ++, C # dan Java. Untuk menguji kualitas alat analisis kami, kami menggunakan alat internal, umumnya disebut SelfTester. Kami membuat versi SelfTester terpisah untuk setiap bahasa yang didukung. Itu karena spesifik pengujian, dan itu hanya lebih nyaman. Dengan demikian, saat ini kami memiliki tiga alat SelfTester internal di perusahaan kami masing-masing untuk C \ C ++, C # dan Java. Selanjutnya, saya akan memberi tahu Anda tentang versi Windows dari SelfTester untuk proyek-proyek Visual Studio C \ C ++, dengan menyebutnya SelfTester. Penguji ini adalah yang pertama dalam garis alat internal serupa, ini yang paling canggih dan kompleks dari semua.

Bagaimana cara kerja SelfTester? Idenya sederhana: ambil kumpulan proyek uji (kami menggunakan proyek open source nyata) dan menganalisisnya menggunakan PVS-Studio. Sebagai hasilnya, log penganalisis dihasilkan untuk setiap proyek. Log ini dibandingkan dengan log referensi dari proyek yang sama. Saat membandingkan log, SelfTester membuat ringkasan log yang membandingkan dengan cara yang ramah-pengembang.

Setelah mempelajari ringkasan, pengembang menyimpulkan tentang perubahan perilaku penganalisa sesuai dengan jumlah dan jenis peringatan, kecepatan kerja, kesalahan penganalisa internal, dll. Semua informasi ini sangat penting: ini memungkinkan Anda untuk mengetahui bagaimana penganalisa mengatasi pekerjaannya.

Berdasarkan ringkasan perbandingan log, pengembang memperkenalkan perubahan pada inti penganalisa (misalnya, saat membuat aturan diagnostik baru) dan segera mengontrol hasil editnya. Jika pengembang tidak lagi memiliki masalah untuk membandingkan log biasa, ia membuat referensi log peringatan saat ini untuk proyek. Kalau tidak, pekerjaan terus berlanjut.

Jadi, tugas SelfTester adalah bekerja dengan kumpulan proyek uji (omong-omong, ada lebih dari 120 di antaranya untuk C / C ++). Proyek-proyek untuk kumpulan dipilih dalam bentuk solusi Visual Studio. Hal ini dilakukan untuk memeriksa pekerjaan penganalisis pada berbagai versi Visual Studio, yang mendukung penganalisa (pada titik ini dari Visual Studio 2010 ke Visual Studio 2019).

Catatan: selanjutnya saya akan memisahkan konsep solusi dan proyek , dengan mempertimbangkan proyek sebagai bagian dari solusi.

Antarmuka SelfTester terlihat sebagai berikut:

Gambar 3

Di sebelah kiri ada daftar solusi, di sebelah kanan - hasil cek untuk setiap versi Visual Studio.

Label abu-abu "Tidak didukung" menunjukkan bahwa solusi tidak mendukung versi Visual Studio yang dipilih atau tidak dikonversi untuk versi ini. Beberapa solusi memiliki konfigurasi dalam kumpulan, yang menunjukkan versi Visual Studio tertentu untuk pemeriksaan. Jika versi tidak ditentukan, solusi akan diperbarui untuk semua versi Visual Studio berikutnya. Contoh dari solusi semacam itu ada di tangkapan layar - "smart_ptr_check.sln" (sebuah cek dibuat untuk semua versi Visual Studio).

Label hijau "OK" menunjukkan bahwa pemeriksaan reguler belum mendeteksi perbedaan dengan log referensi. Label merah "Diff" menunjukkan tentang perbedaan. Label-label ini harus mendapat perhatian khusus. Setelah mengklik dua kali pada label yang diperlukan, solusi yang dipilih akan dibuka dalam versi Visual Studio terkait. Jendela dengan log peringatan juga akan terbuka di sana. Tombol kontrol di bagian bawah memungkinkan Anda untuk menjalankan kembali analisis dari solusi yang dipilih atau semua, membuat referensi log yang dipilih (atau sekaligus), dll.

Hasil SelfTester selalu diduplikasi dalam laporan html (laporan berbeda)

Selain GUI, SelfTester juga memiliki mode otomatis untuk lari malam. Namun, pola penggunaan yang biasa diulang pengembang dijalankan oleh pengembang selama hari kerja. Oleh karena itu, salah satu karakteristik SelfTester yang paling penting adalah kecepatan bekerja.

Mengapa kecepatan penting:

  1. Kinerja setiap langkah sangat penting dalam hal tes malam berjalan. Jelas, semakin cepat tes lulus, semakin baik. Saat ini, waktu kinerja rata-rata SelfTester melebihi 2 jam;
  2. Ketika menjalankan SelfTester di siang hari, pengembang harus menunggu lebih sedikit untuk hasilnya, yang meningkatkan produktivitas tenaga kerjanya.

Performa yang mempercepat itulah yang menjadi alasan penyempurnaan kali ini.

Multi-threading di SelfTester


SelfTester pada awalnya dibuat sebagai aplikasi multithreaded dengan kemampuan untuk secara bersamaan menguji beberapa solusi. 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 pengujian. Selama itu, perubahan diperkenalkan langsung dalam file proyek .vcxproj , yang mengarah ke kesalahan selama menjalankan paralel.

Untuk membuat pekerjaan lebih efisien, SelfTester menggunakan penjadwal tugas cerdas untuk menetapkan nilai thread paralel yang sangat terbatas dan memeliharanya.

Perencana digunakan pada dua level. Yang pertama adalah tingkat solusi , digunakan untuk mulai menguji solusi .sln menggunakan utilitas PVS-Studio_Cmd.exe . Penjadwal yang sama, tetapi dengan pengaturan lain tingkat paralelisme , digunakan di dalam PVS-Studio_Cmd.exe (pada tingkat pengujian file sumber).

Tingkat paralelisme adalah parameter yang menunjukkan berapa banyak thread paralel yang harus dijalankan secara bersamaan. Empat dan delapan nilai default dipilih untuk tingkat paralelisme solusi dan tingkat file, masing-masing. Dengan demikian, jumlah utas paralel dalam implementasi ini harus 32 (4 solusi dan 8 file yang diuji secara bersamaan). Pengaturan ini tampak optimal bagi kami untuk pekerjaan analisis pada prosesor delapan inti.

Seorang pengembang dapat mengatur sendiri nilai-nilai lain dari tingkat paralelisme sesuai dengan kinerja komputernya atau tugas saat ini. Jika pengembang tidak menentukan parameter ini, jumlah prosesor sistem logis akan dipilih secara default.

Catatan: mari kita asumsikan lebih lanjut bahwa kita berurusan dengan tingkat paralelisme default.

Penjadwal LimitedConcurrencyLevelTaskScheduler diwarisi dari System.Threading.Tasks.TaskScheduler dan disempurnakan untuk memberikan tingkat paralelisme maksimum saat bekerja di ThreadPool . Hirarki warisan:

LimitedConcurrencyLevelTaskScheduler : PausableTaskScheduler { .... } PausableTaskScheduler: TaskScheduler { .... } 

PausableTaskScheduler memungkinkan Anda untuk menjeda kinerja tugas, dan selain itu, LimitedConcurrencyLevelTaskScheduler memberikan kontrol intelektual terhadap antrian tugas dan menjadwalkan kinerja mereka, dengan mempertimbangkan tingkat paralelisme, ruang lingkup tugas yang dijadwalkan, dan faktor lainnya. Penjadwal digunakan ketika menjalankan tugas LimitedConcurrencyLevelTaskScheduler .

Alasan perbaikan


Proses yang dijelaskan di atas memiliki kelemahan: tidak optimal ketika berhadapan dengan solusi dari berbagai ukuran. Dan ukuran solusi dalam kumpulan uji sangat beragam: dari 8KB hingga 4GB - ukuran folder dengan solusi dan dari 1 hingga beberapa ribu file kode sumber di masing-masingnya.

Penjadwal membuat solusi pada antrian hanya satu demi satu, tanpa komponen yang cerdas. Biarkan saya mengingatkan Anda bahwa secara default tidak lebih dari empat solusi dapat diuji secara bersamaan. Jika empat solusi besar saat ini diuji (jumlah file di masing-masing lebih dari delapan), diasumsikan bahwa kami bekerja secara efektif karena kami menggunakan sebanyak mungkin utas (32).

Tetapi mari kita bayangkan situasi yang agak sering, ketika beberapa solusi kecil diuji. Misalnya, satu solusi berukuran besar dan berisi 50 file (jumlah utas maksimum akan digunakan), sementara tiga solusi lainnya masing-masing berisi tiga, empat, lima file. Dalam hal ini, kami hanya akan menggunakan 20 utas (8 + 3 + 4 + 5). Kami kurang memanfaatkan waktu prosesor dan mengurangi kinerja secara keseluruhan.

Catatan : sebenarnya, bottleneck biasanya adalah subsistem disk, bukan prosesor.

Perbaikan


Peningkatan yang jelas dalam hal ini adalah peringkat daftar solusi yang diuji. Kita perlu mendapatkan penggunaan yang optimal dari jumlah set thread yang dilakukan secara simultan (32), dengan melewati untuk menguji proyek dengan jumlah file yang benar.

Mari kita perhatikan lagi contoh pengujian empat solusi dengan jumlah file berikut di masing-masing: 50, 3, 4 dan 5. Tugas yang memeriksa solusi dari tiga file cenderung bekerja paling cepat. Akan lebih baik untuk menambahkan solusi dengan delapan file atau lebih daripada itu (untuk menggunakan maksimum dari utas yang tersedia untuk solusi ini). Dengan cara ini, kami akan menggunakan 25 utas sekaligus (8 + 8 + 4 + 5). Tidak buruk. Namun, tujuh utas masih belum terlibat. Dan inilah ide perbaikan lain, yaitu untuk menghapus batas empat utas pada solusi pengujian. Karena kita sekarang dapat menambahkan bukan hanya satu, tetapi beberapa solusi, menggunakan 32 utas. Mari kita bayangkan bahwa kita memiliki dua solusi masing-masing dari tiga dan empat file. Menambahkan tugas-tugas ini akan sepenuhnya menutup "celah" dari utas yang tidak digunakan, dan akan ada 32 (8 + 8 + 4 + 5 + 3 + 4 ) dari mereka.

Semoga idenya jelas. Padahal, implementasi perbaikan ini juga tidak membutuhkan banyak usaha. Semuanya dilakukan dalam satu hari.

Kami perlu mengerjakan ulang kelas tugas: mewarisi dari System.Threading.Tasks.Task dan penugasan bidang "berat". Kami menggunakan algoritma sederhana untuk menetapkan bobot ke solusi: jika jumlah file kurang dari delapan, beratnya sama dengan nomor ini (misalnya, 5). Jika angkanya lebih besar atau sama dengan delapan, beratnya akan sama dengan delapan.

Kami juga harus menjabarkan penjadwal: mengajarkannya untuk memilih solusi dengan bobot yang dibutuhkan untuk mencapai nilai maksimum 32 utas. Kami juga harus mengizinkan lebih dari empat utas untuk pengujian solusi simultan.

Akhirnya, kami membutuhkan langkah awal untuk menganalisis semua solusi di kumpulan (evaluasi menggunakan MSBuild API) untuk mengevaluasi dan menetapkan bobot solusi (mendapatkan jumlah file dengan kode sumber).

Hasil


Saya pikir setelah perkenalan yang begitu lama Anda sudah menebak bahwa tidak ada yang datang dari itu.

Gambar 12

Meskipun demikian, perbaikannya sederhana dan cepat.

Inilah bagian dari artikel itu, di mana saya akan memberi tahu Anda tentang apa yang "membawa kita ke banyak masalah" dan semua hal yang berkaitan dengannya.

Efek samping


Jadi, hasil negatif juga merupakan hasil. Ternyata jumlah solusi besar di kolam jauh melebihi jumlah yang kecil (kurang dari delapan file). Dalam hal ini, peningkatan ini tidak memiliki efek yang sangat nyata, karena hampir tidak terlihat: pengujian proyek kecil membutuhkan waktu yang sangat kecil dibandingkan dengan waktu, yang dibutuhkan untuk proyek besar.

Namun, kami memutuskan untuk meninggalkan penyempurnaan baru sebagai "tidak mengganggu" dan berpotensi bermanfaat. Selain itu, kumpulan solusi pengujian terus diisi ulang, sehingga di masa depan, mungkin, situasinya akan berubah.

Dan kemudian ...

Gambar 5

Salah satu pengembang mengeluh tentang kehancuran SelfTester. Ya, hidup terjadi. Untuk mencegah kesalahan ini agar tidak hilang, kami membuat insiden internal (tiket) dengan nama "Pengecualian ketika bekerja dengan SelfTester". Kesalahan terjadi saat mengevaluasi proyek. Meskipun sejumlah besar jendela dengan kesalahan menunjukkan masalah kembali pada penangan kesalahan. Tapi ini dengan cepat dihilangkan, dan selama minggu berikutnya tidak ada yang jatuh. Tiba-tiba, pengguna lain mengeluh tentang SelfTester. Sekali lagi, kesalahan evaluasi proyek:

Gambar 8

Kali ini tumpukan berisi banyak informasi berguna - kesalahannya adalah dalam format xml. Kemungkinan, ketika menangani file proyek Proto_IRC.vcxproj (representasi xml-nya) sesuatu terjadi pada file itu sendiri, itu sebabnya XmlTextReader tidak bisa menanganinya.

Memiliki 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 memulainya, kami menganalisis crash terakhir. Sedih untuk mengatakan, kami menemukan tidak ada yang mencurigakan. Untuk berjaga-jaga kami meminta pengembang (pengguna SelfTester) untuk mengawasi dan melaporkan tentang kemungkinan kesalahan.

Poin penting: kode yang salah digunakan kembali di SelfTester. Ini awalnya digunakan untuk mengevaluasi proyek-proyek dalam penganalisa itu sendiri ( PVS-Studio_Cmd.exe ). Itu sebabnya perhatian terhadap masalah telah tumbuh. Namun, tidak ada crash seperti itu di penganalisa.

Sementara itu, tiket tentang masalah dengan SelfTester dilengkapi dengan kesalahan baru:

Gambar 9

XmlException lagi. Jelas, ada utas bersaing di suatu tempat yang bekerja dengan membaca dan menulis file proyek. SelfTester bekerja dengan proyek dalam kasus-kasus berikut:

  1. Evaluasi proyek dalam proses perhitungan awal bobot solusi: langkah baru yang awalnya menimbulkan kecurigaan;
  2. Memperbarui proyek ke versi Visual Studio yang diperlukan: dilakukan tepat sebelum pengujian (proyek tidak mengganggu) dan tidak boleh memengaruhi proses kerja.
  3. Evaluasi proyek selama pengujian: mekanisme thread-safe mapan, digunakan kembali dari PVS-Studio_Cmd.exe ;
  4. Mengembalikan file proyek (mengganti file .vcxproj yang dimodifikasi dengan file referensi awal) ketika keluar dari SelfTester, karena file proyek dapat memperbarui ke versi Visual Studio yang diperlukan selama pekerjaan. Ini adalah langkah terakhir, yang tidak berdampak pada mekanisme lain.

Kecurigaan jatuh pada kode baru yang ditambahkan untuk optimasi (perhitungan berat). Tetapi penyelidikan kode menunjukkan bahwa jika pengguna menjalankan analisis tepat setelah dimulainya SelfTester, tester selalu benar menunggu sampai akhir pra-evaluasi. Tempat ini tampak aman.

Sekali lagi, kami tidak dapat mengidentifikasi sumber masalah.

Nyeri


Semua bulan berikutnya SelfTester terus crash terus menerus. Tiket terus terisi dengan data, tetapi tidak jelas apa yang harus dilakukan dengan data ini. Sebagian besar crash dengan XmlException yang sama . Kadang-kadang ada sesuatu yang lain, tetapi pada kode yang digunakan kembali yang sama dari PVS-Studio_Cmd.exe .

Gambar 1

Secara tradisional, alat internal tidak memberlakukan persyaratan yang sangat tinggi, jadi kami terus mencari kesalahan SelfTester tentang prinsip residual. Dari waktu ke waktu, orang yang berbeda terlibat (selama seluruh insiden enam orang mengerjakan masalah, termasuk dua pekerja magang). Namun, kami harus terganggu oleh tugas ini.

Kesalahan pertama kami. Bahkan, pada titik ini kita bisa menyelesaikan masalah ini untuk selamanya. Bagaimana? Jelas bahwa kesalahan itu disebabkan oleh optimasi baru. Bagaimanapun, sebelum semuanya bekerja dengan baik, dan kode yang digunakan kembali jelas tidak bisa seburuk itu. Selain itu, pengoptimalan ini tidak membawa manfaat apa pun. Jadi apa yang harus dilakukan? Hapus pengoptimalan ini. Seperti yang mungkin Anda pahami, itu tidak dilakukan. Kami terus mengerjakan masalah, yang kami ciptakan sendiri. Kami terus mencari jawabannya: "BAGAIMANA?" Bagaimana itu crash? Tampaknya ditulis dengan benar.

Kesalahan kedua kami. Orang lain terlibat dalam memecahkan masalah . Ini kesalahan yang sangat, sangat besar. Tidak hanya tidak menyelesaikan masalah tetapi juga membutuhkan sumber daya terbuang tambahan. Ya, orang-orang baru membawa ide-ide baru, tetapi butuh banyak waktu kerja untuk mengimplementasikan (tanpa alasan) ide-ide ini. Pada titik tertentu, kami meminta peserta magang menulis program pengujian yang meniru evaluasi satu dan proyek yang sama di utas yang berbeda dengan modifikasi paralel dari suatu proyek di proyek lain. Itu tidak membantu. Kami hanya menemukan bahwa MSBuild API aman di dalam, yang sudah kami ketahui. Kami juga menambahkan penghematan mini dump otomatis saat pengecualian XmlException terjadi. Kami memiliki seseorang yang men-debug semua ini. Orang miskin! Ada diskusi, kami melakukan hal-hal lain yang tidak perlu.

Akhirnya, keluar kesalahan ketiga. Tahukah Anda berapa banyak waktu yang telah berlalu sejak masalah SelfTester terjadi sampai ke titik ketika masalah itu diselesaikan? Nah, Anda bisa menghitung sendiri. Tiket dibuat pada 09/17/2018 dan ditutup pada 02/20/2019. Ada lebih dari 40 komentar! Guys, itu banyak waktu! Kami membiarkan diri kami sibuk selama lima bulan dengan INI. Pada saat yang sama kami sibuk mendukung Visual Studio 2019, menambahkan dukungan bahasa Jawa, memperkenalkan standar MISRA C / C ++, meningkatkan penganalisa C #, berpartisipasi aktif dalam konferensi, menulis banyak artikel, dll. Semua aktivitas ini menerima lebih sedikit waktu pengembang karena kesalahan bodoh di SelfTester.

Teman-teman, belajarlah dari kesalahan kita dan jangan pernah melakukan ini. Kami juga tidak akan melakukannya.

Itu saja, saya sudah selesai.

Gambar 15

Oke, itu hanya lelucon, saya akan memberi tahu Anda apa masalahnya dengan SelfTester :)

Bingo!


Untungnya, ada seseorang di antara kami dengan pandangan mata jernih (kolega saya Sergey Vasiliev), yang hanya melihat masalah dari sudut pandang yang sangat berbeda (dan juga - ia mendapat sedikit keberuntungan). Bagaimana jika tidak apa-apa di dalam SelfTester, tetapi sesuatu dari luar merusak proyek? Biasanya kami tidak meluncurkan apa pun dengan SelfTester, dalam beberapa kasus kami benar-benar mengendalikan lingkungan eksekusi. Dalam hal ini, "sesuatu" yang sangat ini bisa jadi SelfTester itu sendiri, tetapi contoh yang berbeda.

Saat keluar dari SelfTester, utas yang mengembalikan file proyek dari referensi, terus bekerja untuk sementara waktu. Pada titik ini, penguji mungkin diluncurkan lagi. Perlindungan terhadap proses simultan dari beberapa instance SelfTester telah ditambahkan kemudian dan sekarang terlihat sebagai berikut:

Gambar 16

Tetapi pada saat itu kami tidak memilikinya.

Kacang-kacangan, tetapi benar - selama hampir enam bulan siksaan tidak ada yang memperhatikannya. Mengembalikan proyek dari referensi adalah prosedur latar belakang yang cukup cepat, tetapi sayangnya tidak cukup cepat untuk tidak mengganggu peluncuran ulang SelfTester. Dan apa yang terjadi ketika kita meluncurkannya? Itu benar, menghitung bobot solusi. Satu proses menulis ulang file .vcxproj sementara yang lain mencoba membacanya. Ucapkan salam untuk XmlException .

Sergey mengetahui semua ini ketika dia menambahkan kemampuan untuk beralih ke set log referensi yang berbeda ke tester. Itu menjadi perlu setelah menambahkan satu set aturan MISRA di penganalisa. Anda dapat beralih langsung di antarmuka, sementara pengguna melihat jendela ini:

Gambar 14

Setelah itu, SelfTester memulai kembali. Dan sebelumnya, tampaknya, pengguna entah bagaimana meniru masalah itu sendiri, menjalankan tester lagi.

Blamestorming dan kesimpulan


Tentu saja, kami menghapus (yaitu, dinonaktifkan) optimasi yang dibuat sebelumnya. Selain itu, itu jauh lebih mudah daripada melakukan semacam sinkronisasi antara memulai kembali tester dengan sendirinya. Dan semuanya mulai bekerja dengan sempurna, seperti sebelumnya. Dan sebagai langkah tambahan, kami menambahkan perlindungan di atas terhadap peluncuran tester secara bersamaan.

Saya sudah menulis di atas tentang kesalahan utama kami ketika mencari masalah, jadi cukup dengan self flagellation. Kita adalah manusia, jadi kita mungkin salah. Penting untuk belajar dari kesalahan Anda sendiri dan menarik kesimpulan. Kesimpulan dari kasus ini cukup sederhana:

  • Kita harus memantau dan memperkirakan kompleksitas tugas;
  • Terkadang kita perlu berhenti di beberapa titik;
  • Cobalah untuk melihat masalahnya secara lebih luas. Seiring waktu seseorang bisa mendapatkan visi terowongan dari kasus ini sementara itu membutuhkan perspektif yang segar.
  • Jangan takut untuk menghapus kode lama atau tidak perlu.

Itu saja, kali ini saya pasti selesai. Terima kasih sudah membaca sampai akhir. Semoga kode bugless Anda!

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


All Articles