cara menangani struktur data yang di-cache dengan multi-threading (misalnya openmp)

Saya menggunakan OpenMP untuk memparalelkan perpustakaan C++ kami. Di sana, kami memiliki berbagai tempat di mana kami menghindari menghitung ulang beberapa hal dengan menyimpan hasil dalam sebuah variabel (yaitu menyimpan hasil dalam cache untuk digunakan kembali). Namun, perilaku ini disembunyikan bagi pengguna dalam metode kelas. Misalnya, saat pertama kali menggunakan suatu metode, cache akan terisi. Semua penggunaan selanjutnya hanya akan dibaca dari cache.

Masalah saya sekarang adalah bahwa dalam program multi-utas, beberapa utas dapat memanggil metode seperti itu secara bersamaan, sehingga mengakibatkan kondisi balapan dalam membuat/mengakses cache. Saat ini saya sedang menyelesaikannya dengan meletakkan cache di bagian kritis, tetapi ini tentu saja memperlambat semuanya.

Contoh kelasnya mungkin sebagai berikut

class A {
public:
   A() : initialized(false)
     {}
   int get(int a)
      { 
#pragma omp critical(CACHING)
        if (!initialized)
          initialize_cache();
        return cache[a];
      }
private:
   bool initialized;
   void initialize_cache()
     {
       // do some heavy stuff
       initialized=true;
     }
   int *cache;
};

Akan lebih baik jika bagian kritis berada dalam fungsi inisialisasi_cache(), karena itu hanya akan mengunci semua utas ketika cache belum diinisialisasi (yaitu hanya sekali), tetapi itu tampaknya berbahaya karena beberapa utas dapat mencoba untuk menginisialisasi cache secara bersamaan.

Adakah saran untuk memperbaikinya? Idealnya solusinya akan kompatibel dengan versi OpenMP yang lebih lama (bahkan v2 untuk Visual Studio...)

PS: Ini mungkin sudah ditanyakan sebelumnya, tapi pencarian openmp dan caching memunculkan banyak hal pada cache prosesor, bukan itu yang ingin saya ketahui...


person krthie    schedule 16.01.2015    source sumber
comment
Bisakah Anda membuat kelas baru dan variabel global dari kelas itu dan meminta ctor baru melakukan init Anda?   -  person brian beuning    schedule 16.01.2015
comment
dalam kehidupan nyata, objek saya adalah set_up saat run-time dengan parameter berbeda. ketika sudah set_up, cache akan dihapus karena sudah tidak sesuai lagi. jadi, tidak, saya tidak bisa memiliki variabel global. Selain itu, karena waktu komputasi untuk cache cukup tinggi, dan tidak diperlukan dalam semua kasus penggunaan, saat ini kami menghindari menginisialisasi cache, kecuali kami benar-benar membutuhkannya. (Anda tidak dapat mengetahui hal ini dari pertanyaan. Saya kira ini terjadi ketika mencoba membuat contoh sederhana).   -  person krthie    schedule 16.01.2015


Jawaban (2)


Anda dapat menggunakan "Pola Double-Checked-Locking(DCL)" dengan operasi atom OpenMP, Diperlukan OpenMP v3.1 atau lebih baru (opsi read/write dari omp atomic pragma).

class A {
public:
   A() : initialized(false)
     {}
   int get(int a)
      {
        bool b;
#pragma omp atomic read
        b = initialized;
        if (!b) {
#pragma omp critical(CACHING)
          // you must recheck in critical section
          if (!initialized)
            initialize_cache();
        }
        return cache[a];
      }
private:
   bool initialized;
   void initialize_cache()
     {
       // do some heavy stuff
#pragma omp atomic write
       initialized = true;
     }
   int *cache;
};

...Tapi saya merekomendasikan salah satu opsi berikut daripada pola DCL:

  • pthread_once() (Perpustakaan Utas POSIX)
  • std::call_once() (Perpustakaan standar C++11)
  • variabel static yang aman untuk thread (fitur bahasa inti C++11)
person yohjp    schedule 17.01.2015
comment
adakah peluang untuk melakukan ini dengan versi openmp sebelumnya? Saya harus kompatibel lintas platform semaksimal mungkin. Apakah solusi ini mengatasi masalah dengan pola DCL (lihat misalnya michaelsuess.net/publications/suess_leopold_singleton_07.pdf) - person krthie; 21.01.2015
comment
AFAIK, tidak ada cara portabel sebelum OpenMP v3.0, kecuali langsung menggunakan konstruksi omp critical. Dan menurut saya kode ini berfungsi dengan baik pada model memori OpenMP, karena akses initialized ditetapkan sebagai atomik dan tetap dilindungi oleh bagian kritis. - person yohjp; 21.01.2015

Singleton yang efisien adalah pilihan terbaik untuk Anda. Silakan periksa di sini.singleton thread-safe yang efisien di C++

Selain itu, Herb Sutter membicarakan hal itu di CppCon 2014

Berikut cuplikan kode lengkap dari video yang saya tunjukkan di atas:

class Foo {
public:
    static Foo* Instance();
private:
    Foo() {init();}
    void init() { cout << "init done." << endl;} // your init cache function.
    static atomic<Foo*> pinstance;
    static mutex m_;
};

atomic<Foo*> Foo::pinstance { nullptr };
std::mutex Foo::m_;

Foo* Foo::Instance() {
  if(pinstance == nullptr) {
    lock_guard<mutex> lock(m_);
    if(pinstance == nullptr) {
        pinstance = new Foo();
    }
  }
  return pinstance;
}

jalankan kode di sini: http://ideone.com/olvK13

person qqibrow    schedule 16.01.2015
comment
terima kasih. Cache saya harus spesifik untuk objek, yaitu bukan tunggal tetapi saya akui bahwa mungkin tidak jelas dari pertanyaan saya. - person krthie; 22.01.2015