Manajemen memori untuk pengiriman pesan cepat antar utas di C ++

9

Misalkan ada dua utas, yang berkomunikasi dengan tidak sinkron mengirim pesan data satu sama lain. Setiap utas memiliki beberapa jenis antrian pesan.

Pertanyaan saya sangat rendah: Apa yang bisa diharapkan menjadi cara paling efisien untuk mengelola memori? Saya dapat memikirkan beberapa solusi:

  1. Pengirim membuat objek melalui new. Panggilan penerima delete.
  2. Memory pooling (untuk mentransfer memori kembali ke pengirim)
  3. Pengumpulan sampah (mis., Boehm GC)
  4. (Jika objek cukup kecil) salin dengan nilai untuk menghindari alokasi tumpukan sepenuhnya

1) adalah solusi yang paling jelas, jadi saya akan menggunakannya untuk prototipe. Kemungkinannya sudah cukup baik. Tetapi terlepas dari masalah spesifik saya, saya bertanya-tanya teknik mana yang paling menjanjikan jika Anda mengoptimalkan kinerja.

Saya berharap pooling menjadi yang terbaik secara teoritis, terutama karena Anda dapat menggunakan pengetahuan ekstra tentang aliran informasi antara utas. Namun, saya khawatir itu juga yang paling sulit untuk dilakukan dengan benar. Banyak penyetelan ... :-(

Pengumpulan sampah harus cukup mudah untuk ditambahkan setelahnya (setelah solusi 1), dan saya berharap ini akan bekerja dengan sangat baik. Jadi, saya kira itu adalah solusi paling praktis jika 1) ternyata terlalu tidak efisien.

Jika objek kecil dan sederhana, salin berdasarkan nilai mungkin yang tercepat. Namun, saya khawatir hal itu memaksa pembatasan yang tidak perlu pada implementasi pesan yang didukung, jadi saya ingin menghindarinya.

Philipp Claßen
sumber

Jawaban:

9

Jika objek kecil dan sederhana, salin berdasarkan nilai mungkin yang tercepat. Namun, saya khawatir hal itu memaksa pembatasan yang tidak perlu pada implementasi pesan yang didukung, jadi saya ingin menghindarinya.

Jika Anda dapat mengantisipasi batas atas char buf[256], misalnya Alternatif praktis jika Anda tidak dapat melakukannya, yang hanya memanggil alokasi tumpukan dalam kasus yang jarang terjadi:

struct Message
{
    // Stores the message data.
    char buf[256];

    // Points to 'buf' if it fits, heap otherwise.
    char* data;
};

sumber
3

Ini akan tergantung pada bagaimana Anda mengimplementasikan antrian.

Jika Anda menggunakan array (gaya round robin), Anda perlu mengatur batas atas ukuran untuk solusi 4. Jika Anda menggunakan antrian yang ditautkan, Anda perlu objek yang dialokasikan.

Kemudian, pengumpulan sumber daya dapat dilakukan dengan mudah ketika Anda baru mengganti yang baru dan menghapus dengan AllocMessage<T>dan freeMessage<T>. Saran saya adalah membatasi jumlah ukuran potensial yang Tdapat dimiliki dan dibulatkan ketika mengalokasikan beton messages.

Pengumpulan sampah langsung dapat bekerja tetapi itu mungkin menyebabkan jeda lama ketika perlu mengumpulkan sebagian besar, dan akan (saya pikir) melakukan sedikit lebih buruk daripada yang baru / hapus.

aneh ratchet
sumber
3

Jika ini dalam C ++, cukup gunakan salah satu pointer cerdas - unique_ptr akan bekerja dengan baik untuk Anda, karena tidak akan menghapus objek yang mendasarinya sampai tidak ada yang memiliki pegangan di atasnya. Anda meneruskan objek ptr ke penerima dengan nilai dan tidak perlu khawatir tentang utas mana yang harus menghapusnya (dalam kasus di mana penerima tidak menerima objek).

Anda masih perlu menangani penguncian di antara utas-utas tetapi kinerja akan baik karena tidak ada memori yang disalin (hanya objek ptr itu sendiri, yang kecil).

Mengalokasikan memori pada heap bukanlah hal tercepat yang pernah ada, jadi pooling digunakan untuk membuat ini lebih cepat. Anda hanya mengambil blok berikutnya dari tumpukan pra-ukuran di kolam, jadi gunakan saja perpustakaan yang ada untuk ini.

gbjbaanb
sumber
2
Mengunci biasanya merupakan masalah yang jauh lebih besar daripada menyalin memori. Hanya mengatakan.
tdammers
Ketika Anda menulis unique_ptr, saya kira maksud Anda shared_ptr. Tetapi sementara tidak ada keraguan bahwa menggunakan pointer pintar baik untuk manajemen sumber daya, itu tidak mengubah fakta bahwa Anda menggunakan beberapa bentuk alokasi memori dan deallokasi. Saya pikir pertanyaan ini lebih rendah.
5gon12eder
3

Performa terbesar yang menghantam ketika mengomunikasikan suatu objek dari satu utas ke utas lainnya adalah overhead dari meraih kunci. Ini ada di urutan beberapa mikrodetik, yang secara signifikan lebih dari waktu rata-rata sepasang new/ deleteambil (pada urutan seratus nanodetik). newImplementasi yang sehat mencoba menghindari penguncian di hampir semua biaya untuk menghindari kinerja mereka yang gagal.

Karena itu, Anda ingin memastikan bahwa Anda tidak perlu mengambil kunci saat mengomunikasikan objek dari satu utas ke utas lainnya. Saya tahu dua metode umum untuk mencapai ini. Keduanya hanya bekerja secara tidak langsung antara satu pengirim dan satu penerima:

  1. Gunakan penyangga dering. Kedua proses mengontrol satu pointer ke buffer ini, satu adalah pointer baca, yang lainnya adalah pointer tulis.

    • Pengirim pertama memeriksa apakah ada ruang untuk menambahkan elemen dengan membandingkan pointer, lalu menambahkan elemen, lalu menambah pointer tulis.

    • Penerima memeriksa apakah ada elemen untuk dibaca dengan membandingkan pointer, kemudian membaca elemen, lalu menambah pointer baca.

    Pointer harus berupa atom karena dibagi di antara utas. Namun, setiap pointer hanya dimodifikasi oleh satu utas, yang lain hanya perlu membaca akses ke pointer. Elemen-elemen dalam buffer mungkin adalah pointer sendiri, yang memungkinkan Anda untuk dengan mudah mengukur buffer cincin Anda ke ukuran yang tidak akan membuat blok pengirim.

  2. Gunakan daftar tertaut yang selalu mengandung setidaknya satu elemen. Penerima memiliki pointer ke elemen pertama, pengirim memiliki pointer ke elemen terakhir. Pointer ini tidak dibagikan.

    • Pengirim membuat simpul baru untuk daftar tertaut, mengatur nextpenunjuknya ke nullptr. Kemudian memperbarui nextpointer elemen terakhir untuk menunjuk ke elemen baru. Akhirnya, ia menyimpan elemen baru di penunjuknya sendiri.

    • Penerima menonton nextpointer elemen pertama untuk melihat apakah ada data baru yang tersedia. Jika demikian, itu menghapus elemen pertama yang lama, memajukan pointer sendiri untuk menunjuk ke elemen saat ini dan mulai memprosesnya.

    Dalam pengaturan ini, nextpenunjuk harus berupa atom, dan pengirim harus yakin untuk tidak melakukan dereferensi elemen terakhir kedua setelah nextpenunjuknya ditetapkan . Keuntungannya, tentu saja, bahwa pengirim tidak harus memblokir.

Kedua pendekatan ini jauh lebih cepat daripada pendekatan berbasis kunci apa pun, tetapi mereka membutuhkan implementasi yang hati-hati agar benar. Dan, tentu saja, mereka memerlukan atomicity perangkat keras asli dari pointer menulis / memuat; jika atomic<>implementasi Anda menggunakan kunci secara internal, Anda akan sangat hancur.

Demikian juga, jika Anda memiliki beberapa pembaca dan / atau penulis, Anda cukup banyak ditakdirkan: Anda dapat mencoba membuat skema tanpa kunci, tetapi akan sulit untuk mengimplementasikannya sebaik mungkin. Situasi ini lebih mudah ditangani dengan kunci. Namun, begitu Anda mengambil kunci, Anda bisa berhenti mengkhawatirkan new/ deletekinerja.

cmaster - mengembalikan monica
sumber
+1 Saya harus memeriksa solusi buffer cincin ini sebagai alternatif antrian bersamaan menggunakan loop CAS. Kedengarannya sangat menjanjikan.