Pertanyaan C ++ 11 implementasi lambda dan model memori


Saya ingin beberapa informasi tentang bagaimana benar berpikir tentang C + + 11 penutupan dan std::function dalam hal bagaimana mereka diimplementasikan dan bagaimana memori ditangani.

Meskipun saya tidak percaya pada pengoptimalan prematur, saya memiliki kebiasaan untuk mempertimbangkan dengan cermat dampak kinerja pilihan saya saat menulis kode baru. Saya juga melakukan cukup banyak pemrograman real-time, mis. pada mikrokontroler dan untuk sistem audio, di mana alokasi memori non-deterministik / jeda-jeda dealokasi harus dihindari.

Oleh karena itu saya ingin mengembangkan pemahaman yang lebih baik tentang kapan menggunakan atau tidak menggunakan C ++ lambdas.

Pemahaman saya saat ini adalah bahwa lambda tanpa penutupan yang ditangkap persis seperti callback C. Namun, ketika lingkungan ditangkap baik oleh nilai atau dengan referensi, objek anonim dibuat di stack. Ketika nilai-penutupan harus dikembalikan dari suatu fungsi, seseorang membungkusnya std::function. Apa yang terjadi pada memori penutupan dalam kasus ini? Apakah disalin dari tumpukan ke tumpukan? Apakah itu dibebaskan kapanpun std::function dibebaskan, yaitu, apakah referensi-dihitung seperti a std::shared_ptr?

Saya membayangkan bahwa dalam sistem real-time saya dapat mengatur rantai fungsi lambda, melewati B sebagai argumen kelanjutan A, sehingga pipa pengolahan A->B dibuat. Dalam hal ini, penutupan A dan B akan dialokasikan sekali. Meskipun saya tidak yakin apakah ini akan dialokasikan pada tumpukan atau tumpukan. Namun secara umum ini tampaknya aman untuk digunakan dalam sistem real-time. Di sisi lain jika B membangun beberapa fungsi lambda C, yang kembali, maka memori untuk C akan dialokasikan dan dialokasi berulang kali, yang tidak akan diterima untuk penggunaan waktu nyata.

Dalam pseudo-code, sebuah loop DSP, yang saya pikir akan aman secara real-time. Saya ingin melakukan pemrosesan blok A dan kemudian B, di mana A memanggil argumennya. Kedua fungsi ini kembali std::function benda, jadi f akan menjadi std::function objek, di mana lingkungannya disimpan di heap:

auto f = A(B);  // A returns a function which calls B
                // Memory for the function returned by A is on the heap?
                // Note that A and B may maintain a state
                // via mutable value-closure!
for (t=0; t<1000; t++) {
    y = f(t)
}

Dan yang saya pikir mungkin buruk untuk digunakan dalam kode waktu nyata:

for (t=0; t<1000; t++) {
    y = A(B)(t);
}

Dan satu di mana saya pikir memori stack kemungkinan digunakan untuk penutupan:

freq = 220;
A = 2;
for (t=0; t<1000; t++) {
    y = [=](int t){ return sin(t*freq)*A; }
}

Dalam kasus terakhir, penutupan dibuat pada setiap iterasi loop, tetapi tidak seperti contoh sebelumnya yang murah karena sama seperti pemanggilan fungsi, tidak ada alokasi tumpukan yang dibuat. Selain itu, saya bertanya-tanya apakah compiler bisa "mengangkat" penutupan dan membuat inlining optimisations.

Apakah ini benar? Terima kasih.


75
2017-08-30 17:50


asal


Jawaban:


Pemahaman saya saat ini adalah bahwa lambda tanpa penutupan yang ditangkap persis seperti callback C. Namun, ketika lingkungan ditangkap baik oleh nilai atau dengan referensi, objek anonim dibuat di stack.

Tidak; ini selalu objek C ++ dengan tipe tidak dikenal, dibuat di stack. Lambda yang tidak bisa menangkap dikonversi menjadi fungsi pointer (meskipun apakah itu cocok untuk konvensi pemanggilan C tergantung implementasi), tetapi itu tidak berarti aku s sebuah penunjuk fungsi.

Ketika nilai-penutupan harus dikembalikan dari suatu fungsi, seseorang membungkusnya dalam std :: function. Apa yang terjadi pada memori penutupan dalam kasus ini?

Lambda bukanlah sesuatu yang istimewa di C ++ 11. Ini objek seperti objek lain. Ekspresi lambda menghasilkan sementara, yang dapat digunakan untuk menginisialisasi variabel pada tumpukan:

auto lamb = []() {return 5;};

lamb adalah objek stack. Ia memiliki konstruktor dan destruktor. Dan itu akan mengikuti semua aturan C ++ untuk itu. Tipe dari lamb akan berisi nilai / referensi yang ditangkap; mereka akan menjadi anggota objek itu, sama seperti anggota objek lain dari jenis lainnya.

Anda bisa memberikannya ke a std::function:

auto func_lamb = std::function<int()>(lamb);

Dalam hal ini, ia akan mendapatkan salinan dari nilai lamb. Jika lamb telah menangkap apa pun dengan nilai, akan ada dua salinan dari nilai-nilai itu; satu dalam lamb, dan satu lagi func_lamb.

Ketika ruang lingkup saat ini berakhir, func_lamb akan dihancurkan, diikuti oleh lamb, sesuai aturan pembersihan variabel tumpukan.

Anda dapat dengan mudah mengalokasikan satu di heap:

auto func_lamb_ptr = new std::function<int()>(lamb);

Tepat dimana memori untuk isi a std::function pergi tergantung pada implementasi, tetapi jenis-penghapusan yang digunakan oleh std::function umumnya membutuhkan setidaknya satu alokasi memori. Ini sebabnya std::functionKonstruktor dapat mengambil alokator.

Apakah itu dibebaskan kapan saja std :: function dibebaskan, yaitu, apakah itu referensi-dihitung seperti std :: shared_ptr?

std::function menyimpan a salinan dari isinya. Seperti hampir setiap perpustakaan standar tipe C ++, function menggunakan semantik nilai. Dengan demikian, itu dapat diterima; saat disalin, baru function objek benar-benar terpisah. Ini juga dapat dipindahkan, sehingga alokasi internal dapat ditransfer secara tepat tanpa perlu mengalokasikan dan menyalin lebih banyak.

Dengan demikian tidak diperlukan penghitungan referensi.

Segala sesuatu yang Anda nyatakan benar, dengan asumsi bahwa "alokasi memori" sama dengan "buruk digunakan dalam kode waktu-nyata".


82
2017-08-30 18:43