Mengikuti dengan prinsip tidak mengoptimalkan terlalu dini, saya bertanya-tanya pada titik apa dalam desain / pengembangan perangkat lunak Anda mulai berpikir tentang peluang konkurensi?
Saya bisa membayangkan bahwa satu strategi adalah menulis satu aplikasi berulir, dan melalui profil mengidentifikasi bagian-bagian yang kandidat untuk dijalankan secara paralel. Strategi lain yang saya lihat sedikit adalah mempertimbangkan perangkat lunak berdasarkan kelompok tugas dan membuat tugas-tugas independen sejajar.
Bagian dari alasan untuk bertanya adalah tentu saja, jika Anda menunggu sampai selesai dan hanya memperbaiki perangkat lunak untuk beroperasi secara bersamaan, Anda dapat menyusun berbagai hal dengan cara yang paling buruk dan memiliki tugas utama di tangan Anda.
Pengalaman apa yang telah membantu menentukan kapan Anda mempertimbangkan paralelisasi dalam desain Anda?
Pemrogram Java harus merangkul
Callable
antarmuka untuk unit kerja. Jika seluruh aplikasi Anda terdiri dari satu loop untuk menciptakan Callable, mengirimkan semua unit ke seorang Pelaksana, menangani tugas-tugas generasi pasca, Anda memiliki sesuatu yang dapat dengan mudah dibentuk menjadi pemrosesan serial, "tiga antrian kerja" dan "melakukan semua sekaligus "hanya dengan memilih pelaksana yang tepat.Kami perlahan-lahan mengadaptasi pola ini karena sangat umum bahwa pendekatan serial terlalu lambat pada satu titik dan kemudian kita perlu melakukannya juga.
sumber
Bervariasi dengan proyek. Terkadang sangat mudah untuk melihat apa yang dapat dibuat paralel: mungkin program Anda memproses kumpulan file. Misalkan pemrosesan setiap file benar-benar independen dari semua file lain sehingga mungkin cukup jelas bahwa Anda dapat memproses 1 file pada suatu waktu, atau 10, atau 100, dan tidak satu pun dari pekerjaan ini akan mempengaruhi yang lain.
Itu menjadi sedikit lebih rumit ketika potensi pekerjaan paralel tidak sama. Memproses file gambar, Anda bisa memiliki satu pekerjaan yang membuat histogram, yang lain menghasilkan thumbnail, dan mungkin yang lain yang mengekstrak metadata EXIF dan kemudian pekerjaan terakhir yang mengambil output dari semua pekerjaan ini dan menyimpannya dalam database. Dalam contoh ini, mungkin tidak jelas apakah ini harus dijalankan secara paralel, atau jika harus (pekerjaan terakhir harus menunggu pekerjaan sebelumnya selesai dengan sukses).
Dalam pengalaman saya, cara termudah untuk memaralelkan sesuatu adalah dengan mencari proses yang dapat dijalankan secara mandiri mungkin (seperti pada contoh pertama) dan mulai dengan itu. Saya hanya akan mencoba membuat contoh kedua berjalan secara paralel jika saya pikir saya akan mendapatkan keuntungan yang signifikan dalam kinerja dengannya.
sumber
Anda harus mendesain concurrency ke dalam aplikasi Anda dari awal. Biasanya sebagai optimasi saya setuju bahwa itu harus dibiarkan sampai nanti jika tidak jelas secara inheren. Masalahnya adalah bahwa concurrency mungkin perlu merancang kembali aplikasi Anda dari awal dalam kasus terburuk - beberapa sistem hampir tidak mungkin untuk memiliki concurrency yang dilekatkan. Contoh mudahnya adalah sistem yang berbagi data - misalnya, aspek simulasi dan rendering game.
sumber
Saya akan mengatakan bahwa threading adalah bagian dari arsitektur aplikasi. Jadi itu adalah salah satu hal pertama yang perlu saya pikirkan.
Misalnya ketika saya melakukan aplikasi GUI, kode GUI adalah utas tunggal sehingga tugas yang berjalan lama (misalnya pemrosesan XML) akan memblokir GUI, dan harus dijalankan di utas latar sebagai gantinya.
Misalnya server akan berbasiskan thread, di mana setiap permintaan ditangani oleh utas baru, atau server dapat digerakkan oleh peristiwa dan hanya menggunakan satu utas per cpu-core, tetapi sekali lagi, tugas yang berjalan lama harus dijalankan dalam latar belakang atau dibagi menjadi tugas yang lebih kecil.
sumber
Dengan cara saya mendekati berbagai hal, jenis multithreading datang gratis dan relatif mudah untuk diterapkan di belakang. Tapi saya berpikir tentang data dulu. Saya tidak tahu apakah ini berfungsi untuk semua domain, tetapi saya akan mencoba untuk membahas bagaimana saya melakukannya.
Jadi pertama-tama ini semua tentang jenis data kasar yang diperlukan untuk perangkat lunak yang akan sering diproses. Jika itu adalah permainan yang mungkin hal-hal seperti jerat, suara, gerakan, penghasil partikel, lampu, tekstur, hal-hal semacam ini. Dan tentu saja ada banyak hal yang perlu dipikirkan jika Anda menelusuri hanya ke jerat dan berpikir tentang bagaimana mereka harus diwakili, tetapi kami akan melewatkan itu untuk saat ini. Saat ini kami sedang berpikir di tingkat arsitektur terluas.
Dan pikiran pertama saya adalah, "Bagaimana kita menyatukan representasi semua hal ini sehingga kita dapat mencapai pola akses yang relatif seragam untuk semua jenis hal ini?" Dan pemikiran pertama saya mungkin untuk menyimpan setiap jenis benda dalam susunannya yang berdekatan dengan cara daftar-tipe gratis untuk mendapatkan kembali ruang kosong. Dan itu cenderung menyatukan API sehingga kita bisa lebih mudah, katakanlah, menggunakan jenis kode yang sama untuk membuat serial jerat seperti yang kita lakukan lampu dan tekstur, setidaknya sejauh di mana dan bagaimana komponen ini diakses. Semakin kita dapat menyatukan bagaimana semua hal direpresentasikan, semakin banyak kode yang mengakses hal-hal tersebut cenderung berbentuk seragam.
Itu keren. Sekarang kita juga dapat menunjuk hal-hal ini dengan indeks 32-bit dan hanya mengambil setengah memori dari pointer 64-bit. Dan hei, kita dapat melakukan set persimpangan dalam waktu linier sekarang jika kita dapat mengaitkan bitset paralel, misalnya Kita juga dapat mengaitkan data ke salah satu dari hal-hal ini dengan sangat murah secara paralel karena kita mengindeks semuanya. Oh dan bitet itu dapat memberikan kita kembali satu set indeks yang diurutkan untuk dilalui secara berurutan untuk pola akses memori yang ditingkatkan, tidak harus memuat ulang garis cache yang sama beberapa kali dalam satu loop tunggal. Kami dapat menguji 64-bit pada suatu waktu. Jika semua 64-bit tidak disetel, kita dapat melewati lebih dari 64 elemen sekaligus. Jika semuanya diatur, kita bisa memproses semuanya sekaligus. Jika beberapa diatur tetapi tidak semua, kita dapat menggunakan instruksi FFS untuk dengan cepat menentukan bit mana yang ditetapkan.
Tapi oh, tunggu, itu agak mahal jika kita hanya ingin mengaitkan data dengan beberapa ratus hal dari puluhan ribu hal. Jadi mari kita gunakan array jarang sebagai gantinya, seperti:
Dan hei, sekarang kita memiliki semua yang tersimpan dalam array jarang dan mengindeksnya, akan sangat mudah untuk membuat ini menjadi struktur data yang persisten.
Sekarang kita dapat menulis lebih banyak fungsi murah tanpa efek samping karena mereka tidak perlu menyalin apa yang belum berubah.
Dan di sini saya sudah diberikan lembar contekan setelah belajar tentang mesin ECS, tetapi sekarang mari kita pikirkan tentang jenis fungsi luas apa yang harus beroperasi pada setiap jenis komponen. Kita bisa menyebutnya "sistem". "SoundSystem" dapat memproses komponen "Sound". Setiap sistem adalah fungsi luas yang beroperasi pada satu atau lebih tipe data.
Itu membuat kami memiliki banyak kasus di mana, untuk setiap jenis komponen tertentu, hanya satu atau dua sistem yang secara umum akan mengaksesnya. Hmm, yang pasti seperti itu akan membantu dengan keamanan benang dan benar-benar membawa pertengkaran thread ke minimum.
Selanjutnya saya mencoba untuk berpikir tentang bagaimana melakukan melewati data yang homogen. Alih-alih seperti:
Saya berusaha untuk membaginya menjadi beberapa, lebih sederhana:
Itu kadang-kadang membutuhkan menyimpan beberapa keadaan menengah untuk lulus ditangguhkan homogen berikutnya untuk memproses tetapi saya menemukan itu benar-benar membantu saya untuk mempertahankan dan alasan tentang kode, mengetahui bahwa setiap loop memiliki lebih sederhana, logika lebih seragam. Dan hei, sepertinya itu akan menyederhanakan keamanan thread dan mengurangi pertikaian thread.
Dan Anda tetap seperti ini sampai Anda menemukan bahwa Anda memiliki arsitektur yang sangat mudah diparalelkan dengan kepercayaan tentang keamanan dan kebenaran utasnya, tetapi semuanya pada awalnya dengan fokus menyatukan representasi data, memiliki pola akses memori yang lebih dapat diprediksi, mengurangi penggunaan memori, menyederhanakan aliran kontrol ke lintasan yang lebih homogen, mengurangi jumlah fungsi dalam sistem Anda yang menyebabkan efek samping tanpa menimbulkan biaya penyalinan yang sangat mahal, menyatukan API Anda, dll.
Ketika Anda menggabungkan semua hal ini, Anda cenderung berakhir dengan sistem yang meminimalkan jumlah keadaan bersama di mana Anda agak tersandung pada desain yang sangat ramah untuk konkurensi. Dan jika ada negara yang perlu dibagikan, Anda sering menemukan tidak memiliki banyak pertentangan di mana itu murah untuk menggunakan beberapa sinkronisasi tanpa menyebabkan kemacetan lalu lintas thread, dan lebih jauh lagi itu sering dapat ditangani oleh struktur data pusat Anda yang menyatukan representasi semua hal dalam sistem sehingga Anda tidak perlu menerapkan sinkronisasi utas ke seratus tempat yang berbeda, hanya segelintir.
Sekarang ketika kita menelusuri ke salah satu komponen yang lebih kompleks seperti jerat, kita mengulangi proses yang sama untuk mendesainnya, dimulai dengan memikirkan data terlebih dahulu. Dan jika kita melakukan itu dengan benar, kita bahkan mungkin dapat dengan mudah memparalelkan pemrosesan mesh tunggal, tetapi desain arsitektur yang lebih luas yang kami bangun sudah memungkinkan kami untuk memaralelkan pemrosesan beberapa mesh.
sumber