Saya sedang mengerjakan aplikasi Java untuk memecahkan kelas masalah optimasi numerik - masalah pemrograman linier skala besar menjadi lebih tepat. Satu masalah dapat dipecah menjadi sub-masalah yang lebih kecil yang dapat diselesaikan secara paralel. Karena ada lebih banyak submasalah dari inti CPU, saya menggunakan ExecutorService dan mendefinisikan setiap subproblem sebagai Callable yang dikirimkan ke ExecutorService. Memecahkan subproblem membutuhkan memanggil perpustakaan asli - pemecah pemrograman linier dalam kasus ini.
Masalah
Saya dapat menjalankan aplikasi pada Unix dan pada sistem Windows dengan hingga 44 core fisik dan memori 256g, tetapi waktu komputasi pada Windows adalah urutan besarnya lebih tinggi daripada di Linux untuk masalah besar. Windows tidak hanya membutuhkan lebih banyak memori, tetapi pemanfaatan CPU dari waktu ke waktu turun dari 25% di awal menjadi 5% setelah beberapa jam. Berikut ini adalah tangkapan layar dari task manager di Windows:
Pengamatan
- Waktu solusi untuk contoh besar dari keseluruhan masalah berkisar dari berjam-jam dan menghabiskan hingga 32g memori (pada Unix). Waktu solusi untuk subproblem berada dalam kisaran ms.
- Saya tidak menemukan masalah ini pada masalah kecil yang hanya membutuhkan waktu beberapa menit untuk menyelesaikannya.
- Linux menggunakan kedua soket out-of-the-box, sedangkan Windows mengharuskan saya untuk secara eksplisit mengaktifkan interleaving memori di BIOS sehingga aplikasi menggunakan kedua core. Apakah saya melakukan ini tidak berpengaruh pada penurunan pemanfaatan CPU secara keseluruhan dari waktu ke waktu.
- Ketika saya melihat utas di VisualVM semua utas kolam berjalan, tidak ada yang menunggu atau yang lain.
- Menurut VisualVM, 90% waktu CPU dihabiskan untuk panggilan fungsi asli (menyelesaikan program linear kecil)
- Pengumpulan Sampah tidak menjadi masalah karena aplikasi tidak membuat dan mengurangi referensi banyak objek. Juga, sebagian besar memori tampaknya dialokasikan-tumpukan. 4g tumpukan cukup di Linux dan 8g di Windows untuk contoh terbesar.
Apa yang saya coba
- semua jenis argumen JVM, XMS tinggi, metaspace tinggi, flag UseNUMA, GC lain.
- JVM yang berbeda (Hotspot 8, 9, 10, 11).
- perpustakaan asli yang berbeda dari pemecah pemrograman linier yang berbeda (CLP, Xpress, Cplex, Gurobi).
Pertanyaan
- Apa yang mendorong perbedaan kinerja antara Linux dan Windows dari aplikasi Java multi-utas besar yang banyak menggunakan panggilan asli?
- Apakah ada sesuatu yang dapat saya ubah dalam implementasi yang akan membantu Windows, misalnya, haruskah saya menghindari menggunakan ExecutorService yang menerima ribuan Callable dan melakukan apa?
ForkJoinPool
bukanExecutorService
? Utilisasi CPU 25% benar-benar rendah jika masalah Anda terkait dengan CPU.ForkJoinPool
lebih efisien daripada penjadwalan manual.Jawaban:
Untuk Windows, jumlah utas per proses dibatasi oleh ruang alamat proses (lihat juga Mark Russinovich - Mendorong Batas Windows: Proses dan Utas ). Pikirkan ini menyebabkan efek samping ketika mendekati batas (memperlambat konteks, fragmentasi ...). Untuk Windows, saya akan mencoba membagi beban pekerjaan ke satu set proses. Untuk masalah serupa yang saya miliki bertahun-tahun lalu saya mengimplementasikan perpustakaan Java untuk melakukan ini dengan lebih mudah (Java 8), lihat apakah Anda suka: Perpustakaan untuk menelurkan tugas dalam proses eksternal .
sumber
Kedengarannya seperti windows caching beberapa memori untuk pagefile, setelah itu tidak tersentuh untuk beberapa waktu, dan itulah sebabnya CPU terhambat oleh kecepatan Disk
Anda dapat memverifikasinya dengan Process explorer dan memeriksa berapa banyak memori yang di-cache
sumber
Saya pikir perbedaan kinerja ini disebabkan oleh bagaimana OS mengelola utas. JVM menyembunyikan semua perbedaan OS. Ada banyak situs di mana Anda dapat membacanya, seperti ini , misalnya. Namun bukan berarti perbedaannya menghilang.
Saya kira Anda menjalankan pada Java 8+ JVM. Karena kenyataan ini, saya sarankan Anda untuk mencoba menggunakan fitur pemrograman aliran dan fungsional. Pemrograman fungsional sangat berguna ketika Anda memiliki banyak masalah independen kecil dan Anda ingin dengan mudah beralih dari eksekusi berurutan ke paralel. Berita baiknya adalah Anda tidak perlu menetapkan kebijakan untuk menentukan berapa banyak utas yang harus Anda kelola (seperti dengan ExecutorService). Contohnya saja (diambil dari sini ):
Jadi, saya sarankan Anda membaca tentang pemrograman fungsi, aliran, fungsi lambda di Jawa dan mencoba menerapkan sejumlah kecil tes dengan kode Anda (disesuaikan untuk bekerja dalam konteks baru ini).
sumber
Bisakah Anda memposting statistik sistem? Manajer tugas cukup baik untuk memberikan petunjuk jika itu adalah satu-satunya alat yang tersedia. Ini dapat dengan mudah mengetahui apakah tugas Anda menunggu IO - yang terdengar seperti pelakunya berdasarkan apa yang Anda jelaskan. Mungkin karena masalah manajemen memori tertentu, atau perpustakaan dapat menulis beberapa data sementara ke disk, dll.
Ketika Anda mengatakan 25% dari pemanfaatan CPU, maksud Anda hanya beberapa core yang sibuk bekerja pada saat yang sama? (Bisa jadi semua inti bekerja dari waktu ke waktu, tetapi tidak secara bersamaan.) Apakah Anda memeriksa berapa banyak utas (atau proses) yang benar-benar dibuat dalam sistem? Apakah jumlahnya selalu lebih besar dari jumlah core?
Jika ada cukup utas, apakah banyak dari mereka menganggur menunggu sesuatu? Jika benar, Anda dapat mencoba menyela (atau melampirkan debugger) untuk melihat apa yang mereka tunggu.
sumber