Pada artikel ini, saya ingin menunjukkan contoh sederhana bekerja dengan komponen dari namespace pmr dan ide dasar yang mendasari pengalokasi polimorfik.
Ide utama dari pengalokasi polimorfik yang diperkenalkan di c ++ 17 adalah untuk meningkatkan pengalokasi standar yang diimplementasikan berdasarkan polimorfisme statis atau dengan kata lain templat. Mereka jauh lebih mudah digunakan daripada pengalokasi standar, selain itu, mereka memungkinkan Anda mempertahankan jenis penampung saat menggunakan pengalokasi yang berbeda dan, oleh karena itu, mengubah pengalokasi pada waktu proses.
Jika Anda ingin
std::vectordengan pengalokasi memori tertentu, Anda dapat menggunakan parameter template Allocator:
auto my_vector = std::vector<int, my_allocator>();
Tetapi ada masalah - vektor ini tidak berjenis sama dengan vektor dengan pengalokasi berbeda, termasuk yang ditentukan secara default.
Penampung seperti itu tidak dapat diteruskan ke fungsi yang membutuhkan vektor dengan penampung default, dan dua vektor dengan jenis pengalokasi berbeda juga tidak dapat diberikan ke variabel yang sama, misalnya:
auto my_vector = std::vector<int, my_allocator>();
auto my_vector2 = std::vector<int, other_allocator>();
auto vec = my_vector; // ok
vec = my_vector2; // error
Pengalokasi polimorfik berisi penunjuk ke antarmuka
memory_resourcesehingga dapat menggunakan pengiriman dinamis.
Untuk mengubah strategi bekerja dengan memori, itu cukup untuk mengganti instance
memory_resource, menjaga jenis pengalokasi. Ini juga dapat dilakukan saat runtime. Jika tidak, pengalokasi polimorfik bekerja sesuai dengan aturan yang sama seperti aturan standar.
Tipe data spesifik yang digunakan oleh pengalokasi baru ada di namespace
std::pmr. Ada juga spesialisasi templat wadah standar yang dapat bekerja dengan pengalokasi polimorfik.
Salah satu masalah utama saat ini adalah ketidakcocokan versi baru penampung
std::pmrdengan analog dari std.
Komponen utama std::pmr:
std::pmr::memory_resource— , .- :
virtual void* do_allocate(std::size_t bytes, std::size_t alignment),virtual void do_deallocate(void* p, std::size_t bytes, std::size_t alignment)virtual bool do_is_equal(const std::pmr::memory_resource& other) const noexcept.
std::pmr::polymorphic_allocator— ,memory_resource.new_delete_resource()null_memory_resource()«»- :
synchronized_pool_resourceunsynchronized_pool_resourcemonotonic_buffer_resource
- ,
std::pmr::vector,std::pmr::string,std::pmr::map. , . -
memory_resource:
memory_resource* new_delete_resource(), memory_resource, new delete .memory_resource* null_memory_resource()
Fungsi bebas mengembalikan sebuah pointermemory_resourceyang akan memunculkan pengecualianstd::bad_allocpada setiap upaya alokasi.
Ini bisa berguna untuk memastikan bahwa objek tidak mengalokasikan memori di heap atau untuk tujuan pengujian.
class synchronized_pool_resource : public std::pmr::memory_resource
Implementasi memory_resource tujuan umum yang aman untuk thread terdiri dari kumpulan kumpulan dengan ukuran blok memori yang berbeda.
Setiap kumpulan adalah kumpulan potongan memori dengan ukuran yang sama.class unsynchronized_pool_resource : public std::pmr::memory_resource
Versi berulir tunggalsynchronized_pool_resource.class monotonic_buffer_resource : public std::pmr::memory_resource
Utas tunggal, cepat,memory_resourcetujuan khusus mengambil memori dari buffer yang dialokasikan sebelumnya, tetapi tidak membebaskannya, artinya, hanya dapat berkembang.
Contoh penggunaan
monotonic_buffer_resourcedan pmr::vector:
#include <iostream>
#include <memory_resource> // pmr core types
#include <vector> // pmr::vector
#include <string> // pmr::string
int main() {
char buffer[64] = {}; // a small buffer on the stack
std::fill_n(std::begin(buffer), std::size(buffer) - 1, '_');
std::cout << buffer << '\n';
std::pmr::monotonic_buffer_resource pool{std::data(buffer), std::size(buffer)};
std::pmr::vector<char> vec{ &pool };
for (char ch = 'a'; ch <= 'z'; ++ch)
vec.push_back(ch);
std::cout << buffer << '\n';
}
Keluaran program:
_______________________________________________________________
aababcdabcdefghabcdefghijklmnopabcdefghijklmnopqrstuvwxyz______
Dalam contoh di atas, kami menggunakan
monotonic_buffer_resourceinisialisasi dengan buffer yang dialokasikan di stack. Dengan menggunakan pointer ke buffer ini, kita dapat dengan mudah menampilkan isi memori.
Vektor mengambil memori dari pool, yang sangat cepat, karena berada di stack, jika kehabisan memori, ia memintanya menggunakan operator global
new. Contoh ini menunjukkan implementasi vektor ketika mencoba memasukkan lebih dari jumlah elemen yang dicadangkan. Dalam hal ini, monotonic_buffermemori lama tidak dibebaskan, tetapi hanya bertambah.
Anda dapat, tentu saja, memanggil
reserve()vektor untuk meminimalkan realokasi, tetapi tujuan dari contoh ini justru untuk mendemonstrasikan bagaimana hal itu berubah monotonic_buffer_resourcesaat container mengembang.
Penyimpanan pmr::string
Bagaimana jika kita ingin menyimpan string
pmr::vector?
Fitur penting adalah jika objek dalam wadah juga menggunakan pengalokasi polimorfik, maka mereka meminta pengalokasi penampung induk untuk mengelola memori.
Jika Anda ingin mengambil keuntungan dari fitur ini, Anda harus menggunakan
std::pmr::stringgantinya std::string.
Pertimbangkan contoh dengan buffer pra-dialokasikan pada stack, yang kita akan lulus sebagai
memory_resourceuntuk std::pmr::vector std::pmr::string:
#include <iostream>
#include <memory_resource> // pmr core types
#include <vector> // pmr::vector
#include <string> // pmr::string
int main() {
std::cout << "sizeof(std::string): " << sizeof(std::string) << '\n';
std::cout << "sizeof(std::pmr::string): " << sizeof(std::pmr::string) << '\n';
char buffer[256] = {}; // a small buffer on the stack
std::fill_n(std::begin(buffer), std::size(buffer) - 1, '_');
const auto BufferPrinter = [](std::string_view buf, std::string_view title) {
std::cout << title << ":\n";
for (auto& ch : buf) {
std::cout << (ch >= ' ' ? ch : '#');
}
std::cout << '\n';
};
BufferPrinter(buffer, "zeroed buffer");
std::pmr::monotonic_buffer_resource pool{std::data(buffer), std::size(buffer)};
std::pmr::vector<std::pmr::string> vec{ &pool };
vec.reserve(5);
vec.push_back("Hello World");
vec.push_back("One Two Three");
BufferPrinter(std::string_view(buffer, std::size(buffer)), "after two short strings");
vec.emplace_back("This is a longer string");
BufferPrinter(std::string_view(buffer, std::size(buffer)), "after longer string strings");
vec.push_back("Four Five Six");
BufferPrinter(std::string_view(buffer, std::size(buffer)), "after the last string");
}
Keluaran program:
sizeof(std::string): 32
sizeof(std::pmr::string): 40
zeroed buffer:
_______________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________
after two short strings:
#m### ###n### ##########Hello World######m### ##@n### ##########One Two Three###_______________________________________________________________________________________________________________________________________________________________________________#
after longer string strings:
#m### ###n### ##########Hello World######m### ##@n### ##########One Two Three####m### ###n### ##################________________________________________________________________________________________This is a longer string#_______________________________#
after the last string:
#m### ###n### ##########Hello World######m### ##@n### ##########One Two Three####m### ###n### ##################________#m### ###n### ##########Four Five Six###________________________________________This is a longer string#_______________________________#
Poin utama yang perlu diperhatikan dalam contoh ini:
- Ukurannya
pmr::stringlebih besar daristd::string. Hal ini disebabkan oleh fakta bahwa pointer kememory_resource; - Kami mencadangkan vektor untuk 5 elemen, jadi tidak ada realokasi yang terjadi saat menambahkan 4.
- 2 baris pertama cukup pendek untuk blok memori vektor, jadi tidak ada alokasi memori tambahan yang terjadi.
- Baris ketiga lebih panjang dan membutuhkan potongan memori terpisah di dalam buffer kami, dan hanya penunjuk ke blok ini yang disimpan dalam vektor.
- Seperti yang Anda lihat dari output, "Ini adalah string yang lebih panjang" terletak hampir di ujung buffer.
- Saat kita memasukkan string pendek lainnya, string itu jatuh kembali ke blok memori vektor
Sebagai perbandingan, mari lakukan eksperimen yang sama dengan
std::stringalih - alihstd::pmr::string
sizeof(std::string): 32
sizeof(std::pmr::string): 40
zeroed buffer:
_______________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________________
after two short strings:
###w# ##########Hello World########w# ##########One Two Three###_______________________________________________________________________________________________________________________________________________________________________________________________#
new 24
after longer string strings:
###w# ##########Hello World########w# ##########One Two Three###0#######################_______________________________________________________________________________________________________________________________________________________________________#
after the last string:
###w# ##########Hello World########w# ##########One Two Three###0#######################________@##w# ##########Four Five Six###_______________________________________________________________________________________________________________________________#
Kali ini, item dalam container mengambil lebih sedikit ruang karena tidak perlu menyimpan pointer ke memory_resource.
String pendek masih disimpan di dalam blok memori vektor, tetapi sekarang string panjang tidak masuk ke buffer kami. Kali ini string panjang dialokasikan menggunakan pengalokasi default dan
penunjuk ke sana ditempatkan di blok memori vektor . Oleh karena itu, kami tidak melihat baris ini di keluaran.
Sekali lagi tentang ekspansi vektor:
Disebutkan bahwa ketika memori di pool habis, pengalokasi memintanya menggunakan operator
new().
Faktanya, ini tidak sepenuhnya benar - memori diminta dari
memory_resource, dikembalikan menggunakan fungsi bebas
std::pmr::memory_resource* get_default_resource()
Secara default, fungsi ini kembali
std::pmr::new_delete_resource(), yang pada gilirannya mengalokasikan memori menggunakan operator new(), tetapi dapat diganti menggunakan fungsi.
std::pmr::memory_resource* set_default_resource(std::pmr::memory_resource* r)
Jadi, mari kita lihat contoh ketika ia
get_default_resourcemengembalikan nilai dengan default.
Perlu diingat bahwa metode
do_allocate()dan do_deallocate()menggunakan argumen "penyelarasan", jadi kami memerlukan versi C ++ 17 new()dengan dukungan penyelarasan:
void* lastAllocatedPtr = nullptr;
size_t lastSize = 0;
void* operator new(std::size_t size, std::align_val_t align) {
#if defined(_WIN32) || defined(__CYGWIN__)
auto ptr = _aligned_malloc(size, static_cast<std::size_t>(align));
#else
auto ptr = aligned_alloc(static_cast<std::size_t>(align), size);
#endif
if (!ptr)
throw std::bad_alloc{};
std::cout << "new: " << size << ", align: "
<< static_cast<std::size_t>(align)
<< ", ptr: " << ptr << '\n';
lastAllocatedPtr = ptr;
lastSize = size;
return ptr;
}
Sekarang mari kita kembali melihat contoh utama:
constexpr auto buf_size = 32;
uint16_t buffer[buf_size] = {}; // a small buffer on the stack
std::fill_n(std::begin(buffer), std::size(buffer) - 1, 0);
std::pmr::monotonic_buffer_resource pool{std::data(buffer), std::size(buffer)*sizeof(uint16_t)};
std::pmr::vector<uint16_t> vec{ &pool };
for (int i = 1; i <= 20; ++i)
vec.push_back(i);
for (int i = 0; i < buf_size; ++i)
std::cout << buffer[i] << " ";
std::cout << std::endl;
auto* bufTemp = (uint16_t *)lastAllocatedPtr;
for (unsigned i = 0; i < lastSize; ++i)
std::cout << bufTemp[i] << " ";
Program mencoba untuk memasukkan 20 angka ke dalam vektor, tetapi mengingat bahwa vektor hanya berkembang, kita membutuhkan lebih banyak ruang daripada di buffer yang dipesan dengan 32 entri.
Oleh karena itu, pada titik tertentu, pengalokasi akan meminta melalui memori
get_default_resource, yang pada gilirannya akan menyebabkan panggilan ke global new().
Keluaran program:
new: 128, align: 16, ptr: 0xc73b20
1 1 2 1 2 3 4 1 2 3 4 5 6 7 8 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 0
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 65535 132 0 0 0 0 0 0 0 144 0 0 0 65 0 0 0 16080 199 0 0 16176 199 0 0 16176 199 0 0 15344 199 0 0 15472 199 0 0 15472 199 0 0 0 0 0 0 145 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
Dilihat dari output ke konsol, buffer yang dialokasikan cukup untuk 16 elemen saja, dan ketika kita memasukkan nomor 17, alokasi baru 128 byte terjadi menggunakan operator
new().
Pada baris ketiga, kita melihat blok memori yang dialokasikan menggunakan operator
new().
Contoh di atas dengan penggantian operator
new()tidak mungkin cocok untuk solusi produk.
Untungnya, tidak ada yang mengganggu kami untuk membuat implementasi antarmuka kami sendiri
memory_resource.
Semua yang kita butuhkan adalah
- mewarisi dari
std::pmr::memory_resource - Menerapkan metode:
do_allocate()do_deallocate()do_is_equal()
- Teruskan implementasi kami ke
memory_resourcecontainer.
Itu saja. Melalui tautan di bawah ini Anda dapat menyaksikan rekaman hari open house, di mana kami menceritakan secara detail tentang program kursus, proses pembelajaran dan menjawab pertanyaan dari calon siswa:
Baca lebih banyak