Salah satu fitur utama Linkurious Enterprise adalah antarmuka visualisasi grafik yang mudah dipelajari dan digunakan yang dirancang untuk orang awam. Pada 2015, frustrasi dengan kemungkinan perpustakaan JavaScript yang ada untuk visualisasi grafik, kami mulai mengembangkan perpustakaan kami sendiri - Ogma.
Ogma adalah pustaka JS rendering dan kinerja komputasi yang ditujukan untuk merender struktur jaringan. Anda mungkin pernah melihat bagaimana struktur jaringan dirender menggunakan alat JavaScript lain seperti D3.js atau Sigma.js. Kami kekurangan kemampuan alat ini. Penting bagi kami bahwa solusi yang kami gunakan memiliki beberapa kemampuan khusus sehingga dapat memenuhi persyaratan kinerja tertentu. Kami tidak menemukan salah satu atau lainnya di perpustakaan pihak ketiga. Oleh karena itu, kami memutuskan untuk mengembangkan perpustakaan kami sendiri dari awal.
Tugas
Visualisasi struktur jaringan
Pustaka Ogma telah dirancang untuk menggunakan algoritme yang paling canggih. Semua komponennya, dari mesin rendering berbasis WebGL yang canggih hingga pekerja web yang digunakan secara mendalam, telah ditujukan untuk memberikan kinerja rendering jaringan terbaik yang tersedia. Kami bertujuan untuk membuat perpustakaan sangat interaktif saat melakukan tugas jangka panjang, dan agar algoritme visualisasi grafik terkemuka yang diterapkan di dalamnya akan bekerja dengan cepat dan stabil.
Teknologi WebAssembly (Wasm), sejak pertama kali dilaporkan, telah menjanjikan para pengembang web tingkat kinerja yang sebanding dengan apa yang sebelumnya tersedia bagi mereka. Pada saat yang sama, para pengembang sendiri tidak perlu melakukan upaya berlebihan untuk menggunakan teknologi baru tersebut. Mereka hanya perlu menulis kode dalam beberapa bahasa berkinerja tinggi yang mereka ketahui, yang, setelah kompilasi, dapat dijalankan di browser.
Setelah teknologi Wasm berkembang sedikit, kami memutuskan untuk mencobanya dan melakukan beberapa uji kinerja sebelum menerapkannya. Secara umum, sebelum melompat ke kereta cepat Wasm tanpa menoleh ke belakang, kami memutuskan untuk bermain aman.
Yang menarik kami ke Wasm adalah bahwa teknologi ini dapat memecahkan masalah kami dengan baik, menggunakan sumber daya memori dan prosesor lebih efisien daripada JavaScript.
Penelitian kami
Penelitian kami dimulai dengan pencarian algoritme yang ditujukan untuk bekerja dengan grafik yang dapat dengan mudah dipindahkan ke berbagai bahasa menggunakan struktur data yang serupa.
Kami memilih algoritma n-body. Ini sering digunakan sebagai dasar untuk membandingkan algoritma visualisasi grafik gaya. Perhitungan yang dilakukan sesuai dengan algoritma ini adalah bagian yang paling intensif sumber daya dari sistem visualisasi. Jika bagian dari pekerjaan sistem tersebut dapat dilakukan lebih efisien dari sebelumnya, ini akan memiliki efek yang sangat positif pada semua algoritma visualisasi grafik gaya yang diimplementasikan di Ogma.
Tolok ukur
Ada kebohongan, kebohongan kotor, dan tolok ukur.
Max De Marzi
Mengembangkan tolok ukur "jujur" sering kali merupakan tugas yang mustahil. Faktanya adalah bahwa dalam lingkungan yang dibuat secara artifisial, sulit untuk mereproduksi skenario khas dunia nyata. Menciptakan lingkungan yang memadai untuk sistem yang kompleks selalu sangat sulit. Memang, dalam lingkungan laboratorium mudah untuk mengontrol faktor-faktor eksternal, tetapi pada kenyataannya, banyak pengaruh yang tidak terduga seperti apa "kinerja" solusi itu.
Dalam kasus kami, tolok ukur ditujukan untuk memecahkan satu masalah yang terdefinisi dengan baik, untuk mempelajari kinerja implementasi algoritma n-body.
Ini adalah algoritma yang jelas dan terdokumentasi dengan baik yang digunakan oleh organisasi terkemuka untuk mengukur kinerja.bahasa pemrograman.
Seperti halnya uji wajar apa pun, kami telah menetapkan beberapa aturan sebelumnya untuk berbagai bahasa yang kami pelajari:
- Implementasi yang berbeda dari algoritme harus menggunakan struktur kode yang serupa.
- Dilarang menggunakan banyak proses atau banyak utas.
- Penggunaan SIMD dilarang .
- Hanya versi kompiler stabil yang diuji. Dilarang menggunakan rilis seperti nightly, beta, alpha, pre-alpha.
- Hanya versi kompilator terbaru yang digunakan untuk setiap bahasa.
Setelah kami memutuskan aturannya, kami sudah dapat melanjutkan ke implementasi algoritme. Tapi, sebelum melakukan ini, kami masih harus memilih bahasa yang performanya akan kami pelajari.
Pesaing JS
Wasm adalah format biner untuk instruksi yang dijalankan di browser. Kode yang ditulis dalam berbagai bahasa pemrograman dikompilasi ke dalam format ini. Mereka mengatakan tentang kode Wasm sebagai kode yang dapat dibaca manusia, tetapi menulis program langsung di Wasm bukanlah keputusan yang dapat dibuat oleh orang yang berpikiran waras. Hasilnya, kami melakukan survei benchmark dan memilih kandidat berikut:
Algoritma n-body diimplementasikan dalam tiga bahasa ini. Performa berbagai implementasi dibandingkan dengan performa implementasi JavaScript dasar.
Dalam setiap implementasi algoritma, kami menggunakan 1000 poin dan menjalankan algoritma dengan jumlah iterasi yang berbeda. Kami mengukur waktu yang dibutuhkan untuk menyelesaikan setiap sesi program.
Pengujian dilakukan dengan menggunakan perangkat lunak dan perangkat keras berikut:
- NodeJS 12.9.1
- Chrome 79.0.3945.130 (Bentukan Resmi) (64-bit)
- clang 10.0.0 - untuk versi algoritme yang diterapkan dalam bahasa C.
- emcc 1.39.6 - frontend untuk memanggil compiler Emscripten dari baris perintah, alternatif untuk gcc / clang, serta linker
- kargo 1.40.0
- wasm-pack 0.8.1
- AssemblyScript 0.9.0
- MacOS 10.15.2
- Macbook Pro 2017 Retina
- Intel Dual Core i5 2,3 GHz, 8GB DDR3, 256GB SSD
Seperti yang Anda lihat, untuk pengujian kami memilih bukan komputer tercepat, tetapi kami menguji Wasm, yaitu kode yang akan dieksekusi dalam konteks browser. Dan browser, bagaimanapun, biasanya tidak memiliki akses ke semua inti prosesor yang tersedia di sistem dan ke semua RAM.
Agar lebih menarik, kami membuat beberapa versi dari setiap implementasi algoritme. Pada satu titik di sistem n-body, mereka memiliki representasi numerik 64-bit dari koordinat, di titik lain, 32-bit.
Perlu juga dicatat bahwa kami menggunakan implementasi "ganda" dari algoritme di Rust. Pertama, tanpa menggunakan alat Wasm, implementasi Rust "asli", "tidak aman" telah ditulis. Kemudian, dengan menggunakan wasm-pack, implementasi Rust tambahan yang "aman" dibuat. Penerapan algoritma ini diharapkan akan lebih mudah diintegrasikan dengan JS, dan dapat mengelola memori di Wasm dengan lebih baik.
Menguji
Kami menjalankan eksperimen kami di dua lingkungan utama. Ini adalah Node.js dan browser (Chrome).
Dalam kedua kasus, tolok ukur dilakukan dengan menggunakan skrip hangat. Yakni, pengumpulan sampah tidak dimulai sebelum menjalankan pengujian. Saat kami menjalankan pengumpulan sampah setelah menjalankan setiap pengujian, tampaknya tidak terlalu berdampak pada hasil.
Berdasarkan kode sumber yang ditulis dalam AssemblyScript, berikut ini dibuat:
- Implementasi JS dasar dari algoritma.
- Modul Wasm.
- Modul Asm.js.
Menarik untuk dicatat bahwa modul asm.js tidak sepenuhnya kompatibel dengan asm.js. Kami mencoba menambahkan perintah "gunakan asm" ke bagian atas modul, tetapi browser tidak menerima pengoptimalan ini. Kemudian kami menemukan bahwa kompilator binaryen yang kami gunakan tidak benar-benar mencoba membuat kode sepenuhnya kompatibel dengan asm.js. Alih-alih, itu difokuskan pada pembentukan semacam versi JS Wasm yang efisien.
Kami pertama kali menjalankan pengujian di Node.js.
Menjalankan kode di lingkungan Node.js
Kemudian kami mengukur kinerja kode yang sama di browser.
Menjalankan Kode di Browser
Kami segera melihat bahwa versi asm.js kode bekerja lebih lambat daripada versi lainnya. Tetapi grafik ini tidak memungkinkan kami untuk secara jelas membandingkan hasil dari berbagai varian kode dengan implementasi algoritma JS dasar. Oleh karena itu, untuk lebih memahami semuanya, kami membuat beberapa diagram lagi.
Perbedaan antara implementasi algoritme lain dan implementasi JS (versi benchmark dengan koordinat titik 64-bit)
Perbedaan antara implementasi algoritme lain dan implementasi JS (versi benchmark dengan koordinat titik 32-bit)
Versi benchmark dengan koordinat titik 64-bit dan 32-bit sangat berbeda. Ini mungkin membuat kita berpikir bahwa dalam JavaScript, angka bisa menjadi keduanya. Faktanya adalah bahwa angka dalam JS, dalam implementasi algoritme, yang diambil sebagai basis perbandingan, selalu 64-bit, tetapi kompiler yang mengonversi kode dari bahasa lain ke Wasm bekerja dengan angka tersebut dengan cara yang berbeda.
Secara khusus, hal ini berdampak besar pada pengujian versi asm.js. Versi dengan titik koordinat 32-bit jauh lebih rendah kinerjanya dibandingkan dengan implementasi JS dasar dan versi asm.js, yang menggunakan angka 64-bit.
Pada diagram sebelumnya, sulit untuk memahami bagaimana performa varian kode lain dibandingkan dengan varian JS. Ini karena metrik AssemblyScript terlalu berbeda dari yang lain. Untuk memahami ini, kami membuat diagram lain, menghapus hasil asm.js.
Perbedaan antara implementasi algoritme lain dengan implementasi JS (versi tolok ukur dengan koordinat titik 64-bit, tanpa asm.js)
Perbedaan antara implementasi algoritme lain dan implementasi JS (varian tolok ukur dengan titik koordinat 32-bit, tanpa asm.js)
Representasi angka yang berbeda tampaknya telah memengaruhi versi lain dari pengujian. Tapi mereka mempengaruhi dengan cara yang berbeda. Misalnya varian C yang menggunakan angka 32 bit (float) menjadi lebih lambat dibandingkan varian C yang menggunakan angka 64 bit (double). Kedua versi Rust dari pengujian dengan nomor 32-bit (f32) menjadi lebih cepat daripada versi mereka dengan nomor 64-bit (f64).
Implementasi algoritma yang buruk?
Analisis data di atas mungkin menyarankan pemikiran berikut. Karena semua rakitan Wasm yang diuji performanya sangat mirip dengan implementasi JavaScript, mungkinkah implementasi Wasm hanya mencerminkan fitur performa implementasi asli dari algoritme?
Membandingkan implementasi asli dari suatu algoritma dengan implementasi JavaScript
Versi asli dari sebuah implementasi algoritma selalu lebih cepat daripada implementasi JavaScript.
Kami juga memperhatikan bahwa rakitan Wasm lebih lambat daripada versi asli dari kode yang digunakan untuk membuat rakitan semacam itu. Perbedaan kinerjanya adalah 20-50%. Kami menemukan ini pada versi benchmark yang dipersingkat dengan 1000 iterasi.
Implementasi C dan perakitan Wasm yang sesuai
Implementasi Rust dan build Wasm yang sesuai
Implementasi Rust, yang dibuat menggunakan wasm-pack, dan perakitan Wasm yang sesuai.
Saat mengukur waktu eksekusi kode asli, waktu yang diperlukan untuk memuat program juga diperhitungkan, dan dalam kasus varian Wasm, waktu ini tidak diperhitungkan.
Hasil
Rata-rata, perolehan performa dari dua implementasi Rust dari algoritme, dibandingkan dengan implementasi JS dasarnya, adalah 20%. Ini mungkin bagus untuk citra Rust, tetapi peningkatan kinerja terlalu sedikit dibandingkan dengan upaya yang diperlukan untuk mendapatkannya.
Kesimpulan apa yang bisa kita ambil dari tes ini? Dan berikut ini beberapa: penulisan kode JS yang cermat memungkinkan Anda mendapatkan kinerja yang cukup tinggi dan tidak perlu beralih ke bahasa pemrograman lain.
Mempelajari bahasa pemrograman baru selalu bagus. Tetapi pasti ada alasan bagus untuk mempelajari bahasa baru. Performa sering kali menjadi alasan yang "salah", karena arsitektur desain tingkat tinggi lebih penting untuk performa daripada kompiler atau pengoptimalan mikro.
Sebagai percobaan, kami mengubah JavaScript ke TypeScript untuk menulis implementasi algoritma visualisasi grafik gaya. Hasilnya, kami telah meningkatkan kualitas basis kode, tetapi bukan kinerjanya. Kami secara khusus mengukur kinerja, dan ternyata setelah transisi, kinerjanya sedikit meningkat, sebesar 5%. Mungkin alasannya adalah pemfaktoran ulang kode.
Jika Anda tertarik dengan pengembangan dan kinerja JavaScript, Anda mungkin tertarik untuk menonton pembicaraan ini , yang kedengarannya mirip dengan hasil yang kami dapatkan.
Bagaimana Anda mendekati pengembangan bagian "berat" dari proyek web?