Pertanyaan C ++ 11 memperkenalkan model memori standar. Apa artinya? Dan bagaimana itu akan mempengaruhi pemrograman C ++?


C ++ 11 memperkenalkan model memori standar, tetapi apa sebenarnya artinya itu? Dan bagaimana itu akan mempengaruhi pemrograman C ++?

Artikel ini (oleh Gavin Clarke siapa yang mengutip Herb Sutter) mengatakan itu,

Model memori berarti kode C ++   sekarang memiliki perpustakaan standar untuk dipanggil   terlepas siapa yang membuat compiler   dan pada platform apa itu berjalan.   Ada cara standar untuk mengontrol caranya   benang berbeda berbicara dengan   memori prosesor.

"Ketika Anda berbicara tentang pemisahan   [kode] di berbagai inti itu   dalam standar, yang sedang kita bicarakan   model memori. Kita akan   mengoptimalkannya tanpa merusak   mengikuti asumsi orang-orang akan pergi   untuk membuat kode, " Sutter kata.

Yah, saya bisa menghafal paragraf ini dan serupa tersedia secara online (karena saya sudah memiliki model memori saya sendiri sejak lahir: P) dan bahkan dapat memposting sebagai jawaban atas pertanyaan yang ditanyakan oleh orang lain, tetapi jujur ​​saja, saya tidak benar-benar memahami hal ini.

Jadi, apa yang pada dasarnya ingin saya ketahui adalah, programmer C ++ digunakan untuk mengembangkan aplikasi multi-threaded bahkan sebelumnya, jadi bagaimana masalah jika thread POSIX, atau Windows thread, atau C ++ 11 thread? Apa manfaatnya? Saya ingin memahami detail tingkat rendah.

Saya juga mendapatkan perasaan ini bahwa model memori C ++ 11 entah bagaimana terkait dengan dukungan multi-threading C ++ 11, karena saya sering melihat keduanya bersama-sama. Jika ya, bagaimana tepatnya? Mengapa mereka harus terkait?

Karena saya tidak tahu bagaimana kerja internal multi-threading, dan apa yang dimaksud dengan model memori pada umumnya, tolong bantu saya memahami konsep-konsep ini. :-)


1550
2018-06-11 23:30


asal


Jawaban:


Pertama, Anda harus belajar berpikir seperti Pengacara Bahasa.

Spesifikasi C ++ tidak mengacu pada kompiler, sistem operasi, atau CPU tertentu. Itu membuat referensi ke suatu mesin abstrak itu adalah generalisasi sistem yang sebenarnya. Di dunia Pengacara Bahasa, pekerjaan programmer adalah menulis kode untuk mesin abstrak; pekerjaan kompilator adalah mengaktualisasikan kode itu pada mesin beton. Dengan mengode secara kaku ke spesifikasi, Anda dapat memastikan bahwa kode Anda akan dikompilasi dan dijalankan tanpa modifikasi pada sistem apa pun dengan compiler C ++ compiler, baik hari ini atau 50 tahun dari sekarang.

Mesin abstrak dalam spesifikasi C ++ 98 / C ++ 03 pada dasarnya adalah single-threaded. Jadi tidak mungkin untuk menulis kode C ++ multi-threaded yang "sepenuhnya portabel" sehubungan dengan spesifikasi. Spek bahkan tidak mengatakan apa-apa tentang atomicity beban memori dan toko atau memesan di mana banyak dan toko mungkin terjadi, apalagi hal-hal seperti mutexes.

Tentu saja, Anda dapat menulis kode multi-berulir dalam praktek untuk sistem beton tertentu - seperti pthreads atau Windows. Tetapi tidak ada standarcara untuk menulis kode multi-berulir untuk C ++ 98 / C ++ 03.

Mesin abstrak dalam C ++ 11 adalah multi-threaded oleh desain. Ini juga memiliki definisi yang jelas model memori; artinya, ia mengatakan apa yang mungkin dilakukan oleh kompiler dan mungkin tidak dilakukan ketika mengakses memori.

Pertimbangkan contoh berikut, di mana sepasang variabel global diakses secara bersamaan oleh dua utas:

           Global
           int x, y;

Thread 1            Thread 2
x = 17;             cout << y << " ";
y = 37;             cout << x << endl;

Apa yang mungkin Thread 2 output?

Di bawah C ++ 98 / C ++ 03, ini bukan Perilaku Tidak Terdefinisi; pertanyaannya sendiri tak berarti karena standar tidak merenungkan apa pun yang disebut "utas".

Di bawah C ++ 11, hasilnya adalah Perilaku Tidak Terdefinisi, karena beban dan toko tidak perlu menjadi atom secara umum. Yang mungkin tidak tampak seperti peningkatan ... Dan dengan sendirinya, itu tidak.

Tetapi dengan C ++ 11, Anda dapat menulis ini:

           Global
           atomic<int> x, y;

Thread 1                 Thread 2
x.store(17);             cout << y.load() << " ";
y.store(37);             cout << x.load() << endl;

Sekarang hal-hal menjadi jauh lebih menarik. Pertama-tama, perilaku di sini adalah didefinisikan. Thread 2 sekarang bisa mencetak 0 0 (jika berjalan sebelum Thread 1), 37 17 (jika berjalan setelah Thread 1), atau 0 17 (jika berjalan setelah Thread 1 ditetapkan ke x tetapi sebelum ditetapkan ke y).

Apa yang tidak bisa dicetak 37 0, karena mode default untuk beban / penyimpanan atom di C ++ 11 adalah untuk menegakkan konsistensi berurutan. Ini hanya berarti semua beban dan toko harus "seolah-olah" terjadi dalam urutan yang Anda tulis dalam setiap utas, sementara operasi di antara untaian dapat disisipkan namun sistem menyukai. Jadi perilaku default dari atom menyediakan keduanya atomicity dan pemesanan untuk banyak dan toko.

Sekarang, pada CPU modern, memastikan konsistensi berurutan bisa mahal. Secara khusus, kompilator kemungkinan akan memancarkan hambatan memori penuh antara setiap akses di sini. Tetapi jika algoritme Anda dapat mentolerir beban dan penyimpanan di luar pesanan; yaitu, jika memerlukan atomicity tetapi tidak memesan; yaitu, apakah itu dapat ditoleransi 37 0 sebagai output dari program ini, maka Anda dapat menulis ini:

           Global
           atomic<int> x, y;

Thread 1                            Thread 2
x.store(17,memory_order_relaxed);   cout << y.load(memory_order_relaxed) << " ";
y.store(37,memory_order_relaxed);   cout << x.load(memory_order_relaxed) << endl;

Semakin modern CPU, semakin mungkin ini menjadi lebih cepat dari contoh sebelumnya.

Akhirnya, jika Anda hanya perlu menyimpan beban dan toko tertentu secara berurutan, Anda dapat menulis:

           Global
           atomic<int> x, y;

Thread 1                            Thread 2
x.store(17,memory_order_release);   cout << y.load(memory_order_acquire) << " ";
y.store(37,memory_order_release);   cout << x.load(memory_order_acquire) << endl;

Ini membawa kita kembali ke beban dan toko yang dipesan - jadi 37 0 tidak lagi merupakan output yang mungkin - tetapi ia melakukannya dengan overhead yang minimal. (Dalam contoh sepele ini, hasilnya sama dengan konsistensi sekuensial sepenuhnya; dalam program yang lebih besar, itu tidak akan terjadi.)

Tentu saja, jika satu-satunya output yang ingin Anda lihat adalah 0 0 atau 37 17, Anda cukup membungkus mutex di sekitar kode asli. Tetapi jika Anda telah membaca sejauh ini, saya yakin Anda sudah tahu cara kerjanya, dan jawaban ini sudah lebih lama dari yang saya maksud :-).

Jadi, intinya. Mutexes sangat bagus, dan C ++ 11 menstandardisasi mereka. Namun terkadang untuk alasan kinerja Anda menginginkan primitif tingkat bawah (mis., Klasik pola penguncian double-checked). Standar baru menyediakan gadget tingkat tinggi seperti mutex dan variabel kondisi, dan juga menyediakan gadget tingkat rendah seperti jenis atom dan berbagai macam penghalang memori. Jadi sekarang Anda dapat menulis, rutinitas berkinerja tinggi berperforma tinggi yang lengkap sepenuhnya dalam bahasa yang ditentukan oleh standar, dan Anda dapat yakin kode Anda akan dikompilasi dan dijalankan tidak berubah pada sistem saat ini dan esok hari.

Meskipun harus jujur, kecuali Anda adalah seorang ahli dan bekerja pada beberapa kode tingkat rendah yang serius, Anda mungkin harus tetap berpegang pada variabel muteks dan kondisi. Itu yang ingin saya lakukan.

Untuk lebih lanjut tentang hal ini, lihat posting blog ini.


1797
2018-06-12 00:23



Saya hanya akan memberikan analogi dengan yang saya mengerti model konsistensi memori (atau model memori, untuk pendek). Ini terinspirasi oleh kertas seminal Leslie Lamport "Waktu, Jam, dan Pengurutan Acara dalam Sistem Terdistribusi". Analogi ini tepat dan memiliki signifikansi mendasar, tetapi mungkin berlebihan bagi banyak orang. Namun, saya berharap ini menyediakan gambar mental (representasi bergambar) yang memfasilitasi penalaran tentang model konsistensi memori.

Mari kita melihat sejarah dari semua lokasi memori dalam diagram ruang-waktu di mana sumbu horizontal mewakili ruang alamat (yaitu, setiap lokasi memori diwakili oleh titik pada sumbu itu) dan sumbu vertikal mewakili waktu (kita akan melihat itu, secara umum, tidak ada gagasan universal tentang waktu). Riwayat nilai yang dipegang oleh masing-masing lokasi memori, oleh karena itu, diwakili oleh kolom vertikal di alamat memori itu. Setiap perubahan nilai adalah karena salah satu utas menulis nilai baru ke lokasi itu. Oleh a gambar memori, kita akan berarti agregat / kombinasi nilai dari semua lokasi memori yang dapat diamati pada waktu tertentu oleh utas tertentu.

Mengutip dari "A Primer on Memory Consistency and Cache Coherence"

Model memori intuitif (dan paling ketat) adalah konsistensi berurutan (SC) di mana eksekusi multithread akan terlihat seperti interleaving dari eksekusi berurutan dari setiap thread konstituen, seolah-olah thread itu waktu-multiplexing pada prosesor single-core.

Urutan memori global dapat bervariasi dari satu program yang dijalankan ke program lainnya dan mungkin tidak diketahui sebelumnya. Fitur karakteristik SC adalah himpunan irisan horizontal dalam diagram alamat-ruang-waktu yang mewakili pesawat simultanitas (yaitu, gambar memori). Pada pesawat yang diberikan, semua peristiwa (atau nilai memorinya) adalah simultan. Ada gagasan tentang Waktu Absolut, di mana semua utas setuju tentang nilai memori mana yang simultan. Di SC, setiap saat instan, hanya ada satu gambar memori yang dibagikan oleh semua utas. Itu, setiap saat, semua prosesor setuju pada gambar memori (yaitu, isi keseluruhan memori). Tidak hanya ini menyiratkan bahwa semua utas melihat urutan nilai yang sama untuk semua lokasi memori, tetapi juga bahwa semua prosesor mengamati hal yang sama kombinasi nilai dari semua variabel. Ini sama dengan mengatakan semua operasi memori (pada semua lokasi memori) diamati dalam jumlah total yang sama oleh semua utas.

Dalam model memori santai, setiap thread akan mengiris address-space-time dengan caranya sendiri, satu-satunya pembatasan adalah bahwa irisan setiap thread tidak boleh saling silang karena semua thread harus setuju pada sejarah setiap lokasi memori individu (tentu saja , irisan benang yang berbeda dapat, dan akan, saling bersilangan). Tidak ada cara universal untuk mengirisnya (tidak ada folio alamat-ruang-waktu istimewa). Irisan tidak harus planar (atau linier). Mereka dapat melengkung dan ini adalah apa yang dapat membuat nilai-nilai yang dibaca oleh thread yang ditulis oleh thread lain dari urutan yang mereka tulis. Histori lokasi memori yang berbeda dapat meluncur (atau diregangkan) secara sewenang-wenang relatif terhadap satu sama lain bila dilihat oleh utas tertentu. Setiap utas akan memiliki arti yang berbeda dari kejadian mana (atau, secara ekuivalen, nilai memori) yang simultan. Kumpulan kejadian (atau nilai memori) yang serentak ke satu utas tidak simultan ke satu utas. Jadi, dalam model memori santai, semua utas masih mengamati sejarah yang sama (yaitu urutan nilai) untuk setiap lokasi memori. Tetapi mereka dapat mengamati gambar memori yang berbeda (yaitu kombinasi nilai dari semua lokasi memori). Bahkan jika dua lokasi memori yang berbeda ditulis oleh rangkaian yang sama secara berurutan, dua nilai yang baru ditulis dapat diamati dalam urutan yang berbeda oleh utas lainnya.

[Gambar dari Wikipedia] Picture from Wikipedia

Pembaca akrab dengan Einstein Teori Relativitas Khusus akan memperhatikan apa yang saya sebutkan. Menerjemahkan kata-kata Minkowski ke dalam dunia model memori: ruang alamat dan waktu adalah bayangan address-space-time. Dalam hal ini, setiap pengamat (yaitu, utas) akan memproyeksikan bayangan peristiwa (yaitu, penyimpanan / muatan memori) ke garis dunianya sendiri (yaitu, sumbu waktunya) dan bidang simultannya sendiri (poros address-space-nya) . Thread dalam model memori C ++ 11 sesuai dengan pengamat yang bergerak relatif satu sama lain dalam relativitas khusus. Konsistensi sekuensial sesuai dengan Ruang waktu Galilea (yaitu, semua pengamat setuju pada satu urutan kejadian absolut dan rasa simultan global).

Kemiripan antara model ingatan dan relativitas khusus berasal dari fakta bahwa keduanya mendefinisikan serangkaian peristiwa yang sebagian dipesan, sering disebut rangkaian kausal. Beberapa peristiwa (mis., Penyimpanan memori) dapat memengaruhi (tetapi tidak terpengaruh oleh) peristiwa lain. Thread C ++ 11 (atau pengamat dalam fisika) tidak lebih dari rantai (yaitu, kumpulan yang benar-benar dipesan) dari peristiwa (mis., Beban memori dan penyimpanan ke alamat yang mungkin berbeda).

Dalam relativitas, beberapa urutan dikembalikan ke gambaran yang tampak kacau dari sebagian peristiwa yang dipesan, karena satu-satunya pemesanan sementara yang disepakati oleh semua pengamat adalah pengurutan di antara peristiwa "mirip waktu" (yaitu, peristiwa-peristiwa yang pada prinsipnya dapat dihubungi oleh partikel mana pun menjadi lebih lambat. dari kecepatan cahaya dalam ruang hampa). Hanya peristiwa terkait waktu yang dipesan secara invarian. Waktu dalam Fisika, Craig Callender.

Dalam model memori C ++ 11, mekanisme yang serupa (model konsistensi perolehan-rilis) digunakan untuk menetapkan ini hubungan kausalitas lokal.

Untuk memberikan definisi konsistensi memori dan motivasi untuk meninggalkan SC, saya akan mengutip dari "A Primer on Memory Consistency and Cache Coherence"

Untuk mesin memori bersama, model konsistensi memori mendefinisikan perilaku yang terlihat arsitektural dari sistem memorinya. Kriteria kebenaran untuk satu inti prosesor membagi perilaku antara “satu hasil yang benar"Dan"banyak alternatif yang salah". Ini karena arsitektur prosesor mengamanatkan bahwa eksekusi untaian mengubah status masukan yang diberikan ke dalam status output terdefinisi tunggal, bahkan pada inti yang tidak sesuai pesanan. Model konsistensi memori bersama, bagaimanapun, menyangkut beban dan menyimpan beberapa utas dan biasanya memungkinkan banyak eksekusi yang benar sementara melarang banyak (lebih) yang salah. Kemungkinan beberapa eksekusi yang benar adalah karena ISA memungkinkan beberapa utas untuk dijalankan secara bersamaan, seringkali dengan banyak kemungkinan interleavings hukum instruksi dari utas yang berbeda.

Santai atau lemah model konsistensi memori dimotivasi oleh fakta bahwa sebagian besar urutan memori dalam model yang kuat tidak diperlukan. Jika thread memperbarui sepuluh item data dan kemudian bendera sinkronisasi, pemrogram biasanya tidak peduli jika item data diperbarui secara berurutan dengan memperhatikan satu sama lain tetapi hanya bahwa semua item data diperbarui sebelum bendera diperbarui (biasanya diimplementasikan menggunakan instruksi FENCE ). Model santai mencari untuk menangkap peningkatan fleksibilitas pemesanan ini dan hanya mempertahankan pesanan yang diprogram oleh pemrogram “memerlukan”Untuk mendapatkan kinerja dan kebenaran SC yang lebih tinggi. Sebagai contoh, dalam arsitektur tertentu, penyangga penulisan FIFO digunakan oleh setiap inti untuk menyimpan hasil penyimpanan (yang sudah diputuskan) sebelum menulis hasilnya ke cache. Pengoptimalan ini meningkatkan kinerja tetapi melanggar SC. Buffer tulis menyembunyikan latensi servis miss toko. Karena toko adalah hal biasa, dapat menghindari sebagian besar dari mereka adalah manfaat yang penting. Untuk prosesor single-core, buffer penulisan dapat dibuat secara arsitektur tidak terlihat dengan memastikan bahwa beban untuk mengatasi A mengembalikan nilai dari toko terbaru ke A bahkan jika satu atau beberapa toko ke A berada di buffer tulis. Ini biasanya dilakukan dengan mengabaikan nilai dari toko terbaru ke A ke beban dari A, di mana "paling baru" ditentukan oleh pesanan program, atau dengan mengulur-ulur beban A jika toko ke A berada di buffer tulis . Ketika beberapa core digunakan, masing-masing akan memiliki sendiri melewati buffer menulis. Tanpa buffer penulisan, perangkat kerasnya SC, tetapi dengan buffer penulisan, tidak, membuat buffer penulisan terlihat secara arsitektur dalam prosesor multicore.

Pengurutan ulang toko-toko dapat terjadi jika sebuah inti memiliki buffer penulisan non-FIFO yang memungkinkan toko berangkat dalam urutan yang berbeda dari urutan di mana mereka masuk. Ini mungkin terjadi jika toko pertama gagal dalam cache saat klik kedua atau jika toko kedua dapat bersatu dengan toko sebelumnya (yaitu, sebelum toko pertama). Pengatahaan beban-beban juga dapat terjadi pada core yang terjadwal secara dinamis yang mengeksekusi instruksi dari urutan program. Itu bisa berlaku sama seperti mengatur ulang toko pada inti lain (Bisakah Anda datang dengan contoh interleaving antara dua utas?). Mengurutkan ulang beban sebelumnya dengan penyimpanan yang lebih baru (penataan ulang penyimpanan-beban) dapat menyebabkan banyak perilaku salah, seperti memuat nilai setelah melepaskan kunci yang melindunginya (jika toko adalah operasi buka kunci). Perhatikan bahwa penyetelan ulang beban-simpan juga dapat timbul karena penguraian lokal dalam buffer penulisan FIFO yang biasa diterapkan, bahkan dengan inti yang menjalankan semua instruksi dalam urutan program.

Karena koherensi cache dan konsistensi memori kadang-kadang membingungkan, adalah instruktif untuk juga memiliki kutipan ini:

Tidak seperti konsistensi, koherensi cache tidak terlihat oleh perangkat lunak atau tidak diperlukan. Koherensi berusaha membuat cache sistem memori bersama sebagai fungsional yang tidak terlihat sebagai cache dalam sistem inti tunggal. Koherensi yang benar memastikan bahwa programmer tidak dapat menentukan apakah dan di mana sistem memiliki cache dengan menganalisis hasil dari beban dan toko. Ini karena koherensi yang benar memastikan bahwa cache tidak pernah memungkinkan baru atau berbeda fungsional perilaku (programmer mungkin masih dapat menyimpulkan kemungkinan menggunakan struktur cache waktu informasi). Tujuan utama dari protokol koherensi cache adalah mempertahankan invariant single-writer-multiple-readers (SWMR) untuk setiap lokasi memori.   Perbedaan penting antara koherensi dan konsistensi adalah koherensi yang ditentukan pada a basis lokasi per-memori, sedangkan konsistensi ditentukan sehubungan dengan semua lokasi memori.

Melanjutkan gambaran mental kami, invariant SWMR sesuai dengan persyaratan fisik yang paling banyak terdapat satu partikel yang terletak di salah satu lokasi tetapi dapat ada sejumlah pengamat yang tidak terbatas di lokasi mana pun.


279
2017-08-29 20:42



Ini sekarang adalah pertanyaan yang sudah berumur beberapa tahun, tetapi menjadi sangat populer, ada baiknya menyebutkan sumber daya yang fantastis untuk belajar tentang model memori C ++ 11. Saya tidak melihat gunanya merangkum ceramahnya untuk membuat ini jawaban lengkap lainnya, tetapi mengingat ini adalah orang yang benar-benar menulis standar, saya pikir itu baik layak menonton pembicaraan.

Herb Sutter memiliki pembicaraan selama tiga jam tentang model memori C ++ 11 berjudul "atomic <> Weapons", tersedia di situs Channel9 - Bagian 1 dan bagian 2. Pembicaraan ini cukup teknis, dan mencakup topik-topik berikut:

  1. Optimasi, Balapan, dan Model Memori
  2. Memesan - Apa: Dapatkan dan Lepaskan
  3. Memesan - Bagaimana: Muteks, Atom, dan / atau Pagar
  4. Batasan lain pada Compiler dan Hardware
  5. Kode Gen & Kinerja: x86 / x64, IA64, POWER, ARM
  6. Atom yang Santai

Pembicaraan tidak menguraikan API, tetapi lebih pada alasan, latar belakang, di bawah kap mesin dan di belakang layar (apakah Anda tahu semantik santai ditambahkan ke standar hanya karena POWER dan ARM tidak mendukung beban yang disinkronkan secara efisien?).


79
2017-12-20 13:22



Ini berarti bahwa standar sekarang mendefinisikan multi-threading, dan itu mendefinisikan apa yang terjadi dalam konteks beberapa utas. Tentu saja, orang menggunakan berbagai penerapan, tetapi itu seperti bertanya mengapa kita harus memiliki std::string ketika kita semua bisa menggunakan home-rolled string kelas.

Ketika Anda berbicara tentang POSIX thread atau Windows thread, maka ini adalah sedikit ilusi karena sebenarnya Anda berbicara tentang benang x86, karena ini adalah fungsi perangkat keras untuk dijalankan secara bersamaan. Model memori C ++ 0x membuat garansi, apakah Anda menggunakan x86, atau ARM, atau MIPS, atau apa pun yang bisa Anda hasilkan.


67
2018-06-11 23:42



Untuk bahasa yang tidak menentukan model memori, Anda menulis kode untuk bahasa tersebut dan model memori yang ditentukan oleh arsitektur prosesor. Prosesor dapat memilih untuk memesan ulang akses memori untuk kinerja. Begitu, jika program Anda memiliki ras data (perlombaan data adalah ketika mungkin untuk beberapa core / hyper-threads untuk mengakses memori yang sama secara bersamaan) maka program Anda tidak cross platform karena ketergantungannya pada model memori prosesor. Anda dapat merujuk ke manual perangkat lunak Intel atau AMD untuk mencari tahu bagaimana prosesor dapat mengatur ulang akses memori.

Sangat penting, kunci (dan semantik konkurensi dengan penguncian) biasanya diimplementasikan dengan cara lintas platform ... Jadi jika Anda menggunakan kunci standar dalam program multithread tanpa data, maka Anda tidak perlu khawatir tentang model memori lintas platform.

Menariknya, kompiler Microsoft untuk C ++ telah memperoleh / merilis semantik untuk volatile yang merupakan ekstensi C ++ untuk menangani kurangnya model memori di C ++ http://msdn.microsoft.com/en-us/library/12a04hfd(v=vs.80).aspx. Namun, mengingat bahwa Windows hanya berjalan di x86 / x64, itu tidak banyak bicara (model memori Intel dan AMD membuatnya mudah dan efisien untuk menerapkan memperoleh / merilis semantik dalam bahasa).


49
2017-07-26 04:27



Jika Anda menggunakan mutex untuk melindungi semua data Anda, Anda seharusnya tidak perlu khawatir. Mutexes selalu menyediakan jaminan pemesanan dan visibilitas yang memadai.

Sekarang, jika Anda menggunakan atom, atau algoritma bebas-lock, Anda perlu memikirkan model memori. Model memori menggambarkan secara tepat kapan atomik memberikan jaminan pemesanan dan visibilitas, dan memberikan pagar portabel untuk jaminan kode-tangan.

Sebelumnya, atom akan dilakukan menggunakan intrinsik kompilator, atau beberapa perpustakaan tingkat yang lebih tinggi. Pagar akan dilakukan menggunakan instruksi spesifik CPU (hambatan memori).


22
2018-06-11 23:49