Rust: Allocator traits dan std :: heap

Dibuat pada 8 Apr 2016  ·  412Komentar  ·  Sumber: rust-lang/rust

📢 Fitur ini memiliki kelompok kerja khusus , harap tujukan komentar dan perhatian ke repo kelompok kerja .

Posting Asli:


Proposal FCP: https://github.com/rust-lang/rust/issues/32838#issuecomment -336957415
Kotak centang FCP: https://github.com/rust-lang/rust/issues/32838#issuecomment -336980230


Masalah pelacakan untuk rust-lang / rfcs # 1398 dan modul std::heap .

  • [x] mendapatkan struct Layout , trait Allocator , dan implementasi default di alloc peti (https://github.com/rust-lang/rust/pull/42313)
  • [x] memutuskan di mana bagian harus ditempatkan (mis. impls default memiliki ketergantungan pada alloc peti, tetapi Layout / Allocator _bisa di libcore ...) (https://github.com/rust-lang/rust/pull/42313)
  • [] perbaiki dari kode sumber: implementasi default audit (dalam Layout untuk kesalahan overflow, (berpotensi beralih ke overflowing_add dan overflowing_mul jika diperlukan).
  • [x] putuskan apakah realloc_in_place harus diganti dengan grow_in_place dan shrink_in_place ( komentar ) (https://github.com/rust-lang/rust/pull/42313)
  • [] tinjau argumen untuk / terhadap jenis kesalahan terkait (lihat subjudul di sini )
  • [] tentukan apa saja persyaratan yang ada pada keselarasan yang diberikan ke fn dealloc . (Lihat diskusi tentang pengalokasi rfc dan pengalokasi global rfc dan sifat Alloc PR .)

    • Apakah diperlukan untuk membatalkan alokasi dengan align yang Anda alokasikan? Kekhawatiran telah dikemukakan bahwa pengalokasi seperti jemalloc tidak memerlukan ini, dan sulit untuk membayangkan pengalokasi yang memerlukan ini. ( diskusi lebih lanjut ). @ruuda dan @rkruppe sepertinya sejauh ini mereka yang paling banyak berpikir tentang hal ini.

  • [] haruskah AllocErr menjadi Error ? ( komentar )
  • [x] Apakah diperlukan untuk melakukan deallokasi dengan ukuran persis yang Anda alokasikan? Dengan bisnis usable_size kami mungkin ingin mengizinkan, misalnya, jika Anda mengalokasikan dengan (size, align) Anda harus membatalkan alokasi dengan ukuran di kisaran size...usable_size(size, align) . Tampaknya jemalloc benar-benar baik-baik saja dengan ini (tidak mengharuskan Anda untuk membatalkan alokasi dengan tepat size Anda alokasikan) dan ini juga akan memungkinkan Vec untuk secara alami memanfaatkan kelebihan kapasitas jemalloc memberikannya saat melakukan alokasi. (meskipun sebenarnya melakukan ini juga agak ortogonal dengan keputusan ini, kami hanya memberdayakan Vec ). Sejauh ini @Gankro memiliki sebagian besar pemikiran tentang hal ini. ( @alexcrichton yakin ini telah diselesaikan di https://github.com/rust-lang/rust/pull/42313 karena definisi "cocok")
  • [] Mirip dengan pertanyaan sebelumnya: Apakah diperlukan untuk deallocate dengan keselarasan yang tepat yang Anda dialokasikan dengan? (Lihat komentar dari 5 Juni 2017 )
  • [x] OSX / alloc_system bermasalah pada keberpihakan besar (misalnya, kesejajaran 1 << 32 ) https://github.com/rust-lang/rust/issues/30170 # 43217
  • [] haruskah Layout menyediakan metode fn stride(&self) ? (Lihat juga https://github.com/rust-lang/rfcs/issues/1397, https://github.com/rust-lang/rust/issues/17027)
  • [x] Allocator::owns sebagai metode? https://github.com/rust-lang/rust/issues/44302

Status std::heap setelah https://github.com/rust-lang/rust/pull/42313 :

pub struct Layout { /* ... */ }

impl Layout {
    pub fn new<T>() -> Self;
    pub fn for_value<T: ?Sized>(t: &T) -> Self;
    pub fn array<T>(n: usize) -> Option<Self>;
    pub fn from_size_align(size: usize, align: usize) -> Option<Layout>;
    pub unsafe fn from_size_align_unchecked(size: usize, align: usize) -> Layout;

    pub fn size(&self) -> usize;
    pub fn align(&self) -> usize;
    pub fn align_to(&self, align: usize) -> Self;
    pub fn padding_needed_for(&self, align: usize) -> usize;
    pub fn repeat(&self, n: usize) -> Option<(Self, usize)>;
    pub fn extend(&self, next: Self) -> Option<(Self, usize)>;
    pub fn repeat_packed(&self, n: usize) -> Option<Self>;
    pub fn extend_packed(&self, next: Self) -> Option<(Self, usize)>;
}

pub enum AllocErr {
    Exhausted { request: Layout },
    Unsupported { details: &'static str },
}

impl AllocErr {
    pub fn invalid_input(details: &'static str) -> Self;
    pub fn is_memory_exhausted(&self) -> bool;
    pub fn is_request_unsupported(&self) -> bool;
    pub fn description(&self) -> &str;
}

pub struct CannotReallocInPlace;

pub struct Excess(pub *mut u8, pub usize);

pub unsafe trait Alloc {
    // required
    unsafe fn alloc(&mut self, layout: Layout) -> Result<*mut u8, AllocErr>;
    unsafe fn dealloc(&mut self, ptr: *mut u8, layout: Layout);

    // provided
    fn oom(&mut self, _: AllocErr) -> !;
    fn usable_size(&self, layout: &Layout) -> (usize, usize);
    unsafe fn realloc(&mut self,
                      ptr: *mut u8,
                      layout: Layout,
                      new_layout: Layout) -> Result<*mut u8, AllocErr>;
    unsafe fn alloc_zeroed(&mut self, layout: Layout) -> Result<*mut u8, AllocErr>;
    unsafe fn alloc_excess(&mut self, layout: Layout) -> Result<Excess, AllocErr>;
    unsafe fn realloc_excess(&mut self,
                             ptr: *mut u8,
                             layout: Layout,
                             new_layout: Layout) -> Result<Excess, AllocErr>;
    unsafe fn grow_in_place(&mut self,
                            ptr: *mut u8,
                            layout: Layout,
                            new_layout: Layout) -> Result<(), CannotReallocInPlace>;
    unsafe fn shrink_in_place(&mut self,
                              ptr: *mut u8,
                              layout: Layout,
                              new_layout: Layout) -> Result<(), CannotReallocInPlace>;

    // convenience
    fn alloc_one<T>(&mut self) -> Result<Unique<T>, AllocErr>
        where Self: Sized;
    unsafe fn dealloc_one<T>(&mut self, ptr: Unique<T>)
        where Self: Sized;
    fn alloc_array<T>(&mut self, n: usize) -> Result<Unique<T>, AllocErr>
        where Self: Sized;
    unsafe fn realloc_array<T>(&mut self,
                               ptr: Unique<T>,
                               n_old: usize,
                               n_new: usize) -> Result<Unique<T>, AllocErr>
        where Self: Sized;
    unsafe fn dealloc_array<T>(&mut self, ptr: Unique<T>, n: usize) -> Result<(), AllocErr>
        where Self: Sized;
}

/// The global default allocator
pub struct Heap;

impl Alloc for Heap {
    // ...
}

impl<'a> Alloc for &'a Heap {
    // ...
}

/// The "system" allocator
pub struct System;

impl Alloc for System {
    // ...
}

impl<'a> Alloc for &'a System {
    // ...
}
B-RFC-approved B-unstable C-tracking-issue Libs-Tracked T-lang T-libs disposition-merge finished-final-comment-period

Komentar yang paling membantu

@alexcrichton Keputusan untuk beralih dari -> Result<*mut u8, AllocErr> menjadi -> *mut void mungkin datang sebagai kejutan yang signifikan bagi orang-orang yang mengikuti perkembangan asli dari pengalokasi RFC.

Saya tidak setuju dengan poin yang Anda buat , tapi tetap saja sepertinya cukup banyak orang yang bersedia untuk hidup dengan "beban berat" dari Result atas kemungkinan peningkatan kehilangan null- periksa nilai yang dikembalikan.

  • Saya mengabaikan masalah efisiensi waktu proses yang diberlakukan oleh ABI karena saya, seperti @alexcrichton , berasumsi bahwa kita dapat mengatasinya melalui trik kompilator.

Adakah cara agar kita bisa meningkatkan visibilitas pada perubahan yang terlambat itu dengan sendirinya?

Satu cara (di luar kepalaku): Ubah tanda tangan sekarang, dalam PR sendiri, di cabang master, sementara Allocator masih tidak stabil. Dan kemudian lihat siapa yang mengeluh di PR (dan siapa yang merayakan!).

  • Apakah ini terlalu berat? Sepertinya itu dengan definisi yang lebih ringan daripada menggabungkan perubahan seperti itu dengan stabilisasi ...

Semua 412 komentar

Sayangnya saya tidak terlalu memperhatikan untuk menyebutkan ini dalam diskusi RFC, tetapi saya pikir realloc_in_place harus diganti dengan dua fungsi, grow_in_place dan shrink_in_place , untuk dua alasan:

  • Saya tidak dapat memikirkan satu kasus penggunaan (singkatnya menerapkan realloc atau realloc_in_place ) di mana tidak diketahui apakah ukuran alokasi meningkat atau menurun. Menggunakan metode yang lebih khusus membuatnya sedikit lebih jelas tentang apa yang sedang terjadi.
  • Jalur kode untuk menumbuhkan dan menyusutkan alokasi cenderung sangat berbeda - pengembangan melibatkan pengujian apakah blok memori yang berdekatan bebas dan mengklaimnya, sementara penyusutan melibatkan pemotongan sub-blok dengan ukuran yang tepat dan membebaskannya. Sementara biaya cabang di dalam realloc_in_place cukup kecil, menggunakan grow dan shrink lebih baik menangkap tugas-tugas berbeda yang perlu dilakukan oleh pengalokasi.

Perhatikan bahwa ini dapat ditambahkan backwards-compatibly di sebelah realloc_in_place , tetapi ini akan membatasi fungsi mana yang secara default akan diimplementasikan dengan yang lainnya.

Untuk konsistensi, realloc mungkin juga ingin dibagi menjadi grow dan split , tetapi satu-satunya keuntungan memiliki fungsi realloc yang dapat diisi berlebihan yang saya ketahui adalah dapat menggunakan opsi peta ulang mmap , yang tidak memiliki perbedaan seperti itu.

Selain itu, saya pikir implementasi default realloc dan realloc_in_place harus sedikit disesuaikan - daripada memeriksa usable_size , realloc harus mencoba dulu realloc_in_place . Pada gilirannya, realloc_in_place seharusnya secara default memeriksa ukuran yang dapat digunakan dan mengembalikan keberhasilan jika terjadi perubahan kecil alih-alih mengembalikan kegagalan secara universal.

Ini membuatnya lebih mudah untuk menghasilkan implementasi berkinerja tinggi dari realloc : yang diperlukan hanyalah meningkatkan realloc_in_place . Namun, kinerja default realloc tidak terganggu, karena pemeriksaan terhadap usable_size masih dilakukan.

Masalah lain: Dokumen untuk fn realloc_in_place mengatakan bahwa jika mengembalikan Ok, maka dijamin bahwa ptr sekarang "cocok" new_layout .

Bagi saya ini menyiratkan bahwa itu harus memeriksa bahwa keselarasan alamat yang diberikan cocok dengan batasan yang tersirat oleh new_layout .

Namun, menurut saya spesifikasi untuk fungsi fn reallocate_inplace yang mendasari menyiratkan bahwa _it_ akan melakukan pemeriksaan seperti itu.

  • Selain itu, tampaknya masuk akal bahwa setiap klien yang menggunakan fn realloc_in_place sendiri akan memastikan bahwa penyelarasan berfungsi (dalam praktiknya saya curiga itu berarti bahwa penyelarasan yang sama diperlukan di mana-mana untuk kasus penggunaan tertentu ...)

Jadi, haruskah implementasi fn realloc_in_place benar-benar dibebani dengan pengecekan bahwa penyelarasan ptr diberikan kompatibel dengan new_layout ? Mungkin lebih baik _dalam kasus ini_ (dari metode yang satu ini) untuk mendorong persyaratan itu kembali ke pemanggil ...

@ lebih baik Anda membuat poin bagus; Saya akan menambahkannya ke daftar periksa yang saya kumpulkan di deskripsi masalah.

(pada titik ini saya menunggu #[may_dangle] dukungan untuk naik kereta ke saluran beta sehingga saya dapat menggunakannya untuk koleksi std sebagai bagian dari integrasi pengalokasi)

Saya baru mengenal Rust, jadi maafkan saya jika ini telah dibahas di tempat lain.

Apakah ada pemikiran tentang bagaimana mendukung pengalokasi khusus objek? Beberapa pengalokasi seperti pengalokasi slab dan magasin terikat ke tipe tertentu, dan melakukan pekerjaan membangun objek baru, meng-cache objek yang dibangun yang telah "dibebaskan" (daripada benar-benar menjatuhkannya), mengembalikan objek cache yang sudah dibuat, dan menjatuhkan objek sebelum membebaskan memori yang mendasari ke pengalokasi yang mendasari bila diperlukan.

Saat ini, proposal ini tidak menyertakan apa pun di sepanjang baris ObjectAllocator<T> , tetapi akan sangat membantu. Secara khusus, saya sedang mengerjakan implementasi lapisan pengalokasi objek majalah (tautan di atas), dan sementara saya dapat memiliki ini hanya membungkus Allocator dan melakukan pekerjaan membangun dan menjatuhkan objek di caching lapisan itu sendiri, alangkah baiknya jika saya juga bisa membungkus pengalokasi objek lain (seperti pengalokasi lempengan) dan benar-benar menjadi lapisan cache generik.

Di manakah tipe atau sifat pengalokasi objek cocok dengan proposal ini? Apakah itu akan ditinggalkan untuk RFC di masa depan? Sesuatu yang lain?

Saya rasa ini belum dibahas.

Anda dapat menulis ObjectAllocator<T> , dan kemudian melakukan impl<T: Allocator, U> ObjectAllocator<U> for T { .. } , sehingga setiap pengalokasi reguler dapat berfungsi sebagai pengalokasi khusus objek untuk semua objek.

Pekerjaan selanjutnya adalah memodifikasi koleksi untuk menggunakan sifat Anda untuk node mereka, bukan pengalokasi biasa (generik) secara langsung.

@pramugari_id

(pada titik ini saya menunggu dukungan # [may_dangle] untuk naik kereta ke saluran beta sehingga saya kemudian dapat menggunakannya untuk koleksi std sebagai bagian dari integrasi pengalokasi)

Saya kira ini telah terjadi?

@ Ericson2314 Ya, menulis milik saya jelas merupakan pilihan untuk tujuan eksperimental, tetapi saya pikir akan ada lebih banyak manfaat untuk itu menjadi standar dalam hal interoperabilitas (misalnya, saya berencana juga menerapkan pengalokasi lempengan, tetapi itu akan menjadi bagus jika pengguna pihak ketiga kode saya dapat menggunakan pengalokasi slab seseorang _else's_ dengan lapisan cache majalah saya). Pertanyaan saya adalah apakah sifat ObjectAllocator<T> atau sesuatu seperti itu layak untuk didiskusikan. Meskipun tampaknya itu yang terbaik untuk RFC yang berbeda? Saya tidak terlalu akrab dengan pedoman tentang berapa banyak yang termasuk dalam satu RFC dan kapan hal-hal termasuk dalam RFC terpisah ...

@tokopedia

Di manakah tipe atau sifat pengalokasi objek cocok dengan proposal ini? Apakah itu akan ditinggalkan untuk RFC di masa depan? Sesuatu yang lain?

Ya, itu akan menjadi RFC lain.

Saya tidak terlalu akrab dengan pedoman tentang berapa banyak yang termasuk dalam satu RFC dan kapan hal-hal termasuk dalam RFC terpisah ...

itu tergantung pada ruang lingkup RFC itu sendiri, yang diputuskan oleh orang yang menulisnya, dan kemudian umpan balik diberikan oleh semua orang.

Tapi sungguh, karena ini adalah masalah pelacakan untuk RFC yang sudah diterima ini, memikirkan tentang ekstensi dan perubahan desain tidak benar-benar untuk utas ini; Anda harus membuka yang baru di atas repo RFC.

@joshlf Ah, saya pikir ObjectAllocator<T> seharusnya menjadi ciri. Maksud saya prototipe sifatnya bukan pengalokasi khusus. Ya, sifat itu akan mendapatkan RFC-nya sendiri seperti yang dikatakan @steveklabnik .


@steveklabnik ya sekarang diskusi akan lebih baik di tempat lain. Namun @joshlf juga mengangkat masalah ini agar tidak mengekspos kesalahan yang sampai sekarang tidak terduga dalam desain API yang diterima tetapi tidak diterapkan. Dalam arti itu cocok dengan posting sebelumnya di utas ini.

@ Ericson2314 Ya, saya pikir itu yang Anda maksud. Saya pikir kita berada di halaman yang sama :)

@eveklabnik Kedengarannya bagus; Saya akan melihat-lihat dengan implementasi saya sendiri dan mengirimkan RFC jika akhirnya tampak seperti ide yang bagus.

@joshlf Saya tidak punya alasan mengapa pengalokasi khusus akan masuk ke kompiler atau pustaka standar. Setelah RFC ini mendarat, Anda dapat dengan mudah memublikasikan peti Anda sendiri yang melakukan alokasi sewenang-wenang (bahkan pengalokasi lengkap seperti jemalloc dapat diimplementasikan secara khusus!).

@alexreg Ini bukan tentang pengalokasi khusus tertentu, melainkan sifat yang menentukan jenis dari semua pengalokasi yang parametrik pada jenis tertentu. Jadi seperti RFC 1398 yang mendefinisikan suatu sifat ( Allocator ) yang merupakan jenis pengalokasi tingkat rendah, saya bertanya tentang sifat ( ObjectAllocator<T> ) yang merupakan jenis pengalokasi mana pun yang mana dapat mengalokasikan / membatalkan alokasi dan membangun / melepas objek dengan tipe T .

@alexreg Lihat poin awal saya tentang menggunakan koleksi perpustakaan standar dengan pengalokasi khusus objek khusus.

Tentu, tapi saya tidak yakin itu akan termasuk dalam perpustakaan standar. Bisa dengan mudah masuk ke peti lain, tanpa kehilangan fungsionalitas atau kegunaan.

Pada 4 Jan 2017, pada 21:59, Joshua Liebow-Feeser [email protected] menulis:

@alexreg https://github.com/alexreg Ini bukan tentang pengalokasi khusus tertentu, melainkan sifat yang menentukan jenis semua pengalokasi yang parametrik pada jenis tertentu. Jadi seperti RFC 1398 yang mendefinisikan suatu sifat (Allocator) yaitu jenis pengalokasi tingkat rendah, saya bertanya tentang suatu sifat (ObjectAllocator) yaitu jenis pengalokasi apa pun yang dapat mengalokasikan / membatalkan alokasi dan membangun / melepaskan objek bertipe T.

-
Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub https://github.com/rust-lang/rust/issues/32838#issuecomment-270499064 , atau nonaktifkan utas https://github.com/notifications/unsubscribe-auth/ AAEF3IhyyPhFgu1EGHr_GM_Evsr0SRzIks5rPBZGgaJpZM4IDYUN .

Saya pikir Anda ingin menggunakan koleksi pustaka standar (nilai alokasi heap apa pun) dengan pengalokasi khusus arbitrer ; yaitu tidak terbatas pada objek khusus.

Pada 4 Jan 2017, pukul 22:01, John Ericson [email protected] menulis:

@alexreg https://github.com/alexreg Lihat poin awal saya tentang menggunakan koleksi perpustakaan standar dengan pengalokasi khusus objek khusus.

-
Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub https://github.com/rust-lang/rust/issues/32838#issuecomment-270499628 , atau nonaktifkan utas https://github.com/notifications/unsubscribe-auth/ AAEF3CrjYIXqcv8Aqvb4VTyPcajJozICks5rPBbOgaJpZM4IDYUN .

Tentu, tapi saya tidak yakin itu akan termasuk dalam perpustakaan standar. Bisa dengan mudah masuk ke peti lain, tanpa kehilangan fungsionalitas atau kegunaan.

Ya, tetapi Anda mungkin ingin beberapa fungsionalitas pustaka standar mengandalkannya (seperti yang disarankan @ Ericson2314 ).

Saya pikir Anda ingin menggunakan koleksi pustaka standar (nilai alokasi heap apa pun) dengan pengalokasi khusus arbitrer ; yaitu tidak terbatas pada objek khusus.

Idealnya Anda ingin keduanya - menerima salah satu jenis pengalokasi. Ada manfaat yang sangat signifikan dalam menggunakan cache khusus objek; misalnya, alokasi pelat dan cache majalah memberikan manfaat kinerja yang sangat signifikan - lihat makalah yang saya tautkan di atas jika Anda penasaran.

Tetapi sifat pengalokasi objek bisa saja merupakan subtrait dari sifat pengalokasi umum. Sesederhana itu, sejauh yang saya ketahui. Tentu, tipe pengalokasi tertentu bisa lebih efisien daripada pengalokasi tujuan umum, tetapi baik kompilator maupun standar benar-benar perlu (atau memang harus) mengetahui tentang ini.

Pada 4 Jan 2017, pukul 22:13, Joshua Liebow-Feeser [email protected] menulis:

Tentu, tapi saya tidak yakin itu akan termasuk dalam perpustakaan standar. Bisa dengan mudah masuk ke peti lain, tanpa kehilangan fungsionalitas atau kegunaan.

Ya, tetapi Anda mungkin ingin beberapa fungsionalitas pustaka standar mengandalkannya (seperti yang disarankan @ Ericson2314 https://github.com/Ericson2314 ).

Saya pikir Anda ingin menggunakan koleksi pustaka standar (nilai apa pun yang dialokasikan heap) dengan pengalokasi khusus arbitrer; yaitu tidak terbatas pada objek khusus.

Idealnya Anda ingin keduanya - menerima salah satu jenis pengalokasi. Ada manfaat yang sangat signifikan dalam menggunakan cache khusus objek; misalnya, alokasi pelat dan cache majalah memberikan manfaat kinerja yang sangat signifikan - lihat makalah yang saya tautkan di atas jika Anda penasaran.

-
Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub https://github.com/rust-lang/rust/issues/32838#issuecomment-270502231 , atau nonaktifkan utas https://github.com/notifications/unsubscribe-auth/ AAEF3L9F9r_0T5evOtt7Es92vw6gBxR9ks5rPBl9gaJpZM4IDYUN .

Tetapi sifat pengalokasi objek bisa saja merupakan subtrait dari sifat pengalokasi umum. Sesederhana itu, sejauh yang saya ketahui. Tentu, tipe pengalokasi tertentu bisa lebih efisien daripada pengalokasi tujuan umum, tetapi baik kompilator maupun standar benar-benar perlu (atau memang harus) mengetahui tentang ini.

Ah, jadi masalahnya adalah semantiknya berbeda. Allocator mengalokasikan dan membebaskan gumpalan byte mentah. ObjectAllocator<T> , di sisi lain, akan mengalokasikan objek yang sudah dibangun dan juga akan bertanggung jawab untuk melepaskan objek-objek ini (termasuk dapat menyimpan objek yang dibangun ke dalam cache yang dapat dibagikan nanti dalam leu untuk membangun objek yang baru dialokasikan , yang mahal). Ciri tersebut akan terlihat seperti ini:

trait ObjectAllocator<T> {
    fn alloc() -> T;
    fn free(t T);
}

Ini tidak kompatibel dengan Allocator , yang metodenya menangani pointer mentah dan tidak memiliki gagasan tentang tipe. Selain itu, dengan Allocator s, pemanggil bertanggung jawab untuk drop objek dibebaskan terlebih dahulu. Ini sangat penting - mengetahui tentang tipe T memungkinkan ObjectAllocator<T> untuk melakukan hal-hal seperti memanggil T 's drop metode, dan sejak free(t) memindahkan t menjadi free , pemanggil _cannot_ menjatuhkan t terlebih dahulu - ini adalah tanggung jawab ObjectAllocator<T> . Pada dasarnya, kedua sifat ini tidak sesuai satu sama lain.

Ah benar, begitu. Saya pikir proposal ini sudah menyertakan sesuatu seperti itu, yaitu pengalokasi "tingkat yang lebih tinggi" di atas tingkat byte. Dalam hal ini, proposal yang sangat adil!

Pada 4 Jan 2017, pukul 22:29, Joshua Liebow-Feeser [email protected] menulis:

Tetapi sifat pengalokasi objek bisa saja merupakan subtrait dari sifat pengalokasi umum. Sesederhana itu, sejauh yang saya ketahui. Tentu, tipe pengalokasi tertentu bisa lebih efisien daripada pengalokasi tujuan umum, tetapi baik kompilator maupun standar benar-benar perlu (atau memang harus) mengetahui tentang ini.

Ah, jadi masalahnya adalah semantiknya berbeda. Allocator mengalokasikan dan membebaskan blob byte mentah. ObjectAllocator, di sisi lain, akan mengalokasikan objek yang sudah dibangun dan juga akan bertanggung jawab untuk melepaskan objek-objek ini (termasuk dapat menyimpan objek yang dibangun ke dalam cache yang dapat dibagikan nanti saat membangun objek yang baru dialokasikan, yang mahal harganya). Ciri tersebut akan terlihat seperti ini:

sifat ObjectAllocator{
fn alokasi () -> T;
fn gratis (t T);
}
Ini tidak kompatibel dengan Allocator, yang metodenya menangani pointer mentah dan tidak memiliki gagasan tipe. Selain itu, dengan Allocators, pemanggil bertanggung jawab untuk menjatuhkan objek yang dibebaskan terlebih dahulu. Ini sangat penting - mengetahui tentang tipe T memungkinkan ObjectAllocatoruntuk melakukan hal-hal seperti memanggil metode drop T, dan karena free (t) memindahkan t ke free, pemanggil tidak dapat menjatuhkan t terlebih dahulu - ini adalah ObjectAllocatortanggung jawab. Pada dasarnya, kedua sifat ini tidak sesuai satu sama lain.

-
Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub https://github.com/rust-lang/rust/issues/32838#issuecomment-270505704 , atau nonaktifkan utas https://github.com/notifications/unsubscribe-auth/ AAEF3GViJBefuk8IWgPauPyL5tV78Fn5ks5rPB08gaJpZM4IDYUN .

@alexreg Ah ya, saya juga berharap begitu :) Oh well - harus menunggu RFC lagi.

Ya, mulailah RFC itu, saya yakin itu akan mendapat banyak dukungan! Dan terima kasih atas klarifikasinya (saya sama sekali tidak mengikuti detail RFC ini).

Pada 5 Jan 2017, pukul 00:53, Joshua Liebow-Feeser [email protected] menulis:

@alexreg https://github.com/alexreg Ah ya, saya juga berharap begitu :) Oh well - harus menunggu RFC lain.

-
Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub https://github.com/rust-lang/rust/issues/32838#issuecomment-270531535 , atau nonaktifkan utas https://github.com/notifications/unsubscribe-auth/ AAEF3MQQeXhTliU5CBsoheBFL26Ee9WUks5rPD8RgaJpZM4IDYUN .

Sebuah peti untuk menguji pengalokasi khusus akan berguna.

Maafkan saya jika saya melewatkan sesuatu yang jelas, tetapi adakah alasan untuk sifat Layout dijelaskan dalam RFC ini untuk tidak menerapkan Copy serta Clone , karena itu hanya POLONG?

Saya tidak bisa memikirkan apapun.

Maaf untuk membicarakan ini sangat terlambat dalam prosesnya, tapi ...

Mungkinkah bermanfaat untuk menambahkan dukungan untuk fungsi seperti dealloc yang bukan metode, melainkan fungsi? Idenya adalah menggunakan penyelarasan untuk dapat menyimpulkan dari penunjuk di mana dalam memori pengalokasi induknya dan dengan demikian dapat membebaskan tanpa memerlukan referensi pengalokasi eksplisit.

Ini bisa menjadi keuntungan besar untuk struktur data yang menggunakan pengalokasi khusus. Ini akan memungkinkan mereka untuk tidak menyimpan referensi ke pengalokasi itu sendiri, tetapi hanya perlu menjadi parametrik pada _type_ pengalokasi untuk dapat memanggil fungsi dealloc . Misalnya, jika Box akhirnya dimodifikasi untuk mendukung pengalokasi khusus, maka itu akan dapat tetap menjadi hanya satu kata (hanya penunjuk) sebagai lawan harus diperluas menjadi dua kata untuk menyimpan referensi ke pengalokasi juga.

Pada catatan terkait, mungkin juga berguna untuk mendukung fungsi non-metode alloc untuk memungkinkan pengalokasi global. Ini akan disusun dengan baik dengan fungsi non-metode dealloc - untuk pengalokasi global, tidak perlu melakukan inferensi penunjuk-ke-pengalokasi apa pun karena hanya akan ada satu contoh statis dari pengalokasi untuk seluruh program.

@joshlf Desain saat ini memungkinkan Anda untuk mendapatkannya dengan hanya membuat pengalokasi Anda menjadi tipe unit (berukuran nol) - yaitu struct MyAlloc; yang kemudian Anda terapkan pada sifat Allocator .
Menyimpan referensi atau tidak sama sekali, selalu , kurang umum daripada menyimpan nilai pengalokasi.

Saya bisa melihat itu benar untuk tipe yang disematkan secara langsung, tetapi bagaimana jika struktur data memutuskan untuk menyimpan referensi? Apakah referensi ke tipe berukuran nol memakan ruang nol? Artinya, jika saya memiliki:

struct Foo()

struct Blah{
    foo: &Foo,
}

Apakah Blah memiliki ukuran nol?

Sebenarnya, meskipun mungkin, Anda mungkin tidak ingin pengalokasi Anda memiliki ukuran nol. Misalnya, Anda mungkin memiliki pengalokasi dengan ukuran bukan nol yang Anda alokasikan _from_, tetapi yang memiliki kemampuan untuk membebaskan objek tanpa mengetahui tentang pengalokasi asli. Ini akan tetap berguna untuk membuat Box hanya mengambil satu kata. Anda akan memiliki sesuatu seperti Box::new_from_allocator yang harus mengambil pengalokasi sebagai argumen - dan ini mungkin pengalokasi berukuran tidak nol - tetapi jika pengalokasi mendukung pembebasan tanpa referensi pengalokasi asli, pengembalian Box<T> dapat menghindari penyimpanan referensi ke pengalokasi yang diteruskan dalam panggilan asli Box::new_from_allocator .

Misalnya, Anda mungkin memiliki pengalokasi dengan ukuran bukan nol yang Anda alokasikan, tapi yang memiliki kemampuan untuk membebaskan objek tanpa mengetahui tentang pengalokasi asli.

Saya ingat lama, dahulu kala mengusulkan memfaktorkan sifat pengalokasi dan deallocator yang terpisah (dengan jenis asosiasi yang menghubungkan keduanya) pada dasarnya untuk alasan ini.

Apakah / haruskah kompilator diizinkan untuk mengoptimalkan alokasi jauh dengan pengalokasi ini?

Apakah / haruskah kompilator diizinkan untuk mengoptimalkan alokasi jauh dengan pengalokasi ini?

@Zoxc Apa maksudmu?

Saya ingat lama, dahulu kala mengusulkan memfaktorkan sifat pengalokasi dan deallocator yang terpisah (dengan jenis asosiasi yang menghubungkan keduanya) pada dasarnya untuk alasan ini.

Untuk anak cucu, izinkan saya mengklarifikasi pernyataan ini (saya berbicara dengan @ Ericson2314 tentangnya secara offline): Idenya adalah bahwa Box bisa menjadi parametrik hanya dengan deallocator. Jadi Anda bisa memiliki implementasi berikut:

trait Allocator {
    type D: Deallocator;

    fn get_deallocator(&self) -> Self::D;
}

trait Deallocator {}

struct Box<T, D: Deallocator> {
    ptr: *mut T,
    d: D,
}

impl<T, D: Deallocator> Box<T, D> {
    fn new_from_allocator<A: Allocator>(x: T, a: A) -> Box<T, A::D> {
        ...
        Box {
            ptr: ptr,
            d: a.get_deallocator()
        }
    }
}

Dengan cara ini, saat memanggil new_from_allocator , jika A::D adalah tipe berukuran nol, maka bidang d dari Box<T, A::D> mengambil ukuran nol, sehingga ukuran yang dihasilkan Box<T, A::D> adalah satu kata.

Apakah ada batas waktu kapan ini akan mendarat? Saya sedang mengerjakan beberapa hal pengalokasi, dan alangkah baiknya jika hal ini ada di sana untuk saya bangun.

Jika ada minat, saya akan dengan senang hati meminjamkan beberapa siklus untuk ini, tetapi saya relatif baru mengenal Rust, jadi itu mungkin hanya menciptakan lebih banyak pekerjaan untuk pengelola dalam hal harus meninjau kode kode pemula. Saya tidak ingin menginjak kaki siapa pun, dan saya tidak ingin membuat lebih banyak pekerjaan untuk orang lain.

Oke, kita baru-baru ini bertemu untuk mengevaluasi status pengalokasi dan menurut saya ada kabar baik untuk ini juga! Sepertinya dukungan belum tersedia di libstd untuk API ini, tetapi semua orang masih senang dengan API tersebut setiap saat!

Satu hal yang kita diskusikan adalah bahwa mengubah semua jenis libstd mungkin sedikit prematur karena kemungkinan masalah inferensi, tetapi terlepas dari itu sepertinya ide yang baik untuk mendapatkan sifat Allocator dan Layout ketik modul std::heap diusulkan untuk eksperimen di tempat lain dalam ekosistem!

@joshlf jika Anda ingin membantu di sini, saya pikir itu akan sangat disambut! Bagian pertama kemungkinan akan mendaratkan tipe / sifat dasar dari RFC ini ke dalam pustaka standar, dan kemudian dari sana kita dapat mulai bereksperimen dan bermain-main dengan koleksi di libstd juga.

@alexcrichton Saya pikir link Anda rusak? Itu menunjuk kembali ke sini.

Satu hal yang kita diskusikan adalah bahwa mengubah semua jenis libstd mungkin agak terlalu dini karena kemungkinan masalah inferensi

Menambahkan sifat adalah langkah pertama yang baik, tetapi tanpa memfaktorkan ulang API yang ada untuk menggunakannya, mereka tidak akan melihat banyak penggunaan. Di https://github.com/rust-lang/rust/issues/27336#issuecomment -300721558 Saya mengusulkan agar kita dapat merefaktor ulang peti di belakang fasad segera, tetapi tambahkan pembungkus tipe baru di std . Menjengkelkan untuk melakukannya, tetapi memungkinkan kita untuk membuat kemajuan.

@alexcrichton Apa yang akan menjadi proses untuk mendapatkan pengalokasi objek? Eksperimen saya sejauh ini (segera dipublikasikan; saya dapat menambahkan Anda ke repo GH pribadi jika Anda penasaran) dan diskusi di sini telah membuat saya percaya bahwa akan ada simetri yang hampir sempurna antara sifat pengalokasi dan objek sifat pengalokasi. Misalnya, Anda akan memiliki sesuatu seperti (Saya mengubah Address menjadi *mut u8 untuk simetri dengan *mut T dari ObjectAllocator<T> ; kita mungkin akan berakhir dengan Address<T> atau sesuatu seperti itu):

unsafe trait Allocator {
    unsafe fn alloc(&mut self, layout: Layout) -> Result<*mut u8, AllocErr>;
    unsafe fn dealloc(&mut self, ptr: *mut u8, layout: Layout);
}
unsafe trait ObjectAllocator<T> {
    unsafe fn alloc(&mut self) -> Result<*mut T, AllocErr>;
    unsafe fn dealloc(&mut self, ptr: *mut T);
}

Jadi, saya pikir bereksperimen dengan pengalokasi dan pengalokasi objek pada saat yang sama dapat bermanfaat. Saya tidak yakin apakah ini tempat yang tepat untuk itu, atau apakah harus ada RFC lain atau, paling tidak, PR terpisah.

Oh, saya bermaksud untuk menautkan di sini yang juga memiliki informasi tentang pengalokasi global. @ Joshlf itu yang kamu pikirkan?

Sepertinya @alexcrichton menginginkan PR yang menyediakan ciri Allocator dan jenis Layout , meskipun tidak diintegrasikan ke dalam koleksi apa pun di libstd .

Jika saya memahaminya dengan benar, maka saya bisa memasang PR untuk itu. Saya tidak melakukannya karena saya terus berusaha untuk mendapatkan setidaknya integrasi dengan RawVec dan Vec prototipe. (Pada titik ini saya telah menyelesaikan RawVec , tetapi Vec sedikit lebih menantang karena banyak struktur lain yang membangunnya, seperti Drain dan IntoIter dll ...)

sebenarnya, cabang saya saat ini sepertinya benar-benar dapat dibangun (dan satu tes untuk integrasi dengan RawVec lulus), jadi saya melanjutkan dan mempostingnya: # 42313

@hawkw bertanya:

Maafkan saya jika saya melewatkan sesuatu yang jelas, tetapi apakah ada alasan untuk sifat Tata Letak yang dijelaskan dalam RFC ini untuk tidak menerapkan Salin serta Klon, karena ini hanya POD?

Alasan saya membuat Layout hanya mengimplementasikan Clone dan bukan Copy adalah karena saya ingin membiarkan kemungkinan menambahkan lebih banyak struktur ke tipe Layout . Secara khusus, saya masih tertarik untuk mencoba mencoba mencoba Layout untuk melacak semua jenis struktur yang digunakan untuk membangunnya (misalnya 16-array struct { x: u8, y: [char; 215] } ), sehingga pengalokasi akan memiliki opsi untuk mengekspos rutinitas instrumentasi yang melaporkan dari jenis apa konten mereka saat ini dibuat.

Ini hampir pasti harus menjadi fitur opsional, yaitu tampaknya gelombang pasang tegas menentang memaksa pengembang untuk menggunakan konstruktor Layout diperkaya tipe. jadi setiap instrumentasi dari formulir ini perlu menyertakan sesuatu seperti kategori "blok memori tidak dikenal" untuk menangani alokasi yang tidak memiliki informasi tipe.

Namun demikian, fitur seperti ini adalah alasan utama mengapa saya tidak memilih untuk menghasilkan Layout implement Copy ; Saya pada dasarnya membayangkan bahwa implementasi Copy akan menjadi kendala prematur pada Layout itu sendiri.

@alexcrichton @pnkfelix

Sepertinya @pnkfelix sudah membahas ini, dan PR mendapatkan daya tarik, jadi mari kita lakukan itu. Saya sedang memeriksanya dan memberi komentar sekarang, dan itu tampak hebat!

Saat ini tanda tangan untuk Allocator::oom adalah:

    fn oom(&mut self, _: AllocErr) -> ! {
        unsafe { ::core::intrinsics::abort() }
    }

Namun, menjadi perhatian saya , bahwa Gecko setidaknya suka mengetahui ukuran alokasi juga di OOM. Kami mungkin ingin mempertimbangkan ini saat menstabilkan untuk mungkin menambahkan konteks seperti Option<Layout> untuk alasan OOM terjadi.

@alexrichard
Mungkinkah layak memiliki beberapa varian oom_xxx atau enum dari tipe argumen yang berbeda? Ada beberapa tanda tangan berbeda untuk metode yang bisa gagal (mis., alloc mengambil tata letak, realloc mengambil penunjuk, tata letak asli, dan tata letak baru, dll), dan mungkin ada ada kasus di mana metode seperti oom ingin mengetahui semuanya.

@ Joshlf itu benar, ya, tapi saya tidak yakin apakah itu berguna. Saya tidak ingin hanya menambahkan fitur karena kami bisa, mereka harus terus termotivasi dengan baik.


Poin untuk stabilisasi di sini juga "menentukan persyaratan apa yang ada pada penyelarasan yang diberikan ke fn dealloc ", dan implementasi dealloc pada Windows saat ini menggunakan align untuk menentukan cara benar gratis. @ruuda Anda mungkin tertarik dengan fakta ini.

Poin untuk stabilisasi di sini juga "menentukan persyaratan apa yang ada pada penyelarasan yang diberikan ke fn dealloc ", dan implementasi dealloc pada Windows saat ini menggunakan align untuk menentukan cara benar gratis.

Ya, saya rasa beginilah awalnya saya mengalami hal ini; program saya macet di Windows karena ini. Karena HeapAlloc tidak memberikan jaminan penyelarasan, allocate mengalokasikan wilayah yang lebih besar dan menyimpan penunjuk asli di header, tetapi sebagai pengoptimalan, hal ini dihindari jika persyaratan penyelarasan tetap terpenuhi. Saya ingin tahu apakah ada cara untuk mengubah HeapAlloc menjadi pengalokasi sadar-penyelarasan yang tidak memerlukan penyelarasan secara gratis, tanpa kehilangan pengoptimalan ini.

@bayu_joo

Karena HeapAlloc tidak menjamin keselarasan

Itu memang memberikan jaminan penyelarasan minimum 8 byte untuk 32bit atau 16 byte untuk 64bit, itu hanya tidak memberikan cara apa pun untuk menjamin penyelarasan lebih tinggi dari itu.

_aligned_malloc disediakan oleh CRT di Windows dapat memberikan alokasi penyelarasan yang lebih tinggi, tetapi terutama harus dipasangkan dengan _aligned_free , menggunakan free adalah ilegal. Jadi jika Anda tidak tahu apakah alokasi dilakukan melalui malloc atau _aligned_malloc maka Anda terjebak dalam teka-teki yang sama bahwa alloc_system ada di Windows jika Anda tidak melakukannya ' tidak tahu keselarasan untuk deallocate . CRT tidak menyediakan fungsi standar aligned_alloc yang dapat dipasangkan dengan free , jadi bahkan Microsoft belum dapat memecahkan masalah ini. (Meskipun ini adalah fungsi C11 dan Microsoft tidak mendukung C11 jadi itu argumen yang lemah.)

Perhatikan bahwa deallocate hanya peduli tentang penyelarasan untuk mengetahui apakah itu terlalu selaras, nilai sebenarnya itu sendiri tidak relevan. Jika Anda menginginkan deallocate yang benar-benar independen penyelarasan, Anda dapat memperlakukan semua alokasi sebagai terlalu selaras, tetapi Anda akan membuang banyak memori pada alokasi kecil.

@alexcrichton menulis :

Saat ini tanda tangan untuk Allocator::oom adalah:

    fn oom(&mut self, _: AllocErr) -> ! {
        unsafe { ::core::intrinsics::abort() }
    }

Namun, menjadi perhatian saya , bahwa Gecko setidaknya suka mengetahui ukuran alokasi juga di OOM. Kami mungkin ingin mempertimbangkan ini saat menstabilkan untuk mungkin menambahkan konteks seperti Option<Layout> untuk alasan OOM terjadi.

AllocErr sudah membawa Layout dalam varian AllocErr::Exhausted . Kita juga bisa menambahkan Layout ke varian AllocErr::Unsupported , yang menurut saya paling sederhana dalam hal harapan klien. (Itu memang memiliki kelemahan dengan konyol meningkatkan sisi AllocErr enum itu sendiri, tapi mungkin kita tidak perlu khawatir tentang itu ...)

Oh, saya rasa hanya itu yang dibutuhkan, terima kasih atas koreksi @pnkfelix!

Saya akan mulai menggunakan kembali masalah ini untuk masalah pelacakan sebesar std::heap secara umum seperti yang akan terjadi setelah https://github.com/rust-lang/rust/pull/42727 mendarat. Saya akan menutup beberapa masalah terkait lainnya yang mendukung ini.

Apakah ada masalah pelacakan untuk mengonversi koleksi? Sekarang setelah PR digabungkan, saya ingin

  • Diskusikan jenis kesalahan terkait
  • Diskusikan mengonversi koleksi untuk menggunakan pengalokasi lokal apa pun, (terutama memanfaatkan jenis kesalahan terkait)

Saya telah membuka https://github.com/rust-lang/rust/issues/42774 untuk melacak integrasi Alloc ke dalam koleksi std. Dengan diskusi historis dalam tim libs yang kemungkinan berada pada jalur stabilisasi yang terpisah dari kelulusan awal modul std::heap .

Saat meninjau masalah terkait pengalokasi, saya juga menemukan https://github.com/rust-lang/rust/issues/30170 yang @pnkfelix beberapa waktu lalu. Sepertinya pengalokasi sistem OSX bermasalah dengan penyelarasan tinggi dan saat menjalankan program itu dengan jemalloc, setidaknya tersegfault selama deallocation di Linux. Layak dipertimbangkan selama stabilisasi!

Saya telah membuka # 42794 sebagai tempat untuk mendiskusikan pertanyaan spesifik apakah alokasi berukuran nol perlu sesuai dengan penyelarasan yang diminta.

(oh tunggu, alokasi berukuran nol ilegal di pengalokasi pengguna!)

Sejak fungsi alloc::heap::allocate dan teman-teman sekarang hilang di Nightly, saya telah memperbarui Servo untuk menggunakan API baru ini. Ini adalah bagian dari perbedaan:

-use alloc::heap;
+use alloc::allocator::{Alloc, Layout};
+use alloc::heap::Heap;
-        let ptr = heap::allocate(req_size as usize, FT_ALIGNMENT) as *mut c_void;
+        let layout = Layout::from_size_align(req_size as usize, FT_ALIGNMENT).unwrap();
+        let ptr = Heap.alloc(layout).unwrap() as *mut c_void;

Saya merasa ergonominya tidak bagus. Kami beralih dari mengimpor satu item menjadi mengimpor tiga dari dua modul yang berbeda.

  • Apakah masuk akal memiliki metode praktis untuk allocator.alloc(Layout::from_size_align(…)) ?
  • Apakah masuk akal untuk membuat metode <Heap as Alloc>::_ tersedia sebagai fungsi gratis atau metode bawaan? (Untuk memiliki satu item lebih sedikit untuk diimpor, sifat Alloc .)

Atau, dapatkah sifat Alloc berada di awal atau terlalu ceruk kasus penggunaan?

@SimonSapin IMO tidak banyak gunanya mengoptimalkan ergonomi API tingkat rendah seperti itu.

@Septianjoko_

Saya merasa ergonominya tidak bagus. Kami beralih dari mengimpor satu item menjadi mengimpor tiga dari dua modul yang berbeda.

Saya memiliki perasaan yang sama persis dengan basis kode saya - sekarang cukup kikuk.

Apakah masuk akal memiliki metode praktis untuk allocator.alloc(Layout::from_size_align(…))?

Apakah yang Anda maksud dalam sifat Alloc , atau hanya untuk Heap ? Satu hal yang perlu dipertimbangkan di sini adalah bahwa sekarang ada kondisi kesalahan ketiga: Layout::from_size_align mengembalikan Option , sehingga dapat mengembalikan None selain kesalahan normal yang bisa Anda dapatkan saat mengalokasikan .

Atau, dapatkah sifat Alloc berada di awal atau terlalu ceruk kasus penggunaan?

IMO tidak ada gunanya mengoptimalkan ergonomi API tingkat rendah seperti itu.

Saya setuju bahwa ini mungkin level yang terlalu rendah untuk dimasukkan ke dalam pendahuluan, tetapi saya masih berpikir bahwa ada nilai dalam mengoptimalkan ergonomi (egois, setidaknya - itu adalah refaktor yang sangat mengganggu 😝).

@SimonSapin Apakah Anda tidak menangani OOM sebelumnya? Juga di std ketiga jenis tersedia dalam modul std::heap (mereka seharusnya berada dalam satu modul). Juga apakah Anda tidak menangani kasus ukuran yang meluap sebelumnya? Atau tipe berukuran nol?

apakah Anda tidak menangani OOM sebelumnya?

Ketika ada fungsi alloc::heap::allocate mengembalikan pointer tanpa Result dan tidak memberikan pilihan dalam penanganan OOM. Saya pikir itu membatalkan proses. Sekarang saya telah menambahkan .unwrap() untuk membuat utas panik.

mereka seharusnya berada dalam satu modul

Saya melihat sekarang bahwa heap.rs berisi pub use allocator::*; . Tetapi ketika saya mengklik Alloc di impl yang tercantum di halaman rustdoc untuk Heap saya dikirim ke alloc::allocator::Alloc .

Mengenai sisanya, saya belum memeriksanya. Saya porting ke kompiler baru setumpuk besar kode yang ditulis tahun lalu. Saya rasa ini adalah panggilan balik untuk FreeType, pustaka C.

Ketika ada, fungsi aloc :: heap :: alocate mengembalikan pointer tanpa Hasil dan tidak memberikan pilihan dalam penanganan OOM.

Itu memberi Anda pilihan. Penunjuk yang dikembalikan bisa jadi penunjuk nol yang akan menunjukkan pengalokasi heap gagal untuk mengalokasikan. Inilah mengapa saya sangat senang itu beralih ke Result sehingga orang tidak lupa menangani kasus itu.

Oh ya, mungkin FreeType akhirnya melakukan pemeriksaan nol, saya tidak tahu. Bagaimanapun, ya, mengembalikan Hasil itu bagus.

Diberikan # 30170 dan # 43097, saya tergoda untuk menyelesaikan masalah OS X dengan penyelarasan yang sangat besar hanya dengan menentukan bahwa pengguna tidak dapat meminta penyelarasan> = 1 << 32 .

Salah satu cara yang sangat mudah untuk menegakkan ini: Ubah antarmuka Layout sehingga align dilambangkan dengan u32 bukan usize .

@alexcrichton apakah Anda memiliki pendapat tentang ini? Haruskah saya membuat PR yang melakukan ini?

@pnkfelix Layout::from_size_align masih akan mengambil usize dan mengembalikan kesalahan pada u32 overflow, bukan?

@SimonSapin alasan apa yang membuatnya terus mengambil usize align, jika prasyarat statis adalah tidak aman untuk meneruskan nilai> = 1 << 32 ?

dan jika jawabannya adalah "baik beberapa pengalokasi mungkin mendukung penyelarasan> = 1 << 32 ", maka kita kembali ke status quo dan Anda dapat mengabaikan saran saya. Inti dari saran saya pada dasarnya adalah "+1" untuk komentar seperti ini

Karena std::mem::align_of mengembalikan usize

@SimonSapin ah, API stabil lama yang bagus ... huh.

@pnkfelix membatasi hingga 1 << 32 tampaknya masuk akal bagi saya!

@rachmandfc_gabung

Ok sifat ini dan tipenya telah dipanggang untuk sementara waktu sekarang dan juga menjadi implementasi yang mendasari dari koleksi standar sejak awal. Saya akan mengusulkan memulai dengan penawaran awal yang sangat konservatif, yaitu hanya menstabilkan antarmuka berikut:

pub struct Layout { /* ... */ }

extern {
    pub type void;
}

impl Layout {
    pub fn from_size_align(size: usize, align: usize) -> Option<Layout>;
    pub unsafe fn from_size_align_unchecked(size: usize, align: usize) -> Layout;

    pub fn size(&self) -> usize;
    pub fn align(&self) -> usize;
}

pub unsafe trait Alloc {
    unsafe fn alloc(&mut self, layout: Layout) -> *mut void;
    unsafe fn alloc_zeroed(&mut self, layout: Layout) -> *mut void;
    unsafe fn dealloc(&mut self, ptr: *mut void, layout: Layout);

    // all other methods are default and unstable
}

/// The global default allocator
pub struct Heap;

impl Alloc for Heap {
    // ...
}

impl<'a> Alloc for &'a Heap {
    // ...
}

/// The "system" allocator
pub struct System;

impl Alloc for System {
    // ...
}

impl<'a> Alloc for &'a System {
    // ...
}

Proposal asli

pub struct Layout { /* ... */ }

impl Layout {
    pub fn from_size_align(size: usize, align: usize) -> Option<Layout>;
    pub unsafe fn from_size_align_unchecked(size: usize, align: usize) -> Layout;

    pub fn size(&self) -> usize;
    pub fn align(&self) -> usize;
}

// renamed from AllocErr today
pub struct Error {
    // ...
}

impl Error {
    pub fn oom() -> Self;
}

pub unsafe trait Alloc {
    unsafe fn alloc(&mut self, layout: Layout) -> Result<*mut u8, Error>;
    unsafe fn dealloc(&mut self, ptr: *mut u8, layout: Layout);

    // all other methods are default and unstable
}

/// The global default allocator
pub struct Heap;

impl Alloc for Heap {
    // ...
}

impl<'a> Alloc for &'a Heap {
    // ...
}

/// The "system" allocator
pub struct System;

impl Alloc for System {
    // ...
}

impl<'a> Alloc for &'a System {
    // ...
}

Terutama:

  • Hanya menstabilkan metode alloc , alloc_zeroed , dan dealloc pada sifat Alloc untuk saat ini. Saya rasa ini menyelesaikan masalah paling mendesak yang kita hadapi saat ini, mendefinisikan pengalokasi global kustom.
  • Hapus tipe Error mendukung penggunaan pointer mentah.
  • Ubah tipe u8 di antarmuka menjadi void
  • Versi yang dipreteli dari tipe Layout .

Masih ada pertanyaan terbuka seperti apa yang harus dilakukan dengan dealloc dan penyelarasan (perataan yang tepat? Cocok? Tidak yakin?), Tetapi saya berharap kami dapat menyelesaikannya selama FCP karena kemungkinan besar tidak akan menjadi API- melanggar perubahan.

1 untuk mendapatkan sesuatu yang stabil!

Ubah nama AllocErr menjadi Error dan pindahkan antarmuka menjadi sedikit lebih konservatif.

Apakah ini menghilangkan opsi untuk pengalokasi untuk menentukan Unsupported ? Dengan risiko mengomel lebih banyak tentang sesuatu yang sering saya bicarakan, saya pikir # 44557 masih menjadi masalah.

Layout

Sepertinya Anda telah menghapus beberapa metode dari Layout . Apakah Anda bermaksud agar yang Anda tinggalkan benar-benar dihapus, atau dibiarkan saja tidak stabil?

impl Error {
    pub fn oom() -> Self;
}

Apakah ini konstruktor untuk hari ini AllocErr::Exhausted ? Jika demikian, bukankah seharusnya parameter Layout ?

Anggota tim @alexcrichton telah mengusulkan untuk menggabungkan ini. Langkah selanjutnya adalah meninjau oleh tim yang diberi tag lainnya:

  • [x] @BurntSushi
  • [x] @kimundi
  • [x] @alexcrichton
  • [x] @aturon
  • [x] @cramertj
  • [x] @dtolnay
  • [x] @eddyb
  • [x] @nikomatsakis
  • [x] @nrc
  • [x] @pnkfelix
  • [x] @sfackler
  • [x] @withoutboats

Kekhawatiran:

Setelah peninjau tersebut mencapai konsensus, ini akan memasuki periode komentar terakhirnya. Jika Anda melihat masalah besar yang belum pernah diangkat dalam proses ini, silakan angkat bicara!

Lihat dokumen ini untuk info tentang perintah yang dapat diberikan anggota tim kepada saya.

Saya sangat bersemangat untuk menstabilkan beberapa pekerjaan ini!

Satu pertanyaan: di utas di atas, @joshlf dan @ Ericson2314 mengangkat poin menarik tentang kemungkinan memisahkan sifat Alloc dan Dealloc untuk mengoptimalkan kasus di mana alloc memerlukan beberapa data, tetapi dealloc tidak memerlukan informasi tambahan, jadi tipe Dealloc bisa berukuran nol.

Apakah pertanyaan ini pernah terselesaikan? Apa kerugian dari memisahkan kedua sifat tersebut?

@tokopedia

Apakah ini menghilangkan opsi bagi pengalokasi untuk menentukan Tidak Didukung?

Ya dan tidak, itu berarti bahwa operasi semacam itu tidak segera didukung pada karat yang stabil , tetapi kami dapat terus mendukungnya dalam Karat yang tidak stabil.

Apakah Anda bermaksud agar yang Anda tinggalkan benar-benar dihapus, atau dibiarkan saja tidak stabil?

Memang! Sekali lagi, meskipun saya hanya mengusulkan area permukaan API yang stabil, kita dapat membiarkan semua metode lain tidak stabil. Seiring waktu kami dapat terus menstabilkan lebih banyak fungsi. Saya pikir yang terbaik adalah memulai sekonservatif mungkin.


@Septianjoko_

Apakah ini konstruktor untuk AllocErr :: Exhausted hari ini? Jika demikian, bukankah seharusnya itu memiliki parameter Layout?

Aha poin yang bagus! Saya agak ingin meninggalkan kemungkinan untuk membuat Error tipe berukuran nol jika kita benar-benar membutuhkannya, tetapi tentu saja kita dapat menjaga metode pengambilan tata letak tidak stabil dan menstabilkannya jika perlu. Atau apakah Anda berpikir bahwa pelestarian tata letak Error harus distabilkan pada langkah pertama?


@rumahsakitotak

Secara pribadi saya belum melihat pertanyaan / kekhawatiran seperti itu (saya pikir saya melewatkannya!), Tetapi secara pribadi saya tidak akan melihatnya sepadan. Dua ciri adalah dua kali lipat boilerplate pada umumnya, karena sekarang setiap orang harus mengetik Alloc + Dealloc dalam koleksi misalnya. Saya berharap bahwa penggunaan khusus seperti itu tidak ingin menginformasikan antarmuka yang akhirnya digunakan oleh semua pengguna lain, secara pribadi.

@cramertj @alexcrichton

Secara pribadi saya belum melihat pertanyaan / kekhawatiran seperti itu (saya pikir saya melewatkannya!), Tetapi secara pribadi saya tidak akan melihatnya sepadan.

Secara umum saya setuju bahwa itu tidak layak dengan satu pengecualian mencolok: Box . Box<T, A: Alloc> akan, dengan definisi saat ini dari Alloc , harus berukuran paling tidak dua kata (penunjuk yang sudah dimilikinya dan setidaknya ada referensi ke Alloc ) kecuali dalam kasus lajang global (yang dapat diimplementasikan sebagai ZST). Sebuah ledakan 2x (atau lebih) di ruang yang dibutuhkan untuk menyimpan tipe yang umum dan mendasar seperti itu mengkhawatirkan saya.

@alexrichard

karena sekarang setiap orang harus mengetikkan Alloc + Dealloc dalam koleksi misalnya

Kami dapat menambahkan sesuatu seperti ini:

trait Allocator: Alloc + Dealloc {}
impl<T> Allocator for T where T: Alloc + Dealloc {}

Sebuah ledakan 2x (atau lebih) di ruang yang dibutuhkan untuk menyimpan tipe yang umum dan mendasar

Hanya jika Anda menggunakan pengalokasi kustom yang tidak global proses. std::heap::Heap (default) adalah ukuran nol.

Atau apakah Anda berpikir bahwa Kesalahan pelestarian tata letak harus distabilkan pada langkah pertama?

@alexcrichton Saya tidak begitu mengerti mengapa izin pertama yang diusulkan ini sama sekali. Hampir tidak ada yang bisa dilakukan dengan menyalahgunakan Vec , dan tidak cukup misalnya untuk menggunakan https://crates.io/crates/jemallocator.

Apa yang masih perlu diselesaikan untuk menstabilkan semuanya?

Hanya jika Anda menggunakan pengalokasi kustom yang tidak global proses. std :: heap :: Heap (default) berukuran nol.

Itu sepertinya kasus penggunaan utama memiliki pengalokasi parametrik, bukan? Bayangkan definisi sederhana pohon berikut:

struct Node<T, A: Alloc> {
    t: T,
    left: Option<Box<Node<T, A>>>,
    right: Option<Box<Node<T, A>>>,
}

Sebuah pohon yang dibangun dari pohon dengan 1 kata Alloc akan memiliki ukuran ledakan ~ 1,7x untuk keseluruhan struktur data dibandingkan dengan ZST Alloc . Itu tampaknya sangat buruk bagi saya, dan jenis aplikasi ini adalah inti dari memiliki Alloc menjadi ciri.

@rumahsakitotak

Kami dapat menambahkan sesuatu seperti ini:

Kami juga akan memiliki alias sifat yang sebenarnya :) https://github.com/rust-lang/rust/issues/41517

@glaebhoerl Ya, tapi stabilisasi tampaknya masih jauh karena belum ada implementasi. Jika kita menonaktifkan impls manual Allocator saya pikir kita dapat beralih ke trait-alias backwards-compatibly ketika mereka tiba;)

@tokopedia

Sebuah ledakan 2x (atau lebih) di ruang yang dibutuhkan untuk menyimpan tipe yang umum dan mendasar seperti itu mengkhawatirkan saya.

Saya akan membayangkan semua implementasi hari ini hanyalah tipe ukuran nol atau penunjuk besar, bukan? Bukankah kemungkinan pengoptimalan bahwa beberapa jenis ukuran penunjuk dapat berukuran nol? (atau semacam itu?)


@rumahsakitotak

Kami dapat menambahkan sesuatu seperti ini:

Memang! Kami kemudian mengambil satu sifat menjadi tiga . Di masa lalu kami tidak pernah memiliki pengalaman hebat dengan sifat-sifat seperti itu. Misalnya Box<Both> tidak dimasukkan ke Box<OnlyOneTrait> . Saya yakin kita bisa menunggu fitur bahasa untuk memuluskan semua ini, tapi sepertinya itu masih jauh, paling banter.


@Septianjoko_

Apa yang masih perlu diselesaikan untuk menstabilkan semuanya?

Saya tidak tahu. Saya ingin memulai dengan hal terkecil yang absolut sehingga debat akan berkurang.

Saya akan membayangkan semua implementasi hari ini hanyalah tipe ukuran nol atau penunjuk besar, bukan? Bukankah kemungkinan pengoptimalan bahwa beberapa jenis ukuran penunjuk dapat berukuran nol? (atau semacam itu?)

Ya, idenya adalah, dengan penunjuk ke objek yang dialokasikan dari tipe pengalokasi Anda, Anda bisa mengetahui dari mana asalnya (misalnya, menggunakan metadata sebaris). Jadi, satu-satunya informasi yang Anda perlukan untuk membatalkan alokasi adalah informasi jenis, bukan informasi runtime.

Untuk kembali ke penyelarasan saat deallocate, saya melihat dua cara ke depan:

  • Stabilkan seperti yang diusulkan (dengan penyelarasan saat deallocate). Memberikan kepemilikan atas memori yang dialokasikan secara manual tidak mungkin dilakukan kecuali Layout disertakan. Secara khusus, tidak mungkin untuk membangun wadah Vec atau Box atau String atau wadah std dengan penyelarasan yang lebih ketat dari yang diperlukan (misalnya karena Anda tidak tidak ingin elemen kotak mengangkangi baris cache), tanpa mendekonstruksi dan membatalkan alokasi secara manual nanti (yang tidak selalu merupakan pilihan). Contoh lain dari sesuatu yang tidak mungkin, adalah mengisi Vec menggunakan operasi simd, dan kemudian memberikannya.

  • Jangan memerlukan penyelarasan saat deallocate, dan hapus pengoptimalan alokasi kecil dari Windows ' HeapAlloc -based alloc_system . Selalu simpan perataan. @alexcrichton , saat Anda menggunakan kode itu, apakah Anda ingat mengapa kode itu ditempatkan di sana? Apakah kita memiliki bukti bahwa itu menghemat sejumlah besar memori untuk aplikasi dunia nyata? (Dengan microbenchmarks dimungkinkan untuk membuat hasilnya keluar dengan cara apa pun tergantung ukuran alokasi - kecuali HeapAlloc tetap membulatkan ukuran.)

Bagaimanapun, ini adalah pertukaran yang sangat sulit untuk dilakukan; pengaruh memori dan kinerja akan sangat bergantung pada jenis aplikasi, dan mana yang dioptimalkan untuk aplikasi tertentu juga.

Saya pikir kita mungkin sebenarnya Just Fine (TM). Mengutip Alloc dokumen :

Beberapa metode memerlukan tata letak yang sesuai dengan blok memori.
Apa artinya tata letak untuk "menyesuaikan" dengan blok memori (atau
ekuivalen, untuk blok memori agar "sesuai" dengan tata letak) adalah
dua kondisi berikut harus dipenuhi:

  1. Alamat awal blok harus sejajar dengan layout.align() .

  2. Ukuran blok harus berada pada kisaran [use_min, use_max] , di mana:

    • use_min adalah self.usable_size(layout).0 , dan

    • use_max adalah kapasitas yang dulu (atau akan)
      dikembalikan ketika (jika) blok itu dialokasikan melalui panggilan ke
      alloc_excess atau realloc_excess .

Perhatikan bahwa:

  • ukuran tata letak yang terakhir digunakan untuk mengalokasikan blok
    dijamin berada dalam kisaran [use_min, use_max] , dan

  • batas bawah pada use_max dapat diperkirakan dengan aman melalui panggilan ke
    usable_size .

  • jika tata letak k cocok dengan blok memori (dilambangkan dengan ptr )
    saat ini dialokasikan melalui pengalokasi a , maka legal untuk
    gunakan tata letak itu untuk membatalkan alokasinya, yaitu a.dealloc(ptr, k); .

Perhatikan poin terakhir itu. Jika saya mengalokasikan dengan layout dengan alignment a , maka legal bagi saya untuk membatalkan alokasi dengan alignment b < a karena objek yang disejajarkan dengan a juga disejajarkan dengan b , dan dengan demikian layout dengan alignment b cocok dengan objek yang dialokasikan dengan layout dengan alignment a (dan dengan ukuran yang sama).

Artinya, Anda harus dapat mengalokasikan dengan penyelarasan yang lebih besar dari perataan minimum yang diperlukan untuk jenis tertentu dan kemudian mengizinkan beberapa kode lain untuk membatalkan alokasi dengan perataan minimum, dan itu akan berfungsi.

Bukankah kemungkinan pengoptimalan bahwa beberapa jenis ukuran penunjuk dapat berukuran nol? (atau semacam itu?)

Ada RFC untuk ini baru-baru ini dan tampaknya sangat tidak mungkin hal itu dapat dilakukan karena masalah kompatibilitas: https://github.com/rust-lang/rfcs/pull/2040

Misalnya Box<Both> tidak dimasukkan ke Box<OnlyOneTrait> . Saya yakin kita bisa menunggu fitur bahasa untuk memuluskan semua ini, tapi sepertinya itu masih jauh, paling banter.

Ciri objek upcasting di sisi lain tampaknya diinginkan secara tidak kontroversial, dan sebagian besar pertanyaan tentang usaha / bandwidth / kemauan untuk melaksanakannya. Ada utas baru-baru ini: https://internals.rust-lang.org/t/trait-upcasting/5970

@ruuda Saya adalah orang yang menulis implementasi alloc_system aslinya. alexcrichton hanya memindahkannya selama refaktor pengalokasi besar <time period> .

Implementasi saat ini mengharuskan Anda membatalkan alokasi dengan penyelarasan yang sama dengan yang Anda gunakan untuk mengalokasikan blok memori. Terlepas dari apa yang mungkin diklaim oleh dokumentasi, ini adalah kenyataan saat ini yang harus dipatuhi oleh setiap orang hingga alloc_system pada Windows diubah.

Alokasi di Windows selalu menggunakan kelipatan MEMORY_ALLOCATION_ALIGNMENT (meskipun mereka mengingat ukuran yang Anda alokasikan untuk byte). MEMORY_ALLOCATION_ALIGNMENT adalah 8 pada 32bit dan 16 pada 64bit. Untuk tipe yang diselaraskan secara berlebihan, karena penyelarasan lebih besar dari MEMORY_ALLOCATION_ALIGNMENT , overhead yang disebabkan oleh alloc_system secara konsisten jumlah penyelarasan yang ditentukan, sehingga alokasi selaras 64 byte akan memiliki overhead 64 byte.

Jika kami memutuskan untuk memperluas trik overaligned tersebut ke semua alokasi (yang akan menghilangkan persyaratan untuk membatalkan alokasi dengan penyelarasan yang sama yang Anda tentukan saat mengalokasikan), maka lebih banyak alokasi akan memiliki overhead. Alokasi yang perataannya identik dengan MEMORY_ALLOCATION_ALIGNMENT akan mengalami overhead konstan sebesar MEMORY_ALLOCATION_ALIGNMENT byte. Alokasi yang penyelarasannya kurang dari MEMORY_ALLOCATION_ALIGNMENT akan mengalami overhead sebesar MEMORY_ALLOCATION_ALIGNMENT byte kira-kira separuh waktu. Jika ukuran alokasi dibulatkan menjadi MEMORY_ALLOCATION_ALIGNMENT lebih besar dari atau sama dengan ukuran alokasi ditambah ukuran pointer, maka tidak ada overhead, jika tidak ada. Mengingat 99,99% alokasi tidak akan diselaraskan secara berlebihan, apakah Anda benar-benar ingin menimbulkan overhead semacam itu pada semua alokasi tersebut?

@bayu_joo

Saya pribadi merasa bahwa penerapan alloc_system hari ini di Windows adalah manfaat yang lebih besar daripada memiliki kemampuan untuk melepaskan kepemilikan alokasi ke penampung lain seperti Vec . AFAIK meskipun tidak ada data untuk mengukur dampak dari selalu padding dengan alignment dan tidak memerlukan alignment pada deallocation.

@tokopedia

Saya pikir komentar itu salah, seperti yang ditunjukkan alloc_system pada Windows bergantung pada penyelarasan yang sama yang diteruskan ke deallocation seperti yang diteruskan pada alokasi.

Mengingat 99,99% alokasi tidak akan diselaraskan secara berlebihan, apakah Anda benar-benar ingin menimbulkan overhead semacam itu pada semua alokasi tersebut?

Itu tergantung pada aplikasi apakah overhead-nya signifikan, dan apakah akan mengoptimalkan memori atau kinerja. Kecurigaan saya adalah bahwa untuk sebagian besar aplikasi baik-baik saja, tetapi sebagian kecil sangat peduli dengan memori, dan mereka benar - benar -

@alexrichard

Saya pikir komentar itu salah, seperti yang ditunjukkan alloc_system pada Windows bergantung pada penyelarasan yang sama yang diteruskan ke deallocation seperti yang diteruskan pada alokasi.

Bukankah itu berarti bahwa alloc_system pada Windows tidak benar-benar menerapkan sifat Alloc (dan dengan demikian mungkin kita harus mengubah persyaratan sifat Alloc )?


@ retret

Jika saya membaca komentar Anda dengan benar, bukankah overhead penyelarasan itu ada untuk semua alokasi terlepas dari apakah kita perlu untuk membatalkan alokasi dengan penyelarasan yang berbeda? Artinya, jika saya mengalokasikan 64 byte dengan penyelarasan 64 byte dan saya juga membatalkan alokasi dengan penyelarasan 64 byte, overhead yang Anda jelaskan masih ada. Jadi, ini bukan fitur untuk dapat membatalkan alokasi dengan penyelarasan yang berbeda, melainkan fitur meminta penyelarasan yang lebih besar dari biasanya.

@joshlf Overhead yang disebabkan oleh alloc_system saat ini karena meminta penyelarasan yang lebih besar dari biasanya. Jika perataan Anda kurang dari atau sama dengan MEMORY_ALLOCATION_ALIGNMENT , maka tidak ada biaya tambahan yang disebabkan oleh alloc_system .

Namun, jika kita mengubah implementasi untuk memungkinkan deallocating dengan alignment yang berbeda, maka overhead akan berlaku untuk hampir semua alokasi, terlepas dari alignmentnya.

Ah saya mengerti; masuk akal.

Apa arti penerapan Alloc untuk Heap dan & Heap? Dalam kasus apa pengguna akan menggunakan salah satu impls tersebut vs yang lain?

Apakah ini API pustaka standar pertama di mana *mut u8 berarti "penunjuk ke apa pun"? Ada String :: from_raw_parts tetapi yang satu itu benar-benar berarti penunjuk ke byte. Saya bukan penggemar *mut u8 berarti "penunjuk ke apa pun" - bahkan C lebih baik. Apa sajakah pilihan lain? Mungkin penunjuk ke tipe buram akan lebih berarti.

@rfcbot concern * mut u8

@dtolnay Alloc for Heap adalah semacam "standar" dan Alloc for &Heap seperti Write for &T mana sifat tersebut memerlukan &mut self tetapi implementasinya tidak. Khususnya itu berarti bahwa tipe seperti Heap dan System thread dan tidak perlu disinkronkan saat mengalokasikan.

Lebih penting lagi, bagaimanapun, penggunaan #[global_allocator] mensyaratkan bahwa statis yang dilampirkan, yang memiliki tipe T , memiliki Alloc for &T . (alias semua pengalokasi global harus threadsafe)

Untuk *mut u8 Saya pikir *mut () mungkin menarik, tetapi secara pribadi saya tidak merasa terlalu terdorong untuk "melakukannya dengan benar" di sini.

Keuntungan utama dari *mut u8 adalah sangat mudah untuk menggunakan .offset dengan byte offset.

Untuk *mut u8 Menurut saya *mut () mungkin menarik, tetapi secara pribadi saya tidak merasa terlalu terdorong untuk "melakukannya dengan benar" di sini.

Jika kita menggunakan *mut u8 dalam antarmuka yang stabil, bukankah kita mengunci diri kita sendiri? Dengan kata lain, setelah kita menstabilkan ini, kita tidak akan memiliki kesempatan untuk "melakukan ini dengan benar" di masa depan.

Juga, *mut () tampaknya sedikit berbahaya bagi saya jika kita pernah membuat pengoptimalan seperti RFC 2040 di masa mendatang.

Keuntungan utama dari *mut u8 adalah sangat mudah untuk menggunakan .offset dengan byte offset.

Benar, tetapi Anda dapat dengan mudah melakukan let ptr = (foo as *mut u8) dan kemudian melanjutkan perjalanan Anda. Itu sepertinya bukan motivasi yang cukup untuk tetap menggunakan *mut u8 di API jika ada alternatif yang menarik (yang, sejujurnya, saya tidak yakin ada).

Selain itu, * mut () tampaknya sedikit berbahaya bagi saya jika kami membuat pengoptimalan seperti RFC 2040 di masa mendatang.

Pengoptimalan itu mungkin tidak akan pernah terjadi - itu akan merusak terlalu banyak kode yang ada. Bahkan jika ya, itu akan diterapkan ke &() dan &mut () , bukan *mut () .

Jika RFC 1861 hampir diimplementasikan / distabilkan, saya sarankan untuk menggunakannya:

extern { pub type void; }

pub unsafe trait Alloc {
    unsafe fn alloc(&mut self, layout: Layout) -> Result<*mut void, Error>;
    unsafe fn dealloc(&mut self, ptr: *mut void, layout: Layout);
    // ...
}

Itu mungkin terlalu jauh, kan?

@joshlf Saya pikir saya melihat PR terbuka tentang mereka, sisa yang tidak diketahui adalah DynSized .

Akankah ini berfungsi untuk objek seperti struct hack? Katakanlah saya memiliki Node<T> yang terlihat seperti ini:

struct Node<T> {
   size: u32,
   data: T,
   // followed by `size` bytes
}

dan tipe nilai:

struct V {
  a: u32,
  b: bool,
}

Sekarang saya ingin mengalokasikan Node<V> dengan string berukuran 7 dalam satu alokasi. Idealnya saya ingin membuat alokasi ukuran 16 align 4 dan menyesuaikan semua isinya: 4 untuk u32 , 5 untuk V , dan 7 untuk string byte. Ini berfungsi karena anggota terakhir dari V memiliki perataan 1 dan byte string juga memiliki perataan 1.

Perhatikan bahwa ini tidak diperbolehkan di C / C ++ jika jenisnya disusun seperti di atas, karena menulis ke penyimpanan yang dikemas adalah perilaku yang tidak ditentukan. Saya rasa ini adalah lubang dalam standar C / C ++ yang sayangnya tidak dapat diperbaiki. Saya dapat menjelaskan mengapa ini rusak tetapi mari fokus pada Rust sebagai gantinya. Bisakah ini bekerja? :-)

Sehubungan dengan ukuran dan kesejajaran dari struktur Node<V> itu sendiri, Anda cukup sesuai dengan keinginan kompilator Rust. UB (perilaku tidak terdefinisi) untuk mengalokasikan dengan ukuran atau penyelarasan apa pun yang lebih kecil dari yang dibutuhkan Rust karena Rust dapat melakukan pengoptimalan berdasarkan asumsi bahwa objek Node<V> - di tumpukan, di heap, di belakang referensi, dll. - memiliki ukuran dan kesejajaran yang sesuai dengan yang diharapkan pada waktu kompilasi.

Dalam praktiknya, sepertinya jawabannya adalah sayangnya tidak: Saya menjalankan program ini dan menemukan bahwa, setidaknya di Rust Playground, Node<V> diberi ukuran 12 dan kesejajaran 4, yang berarti bahwa objek apa pun setelahnya Node<V> harus diimbangi dengan setidaknya 12 byte. Sepertinya offset bidang data.b dalam Node<V> adalah 8 byte, yang menyiratkan bahwa byte 9-11 berada di belakang bantalan. Sayangnya, meskipun padding byte "tidak digunakan" dalam beberapa hal, kompilator masih memperlakukan mereka sebagai bagian dari Node<V> , dan berhak untuk melakukan apapun yang disukainya (yang terpenting, termasuk menulis kepada mereka ketika Anda menetapkan ke Node<V> , menyiratkan bahwa jika Anda mencoba membuang data tambahan di sana, itu mungkin akan ditimpa).

(catatan, btw: Anda tidak dapat memperlakukan tipe sebagai dikemas yang menurut kompiler Rust tidak dikemas. Namun, Anda _can_ memberi tahu kompilator Rust bahwa ada sesuatu yang dikemas, yang akan mengubah tata letak tipe (menghapus padding), menggunakan repr(packed) )

Namun, sehubungan dengan meletakkan satu objek demi objek tanpa keduanya menjadi bagian dari tipe Rust yang sama, saya hampir 100% yakin ini valid - lagipula, itulah yang dilakukan Vec . Anda dapat menggunakan metode tipe Layout untuk menghitung secara dinamis berapa banyak ruang yang diperlukan untuk alokasi total:

let node_layout = Layout::new::<Node<V>>();
// NOTE: This is only valid if the node_layout.align() is at least as large as mem::align_of_val("a")!
// NOTE: I'm assuming that the alignment of all strings is the same (since str is unsized, you can't do mem::align_of::<str>())
let padding = node_layout.padding_needed_for(mem::align_of_val("a"));
let total_size = node_layout.size() + padding + 7;
let total_layout = Layout::from_size_align(total_size, node_layout.align()).unwrap();

Apakah sesuatu seperti ini berhasil?

#[repr(C)]
struct Node<T> {
   size: u32,
   data: T,
   bytes: [u8; 0],
}

… Lalu alokasikan dengan ukuran yang lebih besar, dan gunakan slice::from_raw_parts_mut(node.bytes.as_mut_ptr(), size) ?

Terima kasih @joshlf untuk jawaban terperinci! TLDR untuk kasus penggunaan saya adalah bahwa saya bisa mendapatkan Node<V> ukuran 16 tetapi hanya jika V adalah repr(packed) . Kalau tidak, yang terbaik yang bisa saya lakukan adalah ukuran 19 (12 + 7).

@Sonin tidak yakin; Saya akan mencoba.

Belum benar-benar mengikuti utas ini, tetapi saya sudah mati -

  1. Koleksi alokator-polimorfik

    • bahkan bukan kotak yang tidak membengkak!

  2. Koleksi Falliable

Saya pikir desain dari ciri-ciri fundamental akan mempengaruhi solusi dari itu: Saya memiliki sedikit waktu untuk Rust selama beberapa bulan terakhir, tetapi telah memperdebatkannya beberapa kali. Saya ragu saya akan punya waktu untuk sepenuhnya menyelesaikan kasus saya di sini, jadi saya hanya bisa berharap pertama-tama kami setidaknya menulis solusi

Re: @ Ericson2314 komentar

Menurut saya pertanyaan yang relevan terkait konflik antara perspektif itu dan keinginan @alexcrichton untuk menstabilkan sesuatu adalah: Berapa banyak manfaat yang kita dapatkan dari menstabilkan antarmuka minimal? Secara khusus, sangat sedikit konsumen yang akan memanggil metode Alloc secara langsung (bahkan sebagian besar koleksi mungkin akan menggunakan Box atau wadah serupa lainnya), jadi pertanyaan sebenarnya menjadi: apa yang menstabilkan pembelian bagi pengguna yang mau tidak memanggil metode Alloc secara langsung? Sejujurnya, satu-satunya kasus penggunaan serius yang dapat saya pikirkan adalah bahwa ini membuka jalur untuk koleksi alokator-polimorfik (yang kemungkinan akan digunakan oleh kumpulan pengguna yang jauh lebih luas), tetapi sepertinya itu diblokir di # 27336, yang jauh dari sedang diselesaikan. Mungkin ada kasus penggunaan besar lainnya yang saya lewatkan, tetapi berdasarkan analisis cepat itu, saya cenderung menjauh dari stabilisasi karena hanya memiliki manfaat marjinal dengan biaya mengunci kita ke dalam desain yang mungkin nantinya akan kita temukan sebagai suboptimal .

@joshlf memungkinkan orang untuk mendefinisikan dan menggunakan pengalokasi global mereka sendiri.

Hmmm poin yang bagus. apakah mungkin untuk menstabilkan penetapan pengalokasi global tanpa menstabilkan Alloc ? Yaitu, kode yang mengimplementasikan Alloc harus tidak stabil, tapi itu mungkin akan dikemas dalam peti sendiri, dan mekanisme untuk menandai pengalokasi itu sebagai pengalokasi global akan stabil. Atau saya salah paham bagaimana stabil / tidak stabil dan kompilator stabil / kompilator nightly berinteraksi?

Ah @joshlf ingat bahwa # 27336 adalah gangguan, sesuai https://github.com/rust-lang/rust/issues/42774#issuecomment -317279035. Saya cukup yakin kita akan mengalami masalah lain --- masalah dengan sifat-sifat yang ada, itulah sebabnya saya ingin bekerja untuk mulai mengerjakannya sekarang. Jauh lebih mudah untuk mendiskusikan masalah-masalah itu begitu mereka tiba untuk dilihat semua orang daripada debat yang diprediksi di masa depan pasca- # 27336.

@joshlf Tapi Anda tidak bisa mengkompilasi peti yang mendefinisikan pengalokasi global dengan kompilator stabil.

@sfackler Ah ya, ada kesalahpahaman yang saya takuti: P

Saya menemukan nama Excess(ptr, usize) agak membingungkan karena usize bukanlah excess dalam ukuran alokasi yang diminta (seperti dalam ukuran ekstra yang dialokasikan), tetapi total ukuran alokasi.

IMO Total , Real , Usable , atau nama apapun yang menyatakan bahwa ukuran adalah ukuran total atau ukuran sebenarnya dari alokasi lebih baik daripada "kelebihan", yang saya temukan menyesatkan. Hal yang sama berlaku untuk metode _excess .

Saya setuju dengan @gnzlbg di atas, saya pikir tuple biasa (ptr, usize) akan baik-baik saja.

Perhatikan bahwa Excess tidak diusulkan untuk distabilkan pada lintasan pertama

Memposting utas ini untuk diskusi di reddit, yang membuat beberapa orang khawatir: https://www.reddit.com/r/rust/comments/78dabn/custom_allocators_are_on_the_verge_of_being/

Setelah berdiskusi lebih lanjut dengan @ rust-lang / libs hari ini, saya ingin membuat beberapa perubahan pada proposal stabilisasi yang dapat diringkas dengan:

  • Tambahkan alloc_zeroed ke set metode yang distabilkan, jika tidak, memiliki tanda tangan yang sama seperti alloc .
  • Ubah *mut u8 menjadi *mut void di API menggunakan extern { type void; } support, menyelesaikan masalah @dtolnay dan memberikan jalan untuk menyatukan c_void di seluruh ekosistem.
  • Ubah jenis pengembalian alloc menjadi *mut void , hapus Result dan Error

Mungkin yang paling kontroversial adalah poin terakhir jadi saya ingin menguraikannya juga. Ini keluar dari diskusi dengan tim libs hari ini dan secara khusus berkisar pada bagaimana (a) antarmuka berbasis Result memiliki ABI yang kurang efisien daripada yang mengembalikan pointer dan (b) hampir tidak ada pengalokasi "produksi" hari ini memberikan kemampuan untuk mempelajari lebih dari "ini hanya OOM'd". Untuk kinerja, kami kebanyakan dapat menutupinya dengan inlining dan semacamnya tetapi tetap saja Error adalah muatan tambahan yang sulit dihapus pada lapisan terendah.

Pemikiran untuk mengembalikan muatan kesalahan adalah bahwa pengalokasi dapat menyediakan introspeksi khusus implementasi untuk mempelajari mengapa alokasi gagal dan sebaliknya hampir semua konsumen hanya perlu mengetahui apakah alokasi berhasil atau gagal. Selain itu, ini dimaksudkan untuk menjadi API tingkat sangat rendah yang sebenarnya tidak sering dipanggil (sebagai gantinya, API yang diketik yang membungkus semuanya dengan baik harus dipanggil sebagai gantinya). Dalam hal ini, tidak terpenting bahwa kami memiliki API yang paling dapat digunakan dan ergonomis untuk lokasi ini, tetapi lebih penting tentang mengaktifkan kasus penggunaan tanpa mengorbankan kinerja.

Keuntungan utama dari *mut u8 adalah sangat mudah untuk menggunakan .offset dengan byte offset.

Dalam pertemuan libs kami juga menyarankan impl *mut void { fn offset } yang tidak bertentangan dengan offset ditentukan untuk T: Sized . Bisa juga byte_offset .

+1 untuk menggunakan *mut void dan byte_offset . Apakah akan ada masalah dengan stabilisasi fitur tipe eksternal, atau dapatkah kita menghindari masalah itu karena hanya definisinya yang tidak stabil (dan liballoc dapat melakukan hal-hal yang tidak stabil secara internal) dan bukan penggunaan (misalnya, let a: *mut void = ... isn tidak stabil)?

Ya, kita tidak perlu memblokir stabilisasi tipe eksternal. Meskipun dukungan tipe eksternal dihapus, void kita definisikan untuk ini selalu bisa menjadi kasus terburuk tipe magis.

Apakah ada diskusi lebih lanjut dalam pertemuan libs tentang apakah Alloc dan Dealloc harus menjadi ciri yang terpisah atau tidak?

Kami tidak menyinggung itu secara khusus, tetapi kami umumnya merasa bahwa kami tidak boleh beralih dari seni sebelumnya kecuali kami memiliki alasan yang sangat kuat untuk melakukannya. Secara khusus, konsep Allocator C ++ tidak memiliki pemisahan yang serupa.

Saya tidak yakin itu perbandingan yang tepat dalam kasus ini. Di C ++, semuanya secara eksplisit dibebaskan, jadi tidak ada yang setara dengan Box yang perlu menyimpan salinan (atau referensi ke) pengalokasi sendiri. Itulah yang menyebabkan ledakan ukuran besar bagi kami.

@joshlf unique_ptr setara dengan Box , vector setara dengan Vec , unordered_map setara dengan HashMap , dll.

@cramertj Ah, menarik, saya hanya melihat jenis koleksi. Sepertinya itu mungkin hal yang harus dilakukan. Kami selalu dapat menambahkannya nanti melalui blanket impls tetapi mungkin akan lebih bersih untuk menghindarinya.

Pendekatan implan selimut mungkin lebih bersih, sebenarnya:

pub trait Dealloc {
    fn dealloc(&self, ptr: *mut void, layout: Layout);
}

impl<T> Dealloc for T
where
    T: Alloc
{
    fn dealloc(&self, ptr: *mut void, layout: Layout) {
        <T as Alloc>::dealloc(self, ptr, layout)
    }
}

Satu sifat yang perlu dikhawatirkan untuk sebagian besar kasus penggunaan.

  • Ubah jenis return dari alokasi ke * mut void, hapus Hasil dan Kesalahan

Mungkin yang paling kontroversial adalah poin terakhir jadi saya ingin menguraikannya juga. Ini keluar dari diskusi dengan tim libs hari ini dan secara khusus berkisar pada bagaimana (a) antarmuka berbasis Hasil memiliki ABI yang kurang efisien daripada yang mengembalikan penunjuk dan (b) hampir tidak ada pengalokasi "produksi" saat ini yang menyediakan kemampuan untuk belajar lebih dari "ini baru saja OOM". Untuk kinerja, kami sebagian besar dapat menindihnya dengan inlining dan semacamnya tetapi tetap saja Kesalahan adalah muatan tambahan yang sulit dihilangkan pada lapisan terendah.

Saya khawatir bahwa ini akan membuatnya sangat mudah untuk menggunakan pointer yang dikembalikan tanpa memeriksa null. Tampaknya overhead juga dapat dihilangkan tanpa menambahkan risiko ini dengan mengembalikan Result<NonZeroPtr<void>, AllocErr> dan menghasilkan AllocErr ukuran nol?

( NonZeroPtr adalah gabungan ptr::Shared dan ptr::Unique seperti yang diusulkan di https://github.com/rust-lang/rust/issues/27730#issuecomment-316236397.)

@SimonSapin sesuatu seperti Result<NonZeroPtr<void>, AllocErr> membutuhkan tiga jenis untuk distabilkan, yang semuanya baru dan beberapa di antaranya secara historis agak merana untuk stabilisasi. Sesuatu seperti void bahkan tidak diperlukan dan bagus untuk dimiliki (menurut saya).

Saya setuju itu "mudah digunakan tanpa memeriksa null", tetapi, sekali lagi, ini adalah API tingkat rendah yang tidak dimaksudkan untuk digunakan secara berlebihan, jadi menurut saya kita tidak harus mengoptimalkan ergonomi pemanggil.

Orang-orang juga dapat membangun abstraksi tingkat yang lebih tinggi seperti alloc_one di atas tingkat rendah alloc yang dapat memiliki jenis pengembalian yang lebih kompleks seperti Result<NonZeroPtr<void>, AllocErr> .

Saya setuju bahwa AllocErr tidak akan berguna dalam praktiknya, tetapi bagaimana jika hanya Option<NonZeroPtr<void>> ? API yang tidak mungkin disalahgunakan secara tidak sengaja, tanpa overhead, adalah salah satu hal yang membedakan Rust dari C, dan kembali ke pointer nol gaya C terasa seperti langkah mundur bagi saya. Mengatakannya sebagai "API level sangat rendah yang tidak dimaksudkan untuk banyak digunakan" sama seperti mengatakan bahwa kita tidak boleh peduli dengan keamanan memori pada arsitektur mikrokontroler yang tidak umum karena levelnya sangat rendah dan tidak banyak digunakan.

Setiap interaksi dengan pengalokasi melibatkan kode yang tidak aman apa pun jenis kembalian dari fungsi ini. API alokasi tingkat rendah dapat disalahgunakan apakah jenis pengembaliannya adalah Option<NonZeroPtr<void>> atau *mut void .

Alloc::alloc khususnya adalah API yang levelnya rendah dan tidak dimaksudkan untuk banyak digunakan. Metode seperti Alloc::alloc_one<T> atau Alloc::alloc_array<T> adalah alternatif yang akan lebih banyak digunakan dan memiliki tipe pengembalian yang "lebih baik".

Sebuah stateful AllocError tidak sepadan, tapi tipe berukuran nol yang mengimplementasikan Error dan memiliki Display dari allocation failure bagus untuk dimiliki. Jika kita memilih rute NonZeroPtr<void> , saya melihat Result<NonZeroPtr<void>, AllocError> lebih disukai daripada Option<NonZeroPtr<void>> .

Mengapa terburu-buru untuk menstabilkan :( !! Result<NonZeroPtr<void>, AllocErr> tidak dapat disangkal lebih baik untuk digunakan klien. Mengatakan ini adalah "API tingkat sangat rendah" yang tidak perlu bagus adalah sangat tidak ambisius. Kode di semua tingkat harus seaman dan semaksimal mungkin; kode tidak jelas yang tidak terus-menerus diedit (dan karenanya dimasukkan ke dalam ingatan jangka pendek orang-orang) terlebih lagi!

Selain itu, jika kita ingin memiliki koleksi alokasi-polimorfik yang ditulis pengguna, yang tentu saja saya harapkan, itu adalah jumlah terbuka kode yang cukup kompleks yang menggunakan pengalokasi secara langsung.

Dealoaksi, secara operasional, kita hampir pasti ingin mereferensikan / mengkloning aloaktor hanya sekali per koleksi berbasis pohon. Itu berarti meneruskan pengalokasi ke setiap kotak pengalokasi khusus yang dimusnahkan. Tapi, Ini adalah masalah terbuka tentang cara terbaik melakukan ini di Rust tanpa tipe linier. Bertentangan dengan komentar saya sebelumnya, saya akan baik-baik saja dengan beberapa kode yang tidak aman dalam implementasi koleksi untuk ini, karena usecase yang ideal mengubah implementasi Box , bukan implementasi sifat pengalokasi terpisah dan deallocator. Yaitu kita dapat membuat kemajuan yang stabil tanpa menghalangi linieritas.

@sfackler Saya rasa kita memerlukan beberapa tipe terkait yang menghubungkan deallocator ke pengalokasi; mungkin tidak mungkin untuk memperbaikinya.

@ Ericson2314 Ada "terburu-buru" untuk menstabilkan karena orang ingin menggunakan pengalokasi untuk hal-hal nyata di dunia nyata. Ini bukan proyek sains.

Untuk apa tipe terkait itu digunakan?

Orang @sfackler masih bisa menyematkan nightly / dan tipe orang yang peduli dengan fitur advance semacam ini seharusnya merasa nyaman melakukannya. [Jika masalah rustc tidak stabil vs Rust tidak stabil, itu adalah masalah lain yang memerlukan perbaikan kebijakan.] Memanggang di API yang buruk, sebaliknya, melumpuhkan kita selamanya , kecuali kita ingin membagi ekosistem dengan standar 2.0 baru.

Jenis terkait akan menghubungkan deallocator ke pengalokasi. Masing-masing perlu tahu tentang yang lain agar ini berfungsi. [Masih ada masalah dalam menggunakan (de) pengalokasi yang salah dari jenis yang benar, tetapi saya menerima bahwa tidak ada yang mengusulkan solusi untuk itu dari jarak jauh.]

Jika orang bisa memasang pin pada malam hari, mengapa kita memiliki bangunan yang stabil? Kumpulan orang yang berinteraksi langsung dengan API pengalokasi jauh lebih kecil daripada orang yang ingin memanfaatkan API tersebut dengan misalnya mengganti pengalokasi global.

Dapatkah Anda menulis beberapa kode yang menunjukkan mengapa deallocator perlu mengetahui jenis pengalokasi yang terkait? Mengapa API pengalokasi C ++ tidak memerlukan pemetaan serupa?

Jika orang bisa memasang pin pada malam hari, mengapa kita memiliki bangunan yang stabil?

Untuk menunjukkan stabilitas bahasa. Kode yang Anda tulis terhadap versi ini tidak akan pernah rusak. Di kompiler yang lebih baru. Anda menyematkan setiap malam ketika Anda membutuhkan sesuatu yang sangat buruk, tidak ada gunanya menunggu iterasi terakhir dari fitur yang dianggap memiliki kualitas yang layak untuk jaminan itu.

Kumpulan orang yang berinteraksi langsung dengan API pengalokasi jauh lebih kecil daripada orang yang ingin memanfaatkan API tersebut dengan misalnya mengganti pengalokasi global.

Aha! Ini akan untuk memindahkan jemalloc dari pohon, dll? Tidak ada yang mengusulkan menstabilkan peretasan mengerikan yang memungkinkan memilih pengalokasi global, hanya heap statis itu sendiri? Atau apakah saya salah membaca proposal?

Peretasan mengerikan yang memungkinkan untuk memilih pengalokasi global diusulkan untuk distabilkan, yang merupakan setengah dari apa yang memungkinkan kita untuk memindahkan jemalloc dari pohon. Masalah ini adalah separuh lainnya.

#[global_allocator] stabilisasi atribut: https://github.com/rust-lang/rust/issues/27389#issuecomment -336955367

Astaga

@ Ericson2314 Menurut Anda apa cara yang tidak buruk untuk memilih pengalokasi global?

(Ditanggapi di https://github.com/rust-lang/rust/issues/27389#issuecomment-342285805)

Proposal telah diubah untuk menggunakan * mut void.

@rfcbot diselesaikan * mut u8

@rfcbot ditinjau

Setelah beberapa diskusi tentang IRC, saya menyetujui ini dengan pemahaman bahwa kami _do not_ bermaksud untuk menstabilkan Box generik pada Alloc , tetapi sebaliknya pada beberapa Dealloc sifat dengan impl selimut yang sesuai, seperti yang disarankan oleh @sfackler di sini . Tolong beritahu saya jika saya salah mengerti maksudnya.

@cramertj Hanya untuk memperjelas, mungkin saja menambahkan selimut impl setelah fakta dan tidak melanggar definisi Alloc yang kami stabilkan di sini?

@joshlf ya, akan terlihat seperti ini: https://github.com/rust-lang/rust/issues/32838#issuecomment -340959804

Bagaimana kita menentukan Dealloc untuk Alloc ? Saya akan membayangkan sesuatu seperti ini?

pub unsafe trait Alloc {
    type Dealloc: Dealloc = Self;
    ...
}

Saya rasa itu menempatkan kita di wilayah yang sulit. WRT https://github.com/rust-lang/rust/issues/29661.

Ya, menurut saya tidak ada cara agar penambahan Dealloc kompatibel dengan versi sebelumnya dari Alloc (yang tidak memiliki tipe terkait) tanpa default.

Jika Anda ingin secara otomatis dapat mengambil deallocator yang sesuai dengan pengalokasi, Anda memerlukan lebih dari sekadar tipe terkait, tetapi fungsi untuk menghasilkan nilai deallocator.

Tapi, ini bisa ditangani di masa depan dengan hal-hal yang dilampirkan ke subtrait terpisah dari Alloc saya kira.

@sackler saya tidak yakin saya mengerti. Bisakah Anda menuliskan tanda tangan Box::new bawah desain Anda?

Ini mengabaikan sintaks penempatan dan semua itu, tetapi salah satu cara Anda dapat melakukannya adalah

pub struct Box<T, D>(NonZeroPtr<T>, D);

impl<T, D> Box<T, D>
where
    D: Dealloc
{
    fn new<A>(alloc: A, value: T) -> Box<T, D>
    where
        A: Alloc<Dealloc = D>
    {
        let ptr = alloc.alloc_one().unwrap_or_else(|_| alloc.oom());
        ptr::write(&value, ptr);
        let deallocator = alloc.deallocator();
        Box(ptr, deallocator)
    }
}

Khususnya, kita harus benar-benar dapat menghasilkan instance deallocator, tidak hanya mengetahui tipenya. Anda juga dapat membuat parameter Box atas Alloc dan sebagai gantinya menyimpan A::Dealloc , yang mungkin membantu dengan jenis inferensi. Kita dapat membuat ini berfungsi setelah stabilisasi ini dengan memindahkan Dealloc dan deallocator ke sifat terpisah:

pub trait SplitAlloc: Alloc {
    type Dealloc;

    fn deallocator(&self) -> Self::Dealloc;
}

Tapi seperti apa implikasi Drop ?

impl<T, D> Drop for Box<T, D>
where
    D: Dealloc
{
    fn drop(&mut self) {
        unsafe {
            ptr::drop_in_place(self.0);
            self.1.dealloc_one(self.0);
        }
    }
}

Tetapi dengan asumsi kita menstabilkan Alloc terlebih dahulu, maka tidak semua Alloc s akan mengimplementasikan Dealloc , bukan? Dan saya pikir spesialisasi impl masih jauh? Dengan kata lain, secara teori, Anda ingin melakukan hal seperti berikut, tetapi menurut saya belum berhasil?

impl<T, D> Drop for Box<T, D> where D: Dealloc { ... }
impl<T, A> Drop for Box<T, A> where A: Alloc { ... }

Jika ada, kami memiliki file

default impl<T> SplitAlloc for T
where
    T: Alloc { ... }

Tapi menurutku itu tidak terlalu perlu. Kasus penggunaan untuk pengalokasi khusus dan pengalokasi global cukup berbeda sehingga saya tidak akan berasumsi akan ada banyak tumpang tindih di antara keduanya.

Saya kira itu bisa berhasil. Tampaknya jauh lebih bersih bagi saya, dengan hanya memiliki Dealloc langsung dari kelelawar sehingga kita dapat memiliki antarmuka yang lebih sederhana. Saya membayangkan kita dapat memiliki antarmuka yang cukup sederhana dan tidak kontroversial yang tidak memerlukan perubahan pada kode yang sudah ada yang sudah mengimplementasikan Alloc :

unsafe trait Dealloc {
    fn dealloc(&mut self, ptr: *mut void, layout: Layout);
}

impl<T> Dealloc for T
where
    T: Alloc
{
    fn dealloc(&self, ptr: *mut void, layout: Layout) {
        <T as Alloc>::dealloc(self, ptr, layout)
    }
}

unsafe trait Alloc {
    type Dealloc: Dealloc = &mut Self;
    fn deallocator(&mut self) -> Self::Dealloc { self }
    ...
}

Saya meskipun tipe default terkait bermasalah?

A Dealloc yang merupakan referensi yang dapat diubah ke pengalokasi tampaknya tidak terlalu berguna - Anda hanya dapat mengalokasikan satu hal pada satu waktu, bukan?

Saya meskipun tipe default terkait bermasalah?

Oh, saya kira default jenis terkait cukup jauh sehingga kita tidak dapat mengandalkannya.

Namun, kami dapat memiliki yang lebih sederhana:

unsafe trait Dealloc {
    fn dealloc(&mut self, ptr: *mut void, layout: Layout);
}

impl<T> Dealloc for T
where
    T: Alloc
{
    fn dealloc(&self, ptr: *mut void, layout: Layout) {
        <T as Alloc>::dealloc(self, ptr, layout)
    }
}

unsafe trait Alloc {
    type Dealloc: Dealloc;
    fn deallocator(&mut self) -> Self::Dealloc;
    ...
}

dan hanya membutuhkan pelaksana untuk menulis sedikit boilerplate.

A Dealloc yang merupakan referensi yang dapat diubah ke pengalokasi tampaknya tidak terlalu berguna - Anda hanya dapat mengalokasikan satu hal pada satu waktu, bukan?

Ya, poin yang bagus. Mungkin titik diperdebatkan mengingat komentar Anda yang lain.

Haruskah deallocator mengambil self , &self , atau &mut self ?

Mungkin &mut self agar konsisten dengan metode lain.

Adakah pengalokasi yang lebih memilih untuk mengambil nilai sendiri sehingga mereka tidak harus, misalnya, status klon?

Masalah dengan mengambil self dengan nilai adalah hal itu menghalangi mendapatkan Dealloc dan kemudian melanjutkan untuk mengalokasikan.

Saya sedang memikirkan pengalokasi "oneshot" hipotetis, meskipun saya tidak tahu seberapa banyak hal yang nyata itu.

Pengalokasi seperti itu mungkin ada, tetapi mengambil self berdasarkan nilai akan mengharuskan _all_ pengalokasi bekerja dengan cara itu, dan akan menghalangi pengalokasi apa pun yang mengizinkan alokasi setelah deallocator dipanggil.

Saya masih ingin melihat beberapa di antaranya diterapkan dan digunakan dalam koleksi sebelum kita memikirkan untuk menstabilkannya.

Apakah menurut Anda https://github.com/rust-lang/rust/issues/27336 atau poin yang dibahas di https://github.com/rust-lang/rust/issues/32838#issuecomment -339066870 akan memungkinkan kami untuk melanjutkan koleksi?

Saya khawatir tentang dampak pendekatan alias tipe pada keterbacaan dokumentasi. Cara (sangat bertele-tele) untuk memungkinkan kemajuan adalah dengan membungkus jenis:

pub struct Vec<T>(alloc::Vec<T, Heap>);

impl<T> Vec<T> {
    // forwarding impls for everything
}

Saya tahu ini menyebalkan, tetapi tampaknya perubahan yang kita diskusikan di sini cukup besar sehingga jika kita memutuskan untuk melanjutkan dengan sifat alokasi / dealloc terpisah, kita harus mencobanya di std terlebih dahulu dan kemudian kembali ke FCP.

Apa timeline menunggu hal ini untuk diimplementasikan?

Metode grow_in_place tidak mengembalikan segala jenis kelebihan kapasitas. Saat ini memanggil usable_size dengan tata letak, memperluas alokasi ke _at least_ sesuai dengan tata letak ini, tetapi jika alokasi diperpanjang di luar tata letak itu, pengguna tidak dapat mengetahuinya.

Saya mengalami kesulitan memahami keuntungan dari metode alloc dan realloc atas alloc_excess dan realloc_excess .

Pengalokasi perlu menemukan blok memori yang sesuai untuk melakukan alokasi: ini memerlukan mengetahui ukuran blok memori. Apakah pengalokasi kemudian mengembalikan sebuah pointer, atau tuple "pointer dan ukuran blok memori" tidak membuat perbedaan performa yang dapat diukur.

Jadi alloc dan realloc hanya meningkatkan permukaan API dan tampaknya mendorong penulisan kode yang kurang berkinerja. Mengapa kami memilikinya di API? Apa keuntungan mereka?


EDIT: atau dengan kata lain: semua fungsi yang berpotensi mengalokasikan di API harus mengembalikan Excess , yang pada dasarnya menghilangkan kebutuhan untuk semua metode _excess .

Kelebihan hanya menarik untuk kasus penggunaan yang melibatkan array yang dapat berkembang. Ini tidak berguna atau relevan untuk Box atau BTreeMap , misalnya. Mungkin ada beberapa biaya dalam menghitung kelebihannya, dan tentu saja ada API yang lebih kompleks, jadi bagi saya sepertinya kode yang tidak peduli dengan kelebihan kapasitas harus dipaksa untuk membayarnya.

Mungkin ada beberapa biaya dalam menghitung kelebihannya

Bisakah Anda memberi contoh? Saya tidak tahu, dan tidak bisa membayangkan, sebuah pengalokasi yang mampu mengalokasikan memori tetapi tidak tahu berapa banyak memori yang sebenarnya dialokasikan (yang mana Excess adalah: jumlah sebenarnya dari memori yang dialokasikan; kita harus ganti namanya).

Satu-satunya yang umum digunakan Alloc ator di mana ini mungkin sedikit kontroversial adalah POSIX malloc , yang meskipun selalu menghitung Excess internal, tidak mengeksposnya sebagai bagian dari C-nya. API. Namun, mengembalikan ukuran yang diminta sebagai Excess tidak apa-apa, portabel, sederhana, tidak dikenakan biaya sama sekali, dan semua orang yang menggunakan POSIX malloc sudah berasumsi.

jemalloc dan pada dasarnya Alloc ator lainnya di luar sana menyediakan API yang mengembalikan Excess tanpa menimbulkan biaya apa pun, jadi bagi pengalokasi tersebut, mengembalikan Excess adalah nol biaya juga.

Mungkin ada beberapa biaya dalam menghitung kelebihannya, dan tentu saja ada API yang lebih kompleks, jadi bagi saya sepertinya kode yang tidak peduli dengan kelebihan kapasitas harus dipaksa untuk membayarnya.

Saat ini semua orang sudah membayar harga dari sifat pengalokasi yang memiliki dua API untuk mengalokasikan memori. Dan sementara satu dapat membangun Excess -kurang API di atas sebuah Excess -full one`, sebaliknya tidak benar. Jadi saya bertanya-tanya mengapa tidak dilakukan seperti ini:

  • Alloc metode trait selalu menghasilkan Excess
  • tambahkan ciri ExcessLessAlloc yang baru saja menurunkan Excess dari Alloc metode untuk semua pengguna yang 1) cukup peduli untuk menggunakan Alloc tetapi 2) tidak peduli dengan jumlah sebenarnya dari memori yang saat ini dialokasikan (sepertinya ceruk bagi saya, tetapi saya masih berpikir bahwa API seperti itu bagus untuk dimiliki)
  • jika suatu hari seseorang menemukan cara untuk mengimplementasikan Alloc ators dengan jalur cepat untuk metode Excess -less, kami selalu dapat menyediakan implementasi kustom ExcessLessAlloc untuknya.

FWIW Saya baru saja mendarat di utas ini lagi karena saya tidak dapat menerapkan apa yang saya inginkan di atas Alloc . Saya sebutkan bahwa sebelumnya hilang grow_in_place_excess , tetapi saya terjebak lagi karena juga kehilangan alloc_zeroed_excess (dan entah apa lagi).

Saya akan lebih nyaman jika stabilisasi di sini akan fokus pada menstabilkan API Excess -full terlebih dahulu. Meskipun API-nya bukan yang paling ergonomis untuk semua penggunaan, API semacam itu setidaknya akan mengizinkan semua penggunaan yang merupakan syarat yang diperlukan untuk menunjukkan bahwa desainnya tidak cacat.

Bisakah Anda memberi contoh? Saya tidak tahu, dan tidak bisa membayangkan, sebuah pengalokasi yang mampu mengalokasikan memori tetapi tidak tahu berapa banyak memori yang sebenarnya dialokasikan (yang mana Excess adalah: jumlah sebenarnya dari memori yang dialokasikan; kita harus ganti namanya).

Sebagian besar pengalokasi saat ini menggunakan kelas ukuran, di mana setiap kelas ukuran hanya mengalokasikan objek dengan ukuran tetap tertentu, dan permintaan alokasi yang tidak sesuai dengan kelas ukuran tertentu dibulatkan ke kelas ukuran terkecil yang mereka muat di dalamnya. Dalam skema ini, itu umum untuk melakukan hal-hal seperti memiliki array objek kelas ukuran dan kemudian melakukan classes[size / SIZE_QUANTUM].alloc() . Di dunia itu, mencari tahu kelas ukuran apa yang digunakan membutuhkan instruksi tambahan: misalnya, let excess = classes[size / SIZE_QUANTUM].size . Ini mungkin tidak banyak, tetapi kinerja pengalokasi kinerja tinggi (seperti jemalloc) diukur dalam siklus jam tunggal, sehingga dapat mewakili overhead yang berarti, terutama jika ukuran tersebut akhirnya melewati rantai pengembalian fungsi.

Bisakah Anda memberi contoh?

Setidaknya menggunakan PR Anda untuk mengalokasikan_jemalloc, alloc_excess cukup jelas menjalankan lebih banyak kode daripada alloc : https://github.com/rust-lang/rust/pull/45514/files.

Dalam skema ini, biasanya melakukan hal-hal seperti memiliki larik objek kelas ukuran dan kemudian melakukan kelas [size / SIZE_QUANTUM] .alloc (). Di dunia itu, mencari tahu kelas ukuran apa yang digunakan membutuhkan instruksi tambahan: misalnya, biarkan kelebihan = kelas [size / SIZE_QUANTUM] .size

Jadi biarkan saya melihat apakah saya mengikuti dengan benar:

// This happens in both cases:
let size_class = classes[size / SIZE_QUANTUM];
let ptr = size_class.alloc(); 
// This would happen only if you need to return the size:
let size = size_class.size;
return (ptr, size);

Itu saja?


Setidaknya menggunakan PR Anda untuk alokasi_jemalloc, alokasi_excess cukup jelas menjalankan lebih banyak kode daripada alokasi

PR itu adalah perbaikan bug (bukan perbaikan kinerja), ada banyak hal yang salah dengan kondisi saat ini dari lapisan jemalloc kami secara sempurna tetapi karena PR itu setidaknya mengembalikan apa yang seharusnya:

  • nallocx adalah fungsi const dalam pengertian GCC, yaitu fungsi murni sejati. Ini berarti tidak memiliki efek samping, hasilnya hanya bergantung pada argumennya, tidak mengakses status global, argumennya bukan penunjuk (sehingga fungsi tidak dapat mengakses status global membuangnya), dan untuk program C / C ++ LLVM dapat menggunakan ini informasi untuk membatalkan panggilan jika hasilnya tidak digunakan. AFAIK Rust saat ini tidak dapat menandai fungsi FFI C sebagai const fn atau serupa. Jadi ini adalah hal pertama yang dapat diperbaiki dan akan menghasilkan realloc_excess biaya nol bagi mereka yang tidak menggunakan kelebihan selama inline dan pengoptimalan bekerja dengan baik.
  • nallocx selalu dihitung untuk alokasi yang diselaraskan di dalam mallocx , artinya, semua kode sudah menghitungnya, tetapi mallocx membuang hasilnya, jadi di sini kita sebenarnya menghitungnya dua kali , dan dalam beberapa kasus nallocx hampir semahal mallocx ... Saya memiliki garpu jemallocator yang memiliki beberapa tolok ukur untuk hal-hal seperti ini di cabang-cabangnya, tetapi ini harus diperbaiki oleh jemalloc dengan menyediakan API yang tidak membuang ini. Namun, perbaikan ini hanya memengaruhi mereka yang saat ini menggunakan Excess .
  • dan kemudian adalah masalah bahwa kami menghitung bendera penyelarasan dua kali, tetapi itu adalah sesuatu yang dapat dioptimalkan LLVM di pihak kami (dan sepele untuk diperbaiki).

Jadi ya, sepertinya lebih banyak kode, tetapi kode tambahan ini adalah kode yang sebenarnya kita panggil dua kali, karena pertama kali kita memanggilnya kita membuang hasilnya. Bukan tidak mungkin untuk memperbaikinya, tetapi saya belum menemukan waktu untuk melakukan ini.


EDIT: @sfackler Saya berhasil meluangkan waktu untuk itu hari ini dan dapat menghasilkan alloc_excess "gratis" sehubungan dengan alloc di jalur lambat jemallocs, dan hanya memiliki overhead ~ 1ns di jalur cepat jemallocs. Saya belum benar-benar melihat jalur cepat secara mendetail, tetapi mungkin dapat meningkatkannya lebih jauh. Detailnya ada di sini: https://github.com/jemalloc/jemalloc/issues/1074#issuecomment -345040339

Itu saja?

Iya.

Jadi ini adalah hal pertama yang bisa diperbaiki dan akan membuat realloc_excess menjadi nol biaya bagi mereka yang tidak menggunakan kelebihan selama inlining dan pengoptimalan bekerja dengan baik.

Saat digunakan sebagai pengalokasi global, semua ini tidak dapat disisipkan.

Meskipun API-nya bukan yang paling ergonomis untuk semua penggunaan, API semacam itu setidaknya akan mengizinkan semua penggunaan yang merupakan syarat yang diperlukan untuk menunjukkan bahwa desainnya tidak cacat.

Secara harfiah tidak ada kode di Github yang memanggil alloc_excess . Jika ini adalah fitur yang sangat penting, mengapa tidak ada yang pernah menggunakannya? API alokasi C ++ tidak menyediakan akses ke kelebihan kapasitas. Tampaknya sangat mudah untuk menambahkan / menstabilkan fitur-fitur ini di masa mendatang dengan cara yang kompatibel ke belakang jika ada bukti nyata nyata bahwa fitur tersebut meningkatkan kinerja dan siapa pun benar-benar cukup peduli untuk menggunakannya.

Saat digunakan sebagai pengalokasi global, semua ini tidak dapat disisipkan.

Maka itu adalah masalah yang harus kita coba selesaikan, setidaknya untuk build LTO, karena pengalokasi global seperti jemalloc bergantung pada ini: nallocx adalah cara _by design_, dan rekomendasi pertama devs jemalloc membuat kami berkenaan dengan alloc_excess kinerja adalah bahwa kami harus memiliki panggilan itu sebaris, dan kami harus menyebarkan atribut C dengan benar, sehingga kompilator menghapus panggilan nallocx dari situs panggilan yang tidak gunakan Excess , seperti yang dilakukan oleh kompiler C dan C ++.

Bahkan jika kita tidak bisa melakukan itu, API Excess masih dapat dibuat tanpa biaya dengan menambal API jemalloc (Saya memiliki implementasi awal untuk tambalan seperti itu di rust-lang / garpu jemalloc). Kami dapat mempertahankan API itu sendiri, atau mencoba mendaratkannya di hulu, tetapi untuk itu ke hulu kami harus membuat kasus yang baik tentang mengapa bahasa lain ini dapat melakukan pengoptimalan ini dan Rust tidak bisa. Atau kita harus memiliki argumen lain, seperti API baru ini secara signifikan lebih cepat dari mallocx + nallocx untuk pengguna yang membutuhkan Excess .

Jika ini adalah fitur yang sangat penting, mengapa tidak ada yang pernah menggunakannya?

Itu pertanyaan yang bagus. std::Vec adalah poster-child untuk menggunakan Excess API, tetapi saat ini tidak menggunakannya, dan semua komentar saya sebelumnya yang menyatakan "ini dan itu hilang dari Excess API "ketika saya mencoba membuat Vec menggunakannya. API Excess :

Saya tidak tahu mengapa tidak ada yang menggunakan API ini. Tetapi mengingat bahwa bahkan pustaka std dapat menggunakannya untuk struktur data yang paling cocok untuk ( Vec ), jika saya harus menebak, saya akan mengatakan bahwa alasan utamanya adalah itu API ini saat ini rusak.

Jika saya harus menebak lebih jauh, saya akan mengatakan bahwa bahkan mereka yang merancang API ini tidak menggunakannya, terutama karena tidak ada satu pun koleksi std menggunakannya (yang saya harapkan API ini akan diuji pada awalnya) , dan juga karena menggunakan _excess dan Excess mana-mana berarti usable_size / allocation_size sangat membingungkan / mengganggu program.

Ini mungkin karena lebih banyak pekerjaan dimasukkan ke dalam API tanpa Excess , dan ketika Anda memiliki dua API, sulit untuk membuatnya tetap sinkron, sulit bagi pengguna untuk menemukan keduanya dan mengetahui mana yang akan digunakan, dan terakhir, sulit bagi pengguna untuk lebih mengutamakan kenyamanan daripada melakukan hal yang benar.

Atau dengan kata lain, jika saya memiliki dua API yang bersaing, dan saya menempatkan 100% pekerjaan untuk meningkatkan satu, dan 0% pekerjaan untuk meningkatkan yang lain, tidak mengherankan untuk mencapai kesimpulan bahwa satu dalam praktik secara signifikan lebih baik dari yang lain.

Sejauh yang saya tahu, ini adalah dua panggilan ke nallocx luar tes jemalloc di Github:

https://github.com/facebook/folly/blob/f2925b23df8d85ebca72d62a69f1282528c086de/folly/detail/ThreadLocalDetail.cpp#L182
https://github.com/louishust/mysql5.6.14_tokudb/blob/4897660dee3e8e340a1e6c8c597f3b2b7420654a/storage/tokudb/ft-index/ftcxx/malloc_utils.hpp#L91

Tak satu pun dari mereka yang menyerupai API alloc_excess , tetapi lebih digunakan secara mandiri untuk menghitung ukuran alokasi sebelum dibuat.

Apache Arrow melihat menggunakan nallocx dalam implementasinya tetapi menemukan hal-hal tidak berfungsi dengan baik:

https://issues.apache.org/jira/browse/ARROW-464

Ini pada dasarnya adalah satu-satunya referensi ke nallocx dapat saya temukan. Mengapa penting bahwa implementasi awal dari API pengalokasi mendukung fitur yang tidak jelas seperti itu?

Sejauh yang saya tahu, ini adalah dua panggilan ke nallocx di luar tes jemalloc di Github:

Dari atas kepala saya, saya tahu bahwa setidaknya jenis vektor facebook menggunakannya melalui implementasi malloc facebook ( kebijakan pertumbuhan malloc dan fbvector ; itu adalah sebagian besar vektor C ++ di facebook menggunakan ini) dan juga Kapel menggunakannya untuk meningkatkan kinerja tipe String (di sini dan masalah pelacakan ). Jadi mungkin hari ini bukan hari terbaik Github?

Mengapa penting bahwa implementasi awal dari API pengalokasi mendukung fitur yang tidak jelas seperti itu?

Implementasi awal dari API pengalokasi tidak perlu mendukung fitur ini.

Tetapi dukungan yang baik untuk fitur ini seharusnya memblokir stabilisasi API semacam itu.

Mengapa harus memblokir stabilisasi jika dapat ditambahkan ke belakang-kompatibel nanti?

Mengapa harus memblokir stabilisasi jika dapat ditambahkan ke belakang-kompatibel nanti?

Karena bagi saya setidaknya itu berarti hanya setengah dari ruang desain yang sudah cukup dieksplorasi.

Apakah Anda berharap bagian API yang tidak terlalu terkait akan terpengaruh oleh desain fungsi terkait yang berlebihan? Saya akui bahwa saya hanya mengikuti diskusi itu dengan setengah hati tetapi tampaknya tidak mungkin bagi saya.

Jika kami tidak dapat membuat API ini:

fn alloc(...) -> (*mut u8, usize) { 
   // worst case system API:
   let ptr = malloc(...);
   let excess = malloc_excess(...);
   (ptr, excess)
}
let (ptr, _) = alloc(...); // drop the excess

seefisien yang ini:

fn alloc(...) -> *mut u8 { 
   // worst case system API:
   malloc(...)
}
let ptr = alloc(...);

maka kami memiliki masalah yang lebih besar.

Apakah Anda berharap bagian API yang tidak terlalu terkait akan terpengaruh oleh desain fungsi terkait yang berlebihan?

Jadi ya, saya mengharapkan kelebihan-API yang baik untuk memiliki efek besar pada desain fungsi terkait yang tidak berlebih: itu akan sepenuhnya menghapusnya.

Hal itu akan mencegah situasi saat ini yang memiliki dua API yang tidak sinkron, dan di mana kelebihan-api memiliki fungsionalitas yang lebih sedikit daripada yang lebih sedikit. Meskipun seseorang dapat membangun API yang lebih sedikit di atas API yang terlalu penuh, hal sebaliknya tidak benar.

Mereka yang ingin menjatuhkan Excess harus menjatuhkannya.

Untuk memperjelas, jika ada beberapa cara untuk menambahkan metode alloc_excess setelah fakta dengan cara yang kompatibel ke belakang, maka Anda akan setuju? (tapi tentu saja, menstabilkan tanpa alloc_excess berarti menambahkannya nanti akan menjadi perubahan besar; Saya hanya bertanya agar saya mengerti alasan Anda)

@ Joshlf Sangat mudah untuk melakukan itu.

: bell: Ini sekarang memasuki periode komentar terakhirnya , sesuai ulasan di atas . :lonceng:

Mereka yang ingin menghilangkan Kelebihan harus menjatuhkannya.

Sebagai alternatif, 0,01% orang yang peduli dengan kelebihan kapasitas dapat menggunakan metode lain.

@sfackler Ini adalah apa yang saya dapatkan dari istirahat dua minggu dari karat - saya lupa tentang metode default impls :)

Sebagai alternatif, 0,01% orang yang peduli dengan kelebihan kapasitas dapat menggunakan metode lain.

Dari mana Anda mendapatkan nomor ini?

Semua struktur data Rust saya datar dalam memori. Kemampuan untuk melakukan itu adalah satu-satunya alasan saya menggunakan Rust; jika saya bisa Kotak semuanya, saya akan menggunakan bahasa yang berbeda. Jadi saya tidak peduli tentang Excess yang 0.01% waktu itu, saya peduli tentang itu sepanjang waktu.

Saya memahami bahwa ini adalah domain khusus, dan bahwa di domain lain orang tidak akan pernah peduli dengan Excess , tetapi saya ragu bahwa hanya 0,01% pengguna Rust yang peduli tentang ini (maksud saya, banyak orang menggunakan Vec dan String , yang merupakan struktur data poster-anak untuk Excess ).

Saya mendapatkan angka tersebut dari fakta bahwa total ada sekitar 4 hal yang menggunakan nallocx, dibandingkan dengan himpunan hal-hal yang menggunakan malloc.

@bayu_joo

Apakah Anda menyarankan bahwa jika kita melakukannya dengan "benar" dari awal, kita hanya memiliki fn alloc(layout) -> (ptr, excess) dan tidak ada fn alloc(layout) -> ptr sama sekali? Itu tampaknya jauh dari jelas bagi saya. Bahkan jika kelebihan tersedia, tampaknya wajar untuk memiliki API terakhir untuk kasus penggunaan di mana kelebihan tidak menjadi masalah (misalnya, sebagian besar struktur pohon), bahkan jika itu diimplementasikan sebagai alloc_excess(layout).0 .

@tokopedia

Itu tampaknya jauh dari jelas bagi saya. Meskipun kelebihan tersedia, tampaknya wajar untuk memiliki API terakhir untuk kasus penggunaan di mana kelebihan tidak menjadi masalah (misalnya, sebagian besar struktur pohon), meskipun itu diimplementasikan sebagai alok_excess (layout) .0.

Saat ini, API kelebihan-penuh diterapkan di atas yang lebih sedikit. Menerapkan Alloc untuk pengalokasi kelebihan-sedikit mengharuskan pengguna untuk menyediakan metode alloc dan dealloc .

Namun, jika saya ingin mengimplementasikan Alloc untuk pengalokasi penuh berlebih, saya perlu menyediakan lebih banyak metode (setidaknya alloc_excess , tetapi ini tumbuh jika kita beralih ke realloc_excess , alloc_zeroed_excess , grow_in_place_excess , ...).

Jika kita melakukannya dengan cara lain, yaitu, menerapkan API yang lebih sedikit sebagai yang terbaik di atas yang terlalu penuh, kemudian menerapkan alloc_excess dan dealloc sudah cukup untuk mendukung kedua jenis pengalokasi.

Pengguna yang tidak peduli atau tidak dapat mengembalikan atau menanyakan kelebihan dapat mengembalikan ukuran input atau tata letak (yang merupakan ketidaknyamanan kecil), tetapi pengguna yang dapat menangani dan ingin menangani kelebihan tidak perlu menerapkan metode lainnya.


@tokopedia

Saya mendapatkan angka tersebut dari fakta bahwa total ada sekitar 4 hal yang menggunakan nallocx, dibandingkan dengan himpunan hal-hal yang menggunakan malloc.

Mengingat fakta-fakta ini tentang penggunaan _excess di ekosistem Rust:

  • 0 hal total menggunakan _excess dalam ekosistem karat
  • 0 hal total menggunakan _excess di perpustakaan rust std
  • bahkan tidak Vec dan String dapat menggunakan _excess API dengan benar di perpustakaan karat std
  • API _excess tidak stabil, tidak sinkron dengan kelebihan-sedikit API, bermasalah hingga baru-baru ini (bahkan tidak mengembalikan excess sama sekali), ...

    dan mengingat fakta-fakta berikut tentang penggunaan _excess dalam bahasa lain:

  • API jemalloc tidak secara native didukung oleh program C atau C ++ karena kompatibilitas ke belakang

  • Program C dan C ++ yang ingin menggunakan API berlebih jemalloc harus keluar dari cara mereka untuk menggunakannya dengan:

    • memilih keluar dari pengalokasi sistem dan ke jemalloc (atau tcmalloc)

    • menerapkan ulang pustaka std bahasa mereka (dalam kasus C ++, terapkan pustaka std yang tidak kompatibel)

    • tulis seluruh tumpukan mereka di atas pustaka std yang tidak kompatibel ini

  • beberapa komunitas (firefox menggunakannya, facebook menerapkan ulang koleksi di pustaka standar C ++ agar dapat menggunakannya, ...) masih berusaha keras untuk menggunakannya.

Kedua argumen ini tampak masuk akal bagi saya:

  • API excess di std tidak dapat digunakan, oleh karena itu pustaka std tidak dapat menggunakannya, oleh karena itu tidak ada yang bisa, itulah sebabnya ia tidak digunakan bahkan sekali dalam ekosistem Rust .
  • Meskipun C dan C ++ membuat hampir tidak mungkin untuk menggunakan API ini, proyek-proyek besar dengan tenaga kerja berusaha keras untuk menggunakannya, oleh karena itu setidaknya beberapa komunitas orang yang berpotensi kecil sangat peduli tentangnya.

Argumen Anda:

  • Tidak ada yang menggunakan API _excess , oleh karena itu hanya 0,01% orang yang peduli.

tidak.

@alexcrichton Keputusan untuk beralih dari -> Result<*mut u8, AllocErr> menjadi -> *mut void mungkin datang sebagai kejutan yang signifikan bagi orang-orang yang mengikuti perkembangan asli dari pengalokasi RFC.

Saya tidak setuju dengan poin yang Anda buat , tapi tetap saja sepertinya cukup banyak orang yang bersedia untuk hidup dengan "beban berat" dari Result atas kemungkinan peningkatan kehilangan null- periksa nilai yang dikembalikan.

  • Saya mengabaikan masalah efisiensi waktu proses yang diberlakukan oleh ABI karena saya, seperti @alexcrichton , berasumsi bahwa kita dapat mengatasinya melalui trik kompilator.

Adakah cara agar kita bisa meningkatkan visibilitas pada perubahan yang terlambat itu dengan sendirinya?

Satu cara (di luar kepalaku): Ubah tanda tangan sekarang, dalam PR sendiri, di cabang master, sementara Allocator masih tidak stabil. Dan kemudian lihat siapa yang mengeluh di PR (dan siapa yang merayakan!).

  • Apakah ini terlalu berat? Sepertinya itu dengan definisi yang lebih ringan daripada menggabungkan perubahan seperti itu dengan stabilisasi ...

Mengenai masalah apakah akan mengembalikan *mut void atau mengembalikan Result<*mut void, AllocErr> : Ada kemungkinan bahwa kita harus meninjau kembali gagasan memisahkan sifat pengalokasi "tingkat tinggi" dan "tingkat rendah", sebagaimana dibahas dalam pengambilan II dari Allocator RFC .

(Jelas jika saya memiliki keberatan serius terhadap nilai pengembalian *mut void , maka saya akan mengajukannya sebagai kekhawatiran melalui fcpbot. Tetapi pada titik ini saya cukup mempercayai penilaian tim libs, mungkin di beberapa bagian karena kelelahan atas saga pengalokasi ini.)

@pramugari_id

Keputusan untuk beralih dari -> Result<*mut u8, AllocErr> menjadi -> *mut void mungkin datang sebagai kejutan yang signifikan bagi orang-orang yang mengikuti perkembangan asli dari pengalokasi RFC.

Yang terakhir menyiratkan bahwa, seperti yang telah dibahas, satu-satunya kesalahan yang ingin kami ungkapkan adalah OOM. Jadi, bobot yang sedikit lebih ringan di antaranya yang masih memiliki manfaat perlindungan terhadap kegagalan yang tidak disengaja untuk memeriksa kesalahan adalah -> Option<*mut void> .

@bayu_joo

API berlebih di std tidak dapat digunakan, oleh karena itu pustaka std tidak dapat menggunakannya, oleh karena itu tidak ada yang bisa, itulah sebabnya ia tidak digunakan bahkan sekali dalam ekosistem Rust.

Kemudian perbaiki.

@pramugari_id

Mengenai subjek apakah akan mengembalikan mut void atau mengembalikan Hasil < mut void, AllocErr>: Ada kemungkinan bahwa kita harus meninjau kembali gagasan tentang sifat pengalokasi "tingkat tinggi" dan "tingkat rendah" yang terpisah, seperti yang dibahas dalam pengambilan II dari Allocator RFC.

Itu pada dasarnya adalah pemikiran kami, kecuali bahwa API tingkat tinggi akan berada di Alloc itu sendiri sebagai alloc_one , alloc_array dll. Kami bahkan dapat membiarkannya berkembang di ekosistem terlebih dahulu sebagai ekstensi ciri untuk melihat API tempat berkumpulnya orang.

@pramugari_id

Alasan saya membuat Layout hanya mengimplementasikan Clone dan bukan Copy adalah karena saya ingin membiarkan kemungkinan menambahkan lebih banyak struktur ke tipe Layout. Secara khusus, saya masih tertarik untuk mencoba agar Layout mencoba melacak semua tipe struktur yang digunakan untuk membangunnya (misalnya 16-array dari struct {x: u8, y: [char; 215]}), sehingga pengalokasi akan memiliki pilihan untuk mengekspos rutinitas instrumentasi yang melaporkan jenis konten mereka saat ini yang dibuat.

Apakah ini telah dicoba di suatu tempat?

@sfackler Saya telah melakukan sebagian besar darinya, dan semua itu dapat dilakukan dengan API duplikat (tidak ada kelebihan + _excess metode). Saya akan baik-baik saja dengan memiliki dua API dan tidak memiliki API _excess sekarang.

Satu-satunya hal yang masih membuat saya sedikit khawatir adalah bahwa untuk mengimplementasikan pengalokasi sekarang, seseorang perlu mengimplementasikan alloc + dealloc , tetapi alloc_excess + dealloc juga harus berfungsi. Apakah mungkin untuk memberikan alloc implementasi default dalam hal alloc_excess nanti atau apakah itu tidak mungkin atau merupakan perubahan yang merusak? Dalam praktiknya, sebagian besar pengalokasi akan mengimplementasikan sebagian besar metode, jadi ini bukan masalah besar, tetapi lebih seperti keinginan.


jemallocator mengimplementasikan Alloc dua kali (untuk Jemalloc dan &Jemalloc ), di mana implementasi Jemalloc untuk method hanya (&*self).method(...) yang meneruskan pemanggilan metode ke implementasi &Jemalloc . Ini berarti bahwa seseorang harus menyinkronkan kedua implementasi Alloc untuk Jemalloc secara manual. Apakah mendapatkan perilaku yang berbeda untuk implementasi &/_ bisa tragis atau tidak, saya tidak tahu.


Saya merasa sangat sulit untuk mengetahui apa yang sebenarnya dilakukan orang dengan sifat Alloc dalam praktiknya. Satu-satunya proyek yang saya temukan yang menggunakannya akan tetap menggunakan setiap malam (servo, redoks), dan hanya menggunakannya untuk mengubah pengalokasi global. Saya sangat khawatir karena saya tidak dapat menemukan proyek apa pun yang menggunakannya sebagai parameter tipe koleksi (mungkin saya hanya tidak beruntung dan ada beberapa?). Saya secara khusus mencari contoh penerapan SmallVec dan ArrayVec di atas tipe seperti Vec (karena std::Vec tidak memiliki Alloc type parameter lagi), dan juga bertanya-tanya bagaimana kloning antara tipe-tipe ini ( Vec s dengan Alloc ator yang berbeda) akan bekerja (hal yang sama mungkin berlaku untuk kloning Box es dengan Alloc s yang berbeda). Apakah ada contoh bagaimana implementasi ini akan terlihat di suatu tempat?

Satu-satunya proyek yang saya temukan yang menggunakannya akan tetap menggunakan setiap malam (servo, redoks)

Untuk apa nilainya, Servo mencoba untuk beralih dari fitur yang tidak stabil jika memungkinkan: https://github.com/servo/servo/issues/5286

Ini juga masalah ayam-dan-telur. Banyak proyek belum menggunakan Alloc karena masih tidak stabil.

Tidak begitu jelas bagi saya mengapa kita harus memiliki _excess API yang lengkap sejak awal. Mereka awalnya ada untuk mencerminkan API alokasi * eksperimental jemalloc, tetapi itu telah dihapus pada 4.0 beberapa tahun yang lalu demi tidak menduplikasi seluruh permukaan API mereka. Sepertinya kita bisa mengikuti jejak mereka?

Apakah mungkin untuk memberikan alokasi implementasi default dalam hal alokasi_excess nanti atau apakah itu tidak mungkin atau perubahan yang mengganggu?

Kita dapat menambahkan implementasi default dari alloc dalam hal alloc_excess , tetapi alloc_excess perlu memiliki implementasi default dalam hal alloc . Semuanya berfungsi dengan baik jika Anda menerapkan satu atau keduanya, tetapi jika Anda tidak menerapkan keduanya, kode Anda akan dikompilasi tetapi berulang tanpa batas. Ini telah muncul sebelumnya (mungkin untuk Rand ?), Dan kami dapat memiliki beberapa cara untuk mengatakan bahwa Anda perlu mengimplementasikan setidaknya satu dari fungsi-fungsi itu tetapi kami tidak peduli yang mana.

Saya sangat khawatir karena saya tidak dapat menemukan proyek apa pun yang menggunakannya sebagai parameter tipe koleksi (mungkin saya hanya tidak beruntung dan ada beberapa?).

Saya tidak tahu ada orang yang melakukan itu.

Satu-satunya proyek yang saya temukan yang menggunakannya akan tetap menggunakan setiap malam (servo, redoks)

Satu hal besar yang mencegah hal ini bergerak maju adalah bahwa koleksi stdlib belum mendukung pengalokasi parametrik. Itu cukup banyak menghalangi sebagian besar peti lainnya juga, karena sebagian besar koleksi eksternal menggunakan yang internal di bawah tenda ( Box , Vec , dll).

Satu-satunya proyek yang saya temukan yang menggunakannya akan tetap menggunakan setiap malam (servo, redoks)

Satu hal besar yang mencegah hal ini bergerak maju adalah bahwa koleksi stdlib belum mendukung pengalokasi parametrik. Itu cukup banyak menghalangi sebagian besar peti lainnya juga, karena sebagian besar koleksi eksternal menggunakan yang internal di bawah kap (Box, Vec, dll).

Ini berlaku untuk saya - saya punya kernel mainan, dan jika saya bisa, saya akan menggunakan Vec<T, A> , tetapi sebaliknya saya harus memiliki fasad pengalokasi global interior yang bisa berubah, yang kotor.

@remexre bagaimana parameterisasi struktur data Anda akan menghindari keadaan global dengan mutabilitas interior?

Saya kira masih akan ada keadaan global interior yang dapat berubah, tetapi rasanya jauh lebih aman untuk memiliki pengaturan di mana pengalokasi global tidak dapat digunakan sampai memori sepenuhnya dipetakan daripada memiliki fungsi set_allocator global.


EDIT : Baru sadar saya tidak menjawab pertanyaan itu. Saat ini, saya punya sesuatu seperti:

struct BumpAllocator{ ... }
struct RealAllocator{ ... }
struct LinkedAllocator<A: 'static + AreaAllocator> {
    head: Mutex<Option<Cons<A>>>,
}
#[global_allocator]
static KERNEL_ALLOCATOR: LinkedAllocator<&'static mut (AreaAllocator + Send + Sync)> =
    LinkedAllocator::new();

di mana AreaAllocator adalah ciri yang memungkinkan saya (saat runtime) memverifikasi bahwa pengalokasi tidak secara tidak sengaja "tumpang tindih" (dalam hal rentang alamat yang dialokasikan ke dalamnya). BumpAllocator hanya digunakan di awal, untuk ruang awal saat memetakan sisa memori untuk membuat RealAllocator s.

Idealnya, saya ingin memiliki Mutex<Option<RealAllocator>> (atau pembungkus yang menjadikannya "hanya-sisipkan") menjadi satu-satunya pengalokasi, dan semua yang dialokasikan sejak awal diparameterisasi oleh boot awal BumpAllocator . Ini juga memungkinkan saya memastikan bahwa BumpAllocator tidak digunakan setelah boot awal, karena hal-hal yang saya alokasikan tidak dapat bertahan lebih lama.

@tokopedia

Tidak begitu jelas bagi saya mengapa kita harus memiliki _excess API yang lengkap sejak awal. Mereka awalnya ada untuk mencerminkan API alokasi * eksperimental jemalloc, tetapi itu telah dihapus pada 4.0 beberapa tahun yang lalu demi tidak menduplikasi seluruh permukaan API mereka. Sepertinya kita bisa mengikuti jejak mereka?

Saat ini shrink_in_place memanggil xallocx yang mengembalikan ukuran alokasi sebenarnya. Karena shrink_in_place_excess tidak ada, ukuran ini akan dibuang, dan pengguna harus memanggil nallocx untuk menghitung ulang, yang biayanya sangat bergantung pada seberapa besar alokasinya.

Jadi setidaknya beberapa fungsi alokasi jemalloc yang sudah kita gunakan mengembalikan ukuran yang dapat digunakan, tetapi API saat ini tidak mengizinkan kita untuk menggunakannya.

@bayu_joo

Ketika saya sedang mengerjakan kernel mainan saya, menghindari pengalokasi global untuk memastikan tidak ada alokasi yang terjadi sampai pengalokasi disiapkan adalah tujuan saya juga. Senang mendengar aku bukan orangnya!

Saya tidak suka kata Heap untuk pengalokasi global default. Mengapa tidak Default ?

Poin klarifikasi lainnya: RFC 1974 menempatkan semua barang ini di std::alloc tetapi saat ini di std::heap . Lokasi mana yang diusulkan untuk stabilisasi?

@jethrogb "Heap" adalah istilah yang cukup kanonik untuk "hal itu malloc memberi Anda petunjuk" - apa kekhawatiran Anda dengan istilah tersebut?

@tokopedia

"malloc itu memberimu petunjuk untuk"

Kecuali dalam pikiran saya itulah System .

Ah tentu. Global adalah nama lain lalu mungkin? Karena Anda menggunakan #[global_allocator] untuk memilihnya.

Mungkin ada beberapa pengalokasi heap (misalnya libc dan jemalloc yang diawali). Bagaimana jika mengganti nama std::heap::Heap menjadi std::heap::Default dan #[global_allocator] menjadi #[default_allocator] ?

Fakta bahwa itulah yang Anda dapatkan jika Anda tidak menentukan sebaliknya (mungkin ketika misalnya Vec mendapatkan parameter / bidang tipe tambahan untuk pengalokasi) lebih penting daripada fakta bahwa ia tidak memiliki "per -instances "state (atau contoh sebenarnya).

Periode komentar terakhir sekarang sudah selesai.

Mengenai FCP, menurut saya subset API yang diusulkan untuk stabilisasi penggunaannya sangat terbatas. Misalnya, ini tidak mendukung jemallocator peti.

Dengan cara apa? jemallocator mungkin harus menandai beberapa implik dari metode tidak stabil di belakang tanda fitur tapi hanya itu.

Jika jemallocator pada stable Rust tidak dapat mengimplementasikan misalnya Alloc::realloc dengan memanggil je_rallocx tetapi perlu bergantung pada alokasi default + copy + dealloc impl, maka itu bukan pengganti yang dapat diterima untuk perpustakaan standar alloc_jemalloc peti IMO.

Tentu, Anda bisa mendapatkan sesuatu untuk dikompilasi, tetapi itu bukan hal yang sangat berguna.

Mengapa? C ++ tidak memiliki konsep realoc sama sekali di API alokatornya dan itu tampaknya tidak melumpuhkan bahasa. Ini jelas tidak ideal, tapi saya tidak mengerti mengapa itu tidak bisa diterima.

Koleksi C ++ umumnya tidak menggunakan realloc karena C ++ move constructor dapat menjalankan kode arbitrer, bukan karena realoc tidak berguna.

Dan perbandingannya bukan dengan C ++, melainkan dengan pustaka standar Rust saat ini dengan dukungan jemalloc bawaan. Beralih ke dan pengalokasi out-of-std dengan hanya subset Alloc API ini akan menjadi regresi.

Dan realloc adalah contohnya. jemallocator saat ini juga mengimplementasikan alloc_zeroed , alloc_excess , usable_size , grow_in_place , dll.

alokasi_zeroed diusulkan untuk distabilkan. Sejauh yang saya tahu (lihat ke atas), sebenarnya tidak ada penggunaan alloc_excess ada. Bisakah Anda menunjukkan beberapa kode yang akan mundur jika itu kembali ke implementasi default.

Secara lebih umum, saya tidak mengerti mengapa ini merupakan argumen yang menentang stabilisasi sebagian dari API ini. Jika Anda tidak ingin menggunakan jemallocator, Anda dapat terus tidak menggunakannya.

Bisakah Layout::array<T>() dijadikan const fn?

Itu bisa membuat panik, jadi tidak untuk saat ini.

Itu bisa membuat panik, jadi tidak untuk saat ini.

Begitu ... Aku akan puas dengan const fn Layout::array_elem<T>() yang sama saja dengan Layout::<T>::repeat(1).0 .

@mzabaluev Saya rasa yang Anda gambarkan setara dengan Layout::new<T>() . Saat ini dapat menjadi panik, tetapi itu hanya karena diterapkan menggunakan Layout::from_size_align dan kemudian .unwrap() , dan saya berharap ini dapat dilakukan secara berbeda.

@joshlf Saya pikir struct ini memiliki ukuran 5, sedangkan sebagai elemen dari array ini ditempatkan pada setiap 8 byte karena penyelarasan:

struct Foo {
    bar: u32,
    baz: u8
}

Saya tidak yakin bahwa array Foo akan menyertakan padding elemen terakhir untuk perhitungan ukurannya, tapi itulah harapan kuat saya.

Di Rust, ukuran objek selalu merupakan kelipatan dari perataannya sehingga alamat elemen n th dari sebuah array selalu array_base_pointer + n * size_of<T>() . Jadi ukuran sebuah objek dalam sebuah array selalu sama dengan ukuran objek itu sendiri. Lihat halaman Rustonomicon di repr (Rust) untuk lebih jelasnya.

OK, ternyata sebuah struct empuk ke keselarasannya, tapi AFAIK ini bukan jaminan stabil kecuali di #[repr(C)] .
Bagaimanapun, menghasilkan Layout::new sebuah const fn juga akan diterima.

Ini adalah perilaku yang didokumentasikan (dan dijamin) dari fungsi stabil:

https://doc.rust-lang.org/std/mem/fn.size_of.html

Mengembalikan ukuran tipe dalam byte.

Lebih khusus lagi, ini adalah offset dalam byte antara elemen yang berurutan dalam array dengan tipe item tersebut termasuk padding perataan. Jadi, untuk semua jenis T dan panjang n , [T; n] memiliki ukuran n * size_of::<T>() .

Terima kasih. Saya baru menyadari bahwa setiap const fn yang mengalikan hasil dari Layout::new akan secara inheren panik pada gilirannya (kecuali jika dilakukan dengan saturating_mul atau semacamnya), jadi saya kembali ke titik awal. Melanjutkan pertanyaan tentang kepanikan dalam masalah pelacakan konstan.

Makro panic!() saat ini tidak didukung dalam ekspresi konstan, tetapi kepanikan dari aritmatika yang diperiksa dihasilkan oleh kompilator dan tidak terpengaruh oleh batasan itu:

error[E0080]: constant evaluation error
 --> a.rs:1:16
  |
1 | const A: i32 = i32::max_value() * 2;
  |                ^^^^^^^^^^^^^^^^^^^^ attempt to multiply with overflow

error: aborting due to previous error

Ini terkait dengan Alloc::realloc tetapi tidak dengan stabilisasi antarmuka minimal ( realloc bukan bagian darinya):

Saat ini, karena Vec::reserve/double call RawVec::reserve/double yang memanggil Alloc::realloc , implan default Alloc::realloc menyalin elemen vektor mati (dalam kisaran [len(), capacity()) ) . Dalam kasus yang tidak masuk akal dari vektor kosong besar yang ingin memasukkan capacity() + 1 elemen dan dengan demikian dialokasikan kembali, biaya untuk menyentuh semua memori itu tidak signifikan.

Secara teori, jika implementasi default Alloc::realloc juga akan mengambil rentang "bytes_used", itu hanya dapat menyalin bagian yang relevan pada realokasi. Dalam praktiknya, setidaknya jemalloc mengganti Alloc::realloc default impl dengan panggilan ke rallocx . Apakah melakukan tarian alloc / dealloc hanya menyalin memori yang relevan lebih cepat atau lebih lambat dari panggilan rallocx mungkin akan bergantung pada banyak hal (apakah rallocx kelola untuk memperluas blok di tempatnya? berapa banyak memori yang tidak perlu akan rallocx menyalin? dll.).

https://github.com/QuiltOS/rust/tree/allocator-error Saya mulai mendemonstrasikan bagaimana menurut saya jenis kesalahan terkait memecahkan masalah pengumpulan dan penanganan kesalahan kami dengan melakukan generalisasi itu sendiri. Secara khusus, perhatikan bagaimana dalam modul saya mengubah saya

  • Selalu reuse Result<T, A::Err> implementasi untuk T implemetation
  • Jangan pernah unwrap atau apa pun sebagian
  • Tidak ada oom(e) luar AbortAdapter .

Ini berarti perubahan yang saya buat cukup aman, dan juga tidak terlalu dipikirkan! Bekerja dengan error-return dan error-aborting seharusnya tidak memerlukan upaya ekstra untuk mempertahankan invarian mental --- pemeriksa tipe melakukan semua pekerjaan.

Saya ingat --- saya pikir di RFC @Gankro ? atau utas pra-rfc --- Orang-orang Gecko / Servo mengatakan bahwa menyenangkan tidak memiliki falibilitas koleksi menjadi bagian dari tipenya. Nah, saya dapat menambahkan #[repr(transparent)] menjadi AbortAdapter sehingga koleksi dapat dengan aman diubah antara Foo<T, A> dan Foo<T, AbortAdapter<A>> (dalam bungkus aman), memungkinkan seseorang untuk bebas beralih bolak-balik tanpa menduplikasi setiap metode. [Untuk back-compat, koleksi pustaka standar perlu diduplikasi dalam acara apa pun, tetapi metode pengguna tidak perlu Result<T, !> sekarang ini cukup mudah untuk dikerjakan.]

Sayangnya kode tidak dapat sepenuhnya mengetik centang karena mengubah parameter tipe dari item lang (kotak) membingungkan kompilator (kejutan!). Komit kotak penyebab ICE adalah yang terakhir --- semuanya sebelumnya baik. @eddyb memperbaiki

edit @joshlf Saya diberitahu tentang https://github.com/rust-lang/rust/pull/45272 Anda, dan memasukkannya di sini. Terima kasih!

Persistent Memory (mis. Http://pmem.io ) adalah hal besar berikutnya, dan Rust perlu diposisikan untuk bekerja dengan baik dengannya.

Saya baru-baru ini mengerjakan pembungkus Rust untuk pengalokasi memori persisten (khususnya, libpmemcto). Apa pun keputusan yang dibuat terkait stabilisasi API ini, perlu: -

  • Mampu mendukung pembungkus performa di sekitar pengalokasi memori persisten seperti libpmemcto;
  • Mampu menentukan (membuat parameter) jenis koleksi berdasarkan pengalokasi (saat ini, seseorang perlu menduplikasi Box, Rc, Arc, dll)
  • Mampu menggandakan data di seluruh pengalokasi
  • Mampu mendukung memiliki memori persisten yang disimpan struct dengan kolom yang diinisialisasi ulang pada instantiasi kumpulan memori persisten, misalnya, beberapa struct memori persisten perlu memiliki kolom yang hanya disimpan sementara di heap. Kasus penggunaan saya saat ini adalah referensi ke kumpulan memori persisten yang digunakan untuk alokasi dan data sementara yang digunakan untuk kunci.

Selain itu, pengembangan pmem.io (Intel PMDK) menggunakan banyak alokasi jemalloc yang dimodifikasi di bawah sampulnya - jadi tampaknya bijaksana bahwa menggunakan jemalloc sebagai contoh konsumen API akan lebih bijaksana.

Apakah mungkin untuk mengurangi ruang lingkup ini hingga hanya mencakup GlobalAllocator s terlebih dahulu sampai kita mendapatkan lebih banyak pengalaman dengan menggunakan Alloc ators dalam koleksi?

IIUC ini sudah akan memenuhi kebutuhan servo dan memungkinkan kita untuk bereksperimen dengan parametrizing container secara paralel. Di masa mendatang kita dapat memindahkan koleksi untuk menggunakan GlobalAllocator sebagai gantinya atau hanya menambahkan implikasi selimut Alloc untuk GlobalAllocator sehingga ini dapat digunakan untuk semua koleksi.

Pikiran?

@gnzlbg Agar #[global_allocator] berguna (selain memilih heap::System ) sifat Alloc harus stabil, sehingga dapat diimplementasikan oleh peti seperti https: / /crates.io/crates/jemallocator. Tidak ada tipe atau sifat yang bernama GlobalAllocator saat ini, apakah Anda mengusulkan beberapa API baru?

tidak ada tipe atau sifat yang bernama GlobalAllocator saat ini, apakah Anda mengusulkan API baru?

Yang saya sarankan adalah mengganti nama API "minimal" yang @alexcrichton sarankan untuk distabilkan di sini dari Alloc menjadi GlobalAllocator untuk hanya mewakili pengalokasi global, dan membiarkan pintu terbuka untuk koleksi yang akan diparameterisasi oleh yang berbeda sifat pengalokasi di masa mendatang (yang tidak berarti bahwa kita tidak dapat mengukurnya dengan ciri GlobalAllocator ).

IIUC servo saat ini hanya perlu untuk dapat mengganti pengalokasi global (sebagai lawan dari juga dapat mengatur beberapa koleksi oleh pengalokasi). Jadi, mungkin alih-alih mencoba menstabilkan solusi yang seharusnya terbukti di masa mendatang untuk kedua kasus penggunaan, kami hanya dapat mengatasi masalah pengalokasi global sekarang, dan mencari tahu cara mengatur parameter koleksi oleh pengalokasi nanti.

Tidak tahu apakah itu masuk akal.

Servo IIUC saat ini hanya perlu dapat mengalihkan pengalokasi global (sebagai lawan dari juga dapat mengatur beberapa koleksi oleh pengalokasi).

Itu benar, tapi:

  • Jika suatu sifat dan metodenya stabil sehingga dapat diimplementasikan, maka ia juga dapat dipanggil secara langsung tanpa melalui std::heap::Heap . Jadi ini bukan hanya sifat pengalokasi global, ini adalah sifat pengalokasi (bahkan jika kita akhirnya membuat yang berbeda untuk koleksi generik atas pengalokasi) dan GlobalAllocator bukanlah nama yang sangat bagus.
  • Peti jemallocator saat ini mengimplementasikan alloc_excess , realloc , realloc_excess , usable_size , grow_in_place , dan shrink_in_place yang tidak bagian dari API minimal yang diusulkan. Ini bisa lebih efisien daripada impl default, jadi menghapusnya akan menjadi regresi kinerja.

Kedua poin tersebut masuk akal. Saya hanya berpikir bahwa satu-satunya cara untuk mempercepat stabilisasi fitur ini secara signifikan adalah dengan memotong ketergantungan padanya yang juga merupakan sifat yang baik untuk mengatur parameter koleksi di atasnya.

[Alangkah baiknya jika Servo bisa seperti (stable | official mozilla crate), dan kargo bisa memberlakukan ini, untuk menghilangkan sedikit tekanan di sini.]

@ Ericson2314 servo bukanlah satu-satunya proyek yang ingin menggunakan API ini.

@ Ericson2314 Saya tidak mengerti apa artinya ini, dapatkah Anda mengubah kalimatnya?

Untuk konteks: Servo saat ini menggunakan sejumlah fitur yang tidak stabil (termasuk #[global_allocator] ), tetapi kami mencoba perlahan-lahan menjauh dari itu (baik dengan memperbarui ke kompiler yang telah menstabilkan beberapa fitur, atau dengan mencari alternatif yang stabil. ) Ini dilacak di https://github.com/servo/servo/issues/5286. Jadi menstabilkan #[global_allocator] akan menyenangkan, tetapi tidak memblokir pekerjaan Servo apa pun.

Firefox bergantung pada fakta bahwa Rust std default ke pengalokasi sistem ketika mengkompilasi cdylib , dan mozjemalloc yang akhirnya dihubungkan ke biner yang sama mendefinisikan simbol seperti malloc dan free that "shadow" (saya tidak tahu terminologi linker yang tepat) yang berasal dari libc. Jadi alokasi dari kode Rust di Firefox akhirnya menggunakan mozjemalloc. (Ini ada di Unix, saya tidak tahu cara kerjanya di Windows.) Ini berhasil, tetapi bagi saya terasa rapuh. Firefox menggunakan Rust yang stabil, dan saya ingin menggunakan #[global_allocator] untuk memilih mozjemalloc secara eksplisit agar seluruh pengaturan lebih kuat.

@SimonSapin semakin banyak saya bermain dengan pengalokasi dan koleksi, semakin saya cenderung berpikir bahwa kita belum ingin mengatur koleksi dengan Alloc , karena tergantung pada pengalokasi, koleksi mungkin ingin menawarkan API yang berbeda, kerumitan beberapa operasi berubah, beberapa detail koleksi sebenarnya bergantung pada pengalokasi, dll.

Jadi saya ingin menyarankan cara agar kita bisa membuat kemajuan di sini.

Langkah 1: Pengalokasi heap

Kami dapat membatasi diri pada awalnya untuk mencoba membiarkan pengguna memilih pengalokasi untuk heap (atau pengalokasi sistem / platform / global / toko bebas, atau bagaimanapun Anda lebih suka menamainya) di Rust stabil.

Satu-satunya hal yang awalnya kita parametrize adalah Box , yang hanya perlu mengalokasikan ( new ) dan membatalkan alokasi ( drop ) memori.

Sifat pengalokasi ini awalnya dapat memiliki API yang @alexcrichton usulkan (atau agak diperluas), dan sifat pengalokasi ini, pada malam hari, masih dapat memiliki API yang sedikit diperpanjang untuk mendukung koleksi std:: .

Setelah kami berada di sana, pengguna yang ingin bermigrasi ke stabil akan dapat melakukannya, tetapi mungkin mendapatkan kinerja yang buruk, karena API yang tidak stabil.

Langkah 2: Pengalokasi heap tanpa kinerja yang dicapai

Pada titik itu, kami dapat mengevaluasi kembali pengguna yang tidak dapat pindah ke stabil karena kinerja yang terpukul, dan memutuskan cara memperluas API ini dan menstabilkannya.

Langkah 3 ke N: mendukung pengalokasi khusus dalam koleksi std .

Pertama, ini sulit, jadi mungkin tidak akan pernah terjadi, dan menurut saya tidak pernah terjadi bukanlah hal yang buruk.

Ketika saya ingin mengatur parameter koleksi dengan pengalokasi khusus, saya memiliki masalah kinerja atau masalah kegunaan.

Jika saya memiliki masalah kegunaan, saya biasanya menginginkan API koleksi berbeda yang mengeksploitasi fitur pengalokasi khusus saya, seperti misalnya peti SliceDeque . Memperhatikan koleksi oleh pengalokasi khusus tidak akan membantu saya di sini.

Jika saya mengalami masalah kinerja, akan sangat sulit bagi pengalokasi khusus untuk membantu saya. Saya akan mempertimbangkan Vec di bagian berikutnya saja, karena ini adalah koleksi yang paling sering saya terapkan ulang.

Kurangi jumlah panggilan pengalokasi sistem (Small Vector Optimization)

Jika saya ingin mengalokasikan beberapa elemen di dalam objek Vec untuk mengurangi jumlah panggilan ke pengalokasi sistem, hari ini saya hanya menggunakan SmallVec<[T; M]> . Namun, SmallVec bukanlah Vec :

  • memindahkan Vec adalah O (1) dalam jumlah elemen, tetapi memindahkan SmallVec<[T; M]> adalah O (N) untuk N <M dan O (1) setelahnya,

  • pointer ke Vec elemen tidak valid saat bergerak jika len() <= M tetapi tidak sebaliknya, yaitu, jika len() <= M operasi seperti into_iter perlu memindahkan elemen ke iterator objek itu sendiri, bukan hanya mengambil pointer.

Bisakah kita membuat Vec generik melalui pengalokasi untuk mendukung ini? Segalanya mungkin, tetapi menurut saya biaya yang paling penting adalah:

  • hal itu membuat penerapan Vec lebih kompleks, yang mungkin memengaruhi pengguna yang tidak menggunakan fitur ini
  • dokumentasi Vec akan menjadi lebih kompleks, karena perilaku beberapa operasi akan bergantung pada pengalokasi.

Saya pikir biaya ini tidak dapat diabaikan.

Manfaatkan pola alokasi

Faktor pertumbuhan dari Vec disesuaikan dengan pengalokasi tertentu. Di std kita dapat menyesuaikannya dengan yang umum jemalloc / malloc / ... tetapi jika Anda menggunakan pengalokasi khusus, kemungkinan besar faktor pertumbuhan yang kita pilih secara default tidak akan menjadi yang terbaik untuk kasus penggunaan Anda. Haruskah setiap pengalokasi dapat menentukan faktor pertumbuhan untuk pola alokasi seperti vec? Saya tidak tahu tapi firasat saya mengatakan kepada saya: mungkin tidak.

Manfaatkan fitur tambahan pengalokasi sistem Anda

Misalnya, pengalokasi yang terlalu berkomitmen tersedia di sebagian besar target Tingkat 1 dan Tingkat 2. Dalam sistem mirip Linux dan Macos, pengalokasi heap overcommits secara default, sedangkan Windows API mengekspos VirtualAlloc yang dapat digunakan untuk menyimpan memori (misalnya pada Vec::reserve/with_capacity ) dan menjalankan memori pada push .

Saat ini sifat Alloc tidak menunjukkan cara untuk mengimplementasikan pengalokasi semacam itu di Windows, karena ia tidak memisahkan konsep pengalokasian dan penyimpanan memori (di Linux, pengalokasi yang tidak terlalu berkomitmen dapat diretas di dengan hanya menyentuh setiap halaman sekali). Ini juga tidak mengekspos cara bagi pengalokasi untuk menyatakan apakah itu over-commit atau tidak secara default pada alloc .

Artinya, kita perlu memperpanjang API Alloc untuk mendukung ini untuk Vec , dan itu akan menjadi IMO untuk sedikit kemenangan. Karena ketika Anda memiliki pengalokasi seperti itu, semantik Vec berubah lagi:

  • Vec tidak perlu tumbuh lagi, jadi operasi seperti reserve tidak masuk akal
  • push adalah O(1) bukannya diamortisasi O(1) .
  • iterator ke objek hidup tidak pernah membatalkan selama objek masih hidup, yang memungkinkan beberapa pengoptimalan

Manfaatkan lebih banyak fitur tambahan dari pengalokasi sistem Anda

Beberapa pengalokasi sistem seperti cudaMalloc / cudaMemcpy / ... membedakan antara memori yang disematkan dan yang tidak disematkan, memungkinkan Anda untuk mengalokasikan memori pada ruang alamat yang terpisah (jadi kami memerlukan jenis Pointer terkait di Sifat Alloc), ...

Tetapi menggunakan ini pada koleksi seperti Vec tidak lagi mengubah semantik dari beberapa operasi dengan cara yang halus, seperti apakah mengindeks vektor tiba-tiba memunculkan perilaku yang tidak ditentukan atau tidak, tergantung pada apakah Anda melakukannya dari kernel GPU atau dari host.

Membungkus

Saya pikir mencoba membuat API Alloc yang dapat digunakan untuk parameter semua koleksi (atau bahkan hanya Vec ) itu sulit, mungkin terlalu sulit.

Mungkin setelah kita mendapatkan alokasi global / system / platform / heap / free-store, dan Box , kita dapat memikirkan kembali koleksinya. Mungkin kita dapat menggunakan kembali Alloc , mungkin kita membutuhkan VecAlloc, VecDequeAlloc , HashMapAlloc`, ... atau mungkin kita hanya berkata, "Anda tahu, jika Anda benar-benar membutuhkan ini , cukup salin-tempel koleksi standar ke dalam peti, dan bentuk ke pengalokasi Anda ". Mungkin solusi terbaik adalah membuatnya lebih mudah, dengan memiliki koleksi std di peti sendiri (atau peti) di pembibitan dan hanya menggunakan fitur stabil, mungkin diterapkan sebagai satu set blok bangunan.

Bagaimanapun, saya pikir mencoba untuk mengatasi semua masalah ini di sini sekaligus dan mencoba untuk menemukan sifat Alloc yang baik untuk semuanya terlalu sulit. Kita berada di langkah 0. Saya pikir cara terbaik untuk mencapai langkah 1 dan langkah 2 secara cepat adalah dengan meninggalkan koleksi di luar gambar sampai kita berada di sana.

Setelah kami berada di sana, pengguna yang ingin bermigrasi ke stabil akan dapat melakukannya, tetapi mungkin mendapatkan kinerja yang buruk, karena API yang tidak stabil.

Memilih pengalokasi khusus biasanya tentang meningkatkan kinerja, jadi saya tidak tahu siapa yang akan dilayani oleh stabilisasi awal ini.

Memilih pengalokasi khusus biasanya tentang meningkatkan kinerja, jadi saya tidak tahu siapa yang akan dilayani oleh stabilisasi awal ini.

Semua orang? Setidaknya sekarang. Sebagian besar Beberapa metode yang Anda keluhkan hilang dalam proposal stabilisasi awal ( alloc_excess , misalnya), apakah AFAIK belum digunakan oleh apa pun di pustaka standar. Atau apakah ini berubah baru-baru ini?

Vec (dan pengguna lain RawVec ) gunakan realloc dalam push

@Septianjoko_

Peti jemallocator saat ini mengimplementasikan alokasi_excess, realococ, realloc_excess, usable_size, grow_in_place, dan shrink_in_place

Dari metode ini, AFAIK realloc , grow_in_place , dan shrink_in_place digunakan tetapi grow_in_place hanyalah pembungkus naif lebih dari shrink_in_place untuk jemalloc di Setidaknya jika kita menerapkan implan tidak stabil default grow_in_place dalam hal shrink_in_place dalam sifat Alloc , yang memotongnya menjadi dua metode: realloc dan shrink_in_place .

Memilih pengalokasi khusus biasanya tentang meningkatkan kinerja,

Meskipun ini benar, Anda mungkin mendapatkan lebih banyak kinerja dengan menggunakan pengalokasi yang lebih cocok tanpa metode ini, daripada pengalokasi buruk yang memilikinya.

IIUC kasus penggunaan utama untuk servo adalah menggunakan Firefox jemalloc daripada menggunakan jemalloc kedua, benarkah itu?

Bahkan jika kita menambahkan realloc dan shrink_in_place ke sifat Alloc dalam stabilisasi awal, itu hanya akan menunda keluhan kinerja.

Misalnya, saat kami menambahkan API yang tidak stabil ke sifat Alloc yang akhirnya digunakan oleh koleksi std , Anda tidak akan bisa mendapatkan kinerja yang sama pada stabil daripada yang Anda inginkan. bisa naik setiap malam. Artinya, jika kita menambahkan realloc_excess dan shrink_in_place_excess ke sifat alokasi dan menghasilkan Vec / String / ... menggunakannya, maka kita menstabilkan realloc dan shrink_in_place tidak akan membantu Anda sedikit pun.

IIUC kasus penggunaan utama untuk servo adalah menggunakan Firefox jemalloc daripada menggunakan jemalloc kedua, benarkah itu?

Meskipun mereka berbagi beberapa kode, Firefox dan Servo adalah dua proyek / aplikasi yang terpisah.

Firefox menggunakan mozjemalloc, yang merupakan cabang dari versi lama jemalloc dengan banyak fitur yang ditambahkan. Saya pikir beberapa kode unsafe FFI bergantung pada ketepatan dan kesehatan pada mozjemalloc yang digunakan oleh Rust std.

Servo menggunakan jemalloc yang kebetulan menjadi default Rust untuk executable saat ini, tetapi ada rencana untuk mengubah default itu ke pengalokasi sistem. Servo juga memiliki beberapa kode pelaporan penggunaan memori unsafe yang bergantung pada kesehatan jemalloc yang digunakan. (Meneruskan Vec::as_ptr() menjadi je_malloc_usable_size .)

Servo menggunakan jemalloc yang kebetulan menjadi default Rust untuk executable saat ini, tetapi ada rencana untuk mengubah default itu ke pengalokasi sistem.

Akan lebih baik untuk mengetahui apakah pengalokasi sistem dalam sistem yang menjadi target servo menyediakan API realloc dan shrink_to_fit dioptimalkan seperti yang dilakukan jemalloc? realloc (dan calloc ) sangat umum, tetapi shrink_to_fit ( xallocx ) adalah AFAIK khusus untuk jemalloc . Mungkin solusi terbaik adalah menstabilkan realloc dan alloc_zeroed ( calloc ) dalam implementasi awal, dan meninggalkan shrink_to_fit untuk nanti. Itu seharusnya memungkinkan servo bekerja dengan pengalokasi sistem di sebagian besar platform tanpa masalah kinerja.

Servo juga memiliki beberapa kode pelaporan penggunaan memori yang tidak aman yang bergantung pada kesehatan jemalloc yang digunakan. (Meneruskan Vec :: as_ptr () ke je_malloc_usable_size.)

Seperti yang Anda ketahui, peti jemallocator memiliki API untuk ini. Saya berharap bahwa peti yang mirip dengan jemallocator peti akan muncul untuk pengalokasi lain yang menawarkan API serupa saat cerita pengalokasi global mulai stabil. Saya belum memikirkan apakah API ini termasuk dalam sifat Alloc sama sekali.

Saya tidak berpikir malloc_usable_size harus berada dalam sifat Alloc . Menggunakan #[global_allocator] untuk memastikan pengalokasi apa yang digunakan oleh Vec<T> dan secara terpisah menggunakan fungsi dari peti jemallocator tidak masalah.

@SimonSapin setelah sifat Alloc menjadi stabil, kita mungkin akan memiliki peti seperti jemallocator untuk malloc Linux dan Windows. Peti ini dapat memiliki fitur tambahan untuk mengimplementasikan bagian-bagian yang mereka dapat dari API Alloc tidak stabil (seperti, misalnya, usable_size di atas malloc_usable_size ) dan beberapa hal lain yang bukan bagian dari Alloc API, seperti pelaporan memori di atas mallinfo . Setelah ada peti yang dapat digunakan untuk sistem yang ditargetkan servo, akan lebih mudah untuk mengetahui bagian mana dari sifat Alloc diprioritaskan untuk stabilisasi, dan kami mungkin akan menemukan API baru yang setidaknya harus dicoba untuk beberapa pengalokasi.

@gnzlbg Saya agak skeptis dengan hal-hal di https://github.com/rust-lang/rust/issues/32838#issuecomment -358267292. Meninggalkan semua hal khusus sistem itu, tidak sulit untuk menggeneralisasi koleksi untuk alokasi - saya telah melakukannya. Mencoba memasukkannya sepertinya tantangan tersendiri.

@ SimonSapin Apakah firefox memiliki kebijakan Rust tidak tidak stabil? Saya pikir saya menjadi bingung: Firefox dan Servo menginginkan ini, tetapi jika demikian kasus penggunaan Firefox yang akan menambah tekanan untuk menstabilkan.

@ Lihat itu ^. Saya mencoba membuat perbedaan antara proyek yang membutuhkan vs menginginkan stabil ini, tetapi Servo berada di sisi lain dari kesenjangan itu.

Saya memiliki proyek yang menginginkan ini dan membutuhkannya untuk stabil. Tidak ada yang sangat ajaib tentang Servo atau Firefox sebagai konsumen Rust.

@ Ericson2314 Benar, Firefox menggunakan stabil: https://wiki.mozilla.org/Rust_Update_Policy_for_Firefox. Seperti yang saya jelaskan meskipun ada solusi yang berfungsi hari ini, jadi ini bukan pemblokir nyata untuk apa pun. Akan lebih bagus / lebih kuat untuk menggunakan #[global_allocator] , itu saja.

Servo memang menggunakan beberapa fitur yang tidak stabil, tetapi seperti yang disebutkan, kami mencoba mengubahnya.

FWIW, pengalokasi parametrik sangat berguna untuk mengimplementasikan pengalokasi. Banyak pembukuan yang kurang peka terhadap kinerja menjadi jauh lebih mudah jika Anda dapat menggunakan berbagai struktur data secara internal dan mengaturnya dengan beberapa pengalokasi yang lebih sederhana (seperti bsalloc ). Saat ini, satu-satunya cara untuk melakukannya di lingkungan std adalah memiliki kompilasi dua fase di mana fase pertama digunakan untuk mengatur pengalokasi sederhana Anda sebagai pengalokasi global dan fase kedua digunakan untuk mengompilasi pengalokasi yang lebih besar dan lebih rumit . Di no-std, tidak ada cara untuk melakukannya sama sekali.

@ Erica

Meninggalkan semua hal khusus sistem itu, tidak sulit untuk menggeneralisasi koleksi untuk alokasi - saya telah melakukannya. Mencoba memasukkannya sepertinya tantangan tersendiri.

Apakah Anda memiliki implementasi ArrayVec atau SmallVec di atas Vec + pengalokasi khusus yang dapat saya lihat? Itu adalah poin pertama yang saya sebutkan, dan itu sama sekali tidak spesifik sistem. Bisa dibilang itu akan menjadi dua pengalokasi paling sederhana yang bisa dibayangkan, satu hanya larik mentah sebagai penyimpanan, dan yang lainnya dapat dibangun di atas yang pertama dengan menambahkan fallback ke Heap setelah larik kehabisan kapasitas. Perbedaan utamanya adalah bahwa pengalokasi tersebut tidak "global", tetapi masing-masing dari Vec s memiliki pengalokasi sendiri yang tidak bergantung pada pengalokasi lainnya, dan pengalokasi ini bersifat stateful.

Juga, saya tidak berdebat untuk tidak pernah melakukan ini. Saya hanya menyatakan bahwa ini sangat sulit: C ++ telah mencoba selama 30 tahun dengan hanya sebagian yang berhasil: pengalokasi GPU dan pengalokasi GC berfungsi karena jenis penunjuk umum, tetapi menerapkan ArrayVec dan SmallVec di atas Vec tidak menghasilkan abstraksi tanpa biaya di lahan C ++ ( P0843r1 membahas beberapa masalah untuk ArrayVec secara rinci).

Jadi saya lebih suka jika kita akan mengejar ini setelah kita menstabilkan bagian yang memberikan sesuatu yang berguna selama ini tidak mengejar pengalokasi koleksi khusus di masa depan.


Saya berbicara sedikit dengan @SimonSapin di IRC dan jika kami memperpanjang proposal stabilisasi awal dengan realloc dan alloc_zeroed , maka Rust di Firefox (yang hanya menggunakan Rust yang stabil) akan dapat digunakan mozjemalloc sebagai pengalokasi global di Rust stabil tanpa perlu peretasan tambahan. Seperti yang disebutkan oleh @SimonSapin , Firefox saat ini memiliki solusi yang bisa diterapkan untuk hari ini, jadi meskipun ini akan bagus, tampaknya itu tidak menjadi prioritas yang sangat tinggi.

Namun, kita bisa mulai dari sana, dan begitu kita berada di sana, pindahkan servo ke stabil #[global_allocator] tanpa kehilangan kinerja.


@tokopedia

FWIW, pengalokasi parametrik sangat berguna untuk mengimplementasikan pengalokasi.

Bisakah Anda menjelaskan lebih lanjut tentang apa yang Anda maksud? Apakah ada alasan mengapa Anda tidak dapat mengatur pengalokasi khusus Anda dengan sifat Alloc ? Atau ciri pengalokasi khusus Anda sendiri dan cukup terapkan ciri Alloc pada pengalokasi akhir (kedua ciri ini tidak harus sama)?

Saya tidak mengerti dari mana kasus penggunaan "SmallVec = Vec + pengalokasi khusus" berasal. Ini bukan sesuatu yang sering saya lihat sebelumnya (baik di Rust maupun dalam konteks lain), justru karena ia memiliki banyak masalah serius. Ketika saya berpikir tentang "meningkatkan kinerja dengan pengalokasi khusus", itu sama sekali bukan yang saya pikirkan.

Melihat lebih dari Layout API, saya bertanya-tanya tentang perbedaan dalam penanganan kesalahan antara from_size_align dan align_to , di mana yang pertama mengembalikan None jika terjadi kesalahan , sementara yang terakhir panik (!).

Bukankah akan lebih membantu dan konsisten untuk menambahkan LayoutErr enum yang ditentukan dan informatif dan mengembalikan Result<Layout, LayoutErr> dalam kedua kasus (dan mungkin menggunakannya untuk fungsi lain yang saat ini mengembalikan Option juga)?

@tokopedia

Saya tidak mengerti dari mana kasus penggunaan "SmallVec = Vec + pengalokasi khusus" berasal. Ini bukan sesuatu yang sering saya lihat sebelumnya (baik di Rust maupun dalam konteks lain), justru karena ia memiliki banyak masalah serius. Ketika saya berpikir tentang "meningkatkan kinerja dengan pengalokasi khusus", itu sama sekali bukan yang saya pikirkan.

Ada dua cara independen untuk menggunakan pengalokasi di Rust dan C ++: pengalokasi sistem, digunakan oleh semua pengalokasian secara default, dan sebagai argumen tipe untuk koleksi yang diparameterisasi oleh beberapa sifat pengalokasi, sebagai cara untuk membuat objek dari koleksi tertentu yang menggunakan pengalokasi tertentu (yang dapat menjadi pengalokasi sistem atau tidak).

Berikut ini fokus hanya pada kasus penggunaan kedua ini: menggunakan koleksi dan tipe pengalokasi untuk membuat objek dari koleksi itu yang menggunakan pengalokasi tertentu.

Dalam pengalaman saya dengan C ++, parametrizing koleksi dengan pengalokasi melayani dua kasus penggunaan:

  • meningkatkan kinerja objek koleksi dengan membuat koleksi menggunakan pengalokasi khusus yang ditargetkan pada pola alokasi tertentu, dan / atau
  • menambahkan fitur baru ke koleksi yang memungkinkannya melakukan sesuatu yang sebelumnya tidak dapat dilakukan.

Menambahkan fitur baru ke koleksi

Ini adalah kasus penggunaan pengalokasi yang saya lihat di basis kode C ++ 99% dari waktu. Fakta bahwa menambahkan fitur baru ke suatu koleksi meningkatkan kinerja, menurut saya, kebetulan. Secara khusus, tidak ada pengalokasi berikut yang meningkatkan kinerja dengan menargetkan pola alokasi. Mereka melakukannya dengan menambahkan fitur, yang dalam beberapa kasus, seperti yang disebutkan @ Ericson2314 , dapat dianggap "khusus sistem". Ini adalah beberapa contohnya:

  • pengalokasi tumpukan untuk melakukan pengoptimalan buffer kecil (lihat makalah stack_alloc Howard Hinnant). Mereka membiarkan Anda menggunakan std::vector atau flat_{map,set,multimap,...} dan dengan melewatkannya sebuah pengalokasi khusus yang Anda tambahkan dalam optimasi buffer kecil dengan ( SmallVec ) atau tanpa ( ArrayVec ) tumpukan mundur. Ini memungkinkan, misalnya, meletakkan koleksi dengan elemen-elemennya di tumpukan atau memori statis (di mana ia seharusnya menggunakan heap).

  • arsitektur memori tersegmentasi (seperti target x86 pointer lebar 16-bit dan GPGPU). Misalnya, C ++ 17 Parallel STL adalah, selama C ++ 14, Spesifikasi Teknis Paralel. Pustaka prekursor dari penulis yang sama adalah pustaka Thrust NVIDIA, yang menyertakan pengalokasi untuk memungkinkan klas kontainer menggunakan memori GPGPU (mis. Thrust :: device_malloc_allocator ) atau memori yang disematkan (mis. Thrust :: pinned_allocator ; memori yang disematkan memungkinkan transfer yang lebih cepat antara perangkat host di beberapa kasus).

  • pengalokasi untuk memecahkan masalah terkait paralelisme, seperti berbagi palsu (misalnya Intel Thread Building Block cache_aligned_allocator ) atau persyaratan penyelarasan yang berlebihan dari jenis SIMD (misalnya aligned_allocator Eigen3).

  • memori bersama antarproses: Boost.Interprocess memiliki pengalokasi yang mengalokasikan memori koleksi menggunakan fasilitas memori bersama antarproses OS (misalnya seperti memori bersama Sistem V). Ini memungkinkan untuk secara langsung menggunakan kontainer std untuk mengelola memori yang digunakan untuk berkomunikasi antara proses yang berbeda.

  • pengumpulan sampah: Perpustakaan alokasi memori yang ditangguhkan Herb Sutter menggunakan tipe penunjuk yang ditentukan pengguna untuk mengimplementasikan pengalokasi yang mengumpulkan memori sampah. Jadi, misalnya, ketika vektor tumbuh, potongan memori lama dipertahankan hidup sampai semua petunjuk ke memori itu telah dihancurkan, menghindari pembatalan iterator.

  • pengalokasi berinstrumen: Blsma_testallocator dari Perpustakaan Perangkat Lunak Bloomberg memungkinkan Anda untuk mencatat pola alokasi / dealokasi memori (dan C ++ - konstruksi / penghancuran objek tertentu) dari objek tempat Anda menggunakannya. Anda tidak tahu apakah Vec dialokasikan setelah reserve ? Colokkan pengalokasi seperti itu, dan mereka akan memberi tahu Anda jika itu terjadi. Beberapa dari pengalokasi ini memungkinkan Anda menamainya, sehingga Anda dapat menggunakannya pada banyak objek dan mendapatkan log yang mengatakan objek mana yang melakukan apa.

Ini adalah jenis pengalokasi yang paling sering saya lihat di alam liar di C ++. Seperti yang saya sebutkan sebelumnya, fakta bahwa mereka meningkatkan kinerja dalam beberapa kasus, menurut saya, kebetulan. Bagian yang penting adalah tidak satupun dari mereka mencoba menargetkan pola alokasi tertentu.

Pola alokasi penargetan untuk meningkatkan kinerja.

AFAIK tidak ada pengalokasi C ++ yang banyak digunakan yang melakukan ini dan saya akan menjelaskan mengapa menurut saya ini sebentar lagi. Pustaka berikut menargetkan kasus penggunaan ini:

Namun, pustaka ini tidak benar-benar menyediakan pengalokasi tunggal untuk kasus penggunaan tertentu. Sebaliknya, mereka menyediakan blok penyusun pengalokasi yang dapat Anda gunakan untuk membuat pengalokasi khusus yang ditargetkan pada pola alokasi tertentu di bagian tertentu dari sebuah aplikasi.

Saran umum yang saya ingat dari hari-hari C ++ saya adalah untuk "jangan menggunakannya" (mereka adalah pilihan terakhir) karena:

  • mencocokkan kinerja pengalokasi sistem sangat sulit, mengalahkannya sangat sulit,
  • kemungkinan pola alokasi memori aplikasi orang lain cocok dengan Anda sangat kecil, jadi Anda benar-benar perlu mengetahui pola alokasi Anda dan mengetahui blok penyusun pengalokasi apa yang Anda perlukan untuk mencocokkannya
  • mereka tidak portabel karena vendor yang berbeda memiliki implementasi pustaka standar C ++ berbeda yang menggunakan pola alokasi berbeda; vendor biasanya menargetkan implementasinya pada pengalokasi sistem mereka. Artinya, solusi yang disesuaikan untuk satu vendor mungkin berkinerja buruk (lebih buruk daripada pengalokasi sistem) di vendor lain.
  • Ada banyak alternatif yang dapat digunakan seseorang sebelum mencoba menggunakan ini: menggunakan koleksi yang berbeda, menyimpan memori, ... Sebagian besar alternatif adalah usaha yang lebih rendah dan dapat memberikan kemenangan yang lebih besar.

Ini tidak berarti bahwa pustaka untuk kasus penggunaan ini tidak berguna. Ya, itulah sebabnya perpustakaan seperti foonathan / memory berkembang

Membungkus

IMO Saya pikir sangat bagus bahwa model pengalokasi C ++, meskipun jauh dari sempurna, mendukung kedua kasus penggunaan, dan saya pikir jika koleksi std Rust akan diparameterisasi oleh pengalokasi, mereka harus mendukung kedua kasus penggunaan juga, karena setidaknya di Pengalokasi C ++ untuk kedua kasus ternyata berguna.

Saya hanya berpikir masalah ini sedikit ortogonal untuk dapat menyesuaikan pengalokasi global / system / platform / default / heap / free-store dari aplikasi tertentu, dan bahwa mencoba untuk menyelesaikan kedua masalah pada saat yang sama dapat menunda solusi untuk satu aplikasi. dari mereka yang tidak perlu.

Apa yang ingin dilakukan beberapa pengguna dengan koleksi yang diparameterisasi oleh pengalokasi mungkin jauh berbeda dari apa yang ingin dilakukan pengguna lain. Jika @rkruppe dimulai dari "pola alokasi yang cocok" dan saya mulai dari "mencegah berbagi palsu" atau "menggunakan pengoptimalan buffer kecil dengan penggantian heap", akan sulit untuk pertama-tama, memahami kebutuhan satu sama lain, dan kedua, tiba pada solusi yang cocok untuk keduanya.

@gnzlbg Terima kasih atas tulisan yang komprehensif. Sebagian besar tidak menjawab pertanyaan awal saya dan saya tidak setuju dengan beberapa di antaranya, tetapi ada baiknya dijabarkan sehingga kita tidak saling berbicara.

Pertanyaan saya secara khusus tentang aplikasi ini:

pengalokasi tumpukan untuk melakukan pengoptimalan buffer kecil (lihat makalah stack_alloc Howard Hinnant). Mereka memungkinkan Anda menggunakan std :: vector atau flat_ {map, set, multimap, ...} dan dengan meneruskan pengalokasi khusus yang Anda tambahkan dalam pengoptimalan buffer kecil dengan (SmallVec) atau tanpa penggantian heap (ArrayVec). Ini memungkinkan, misalnya, meletakkan koleksi dengan elemen-elemennya di tumpukan atau memori statis (di mana ia seharusnya menggunakan heap).

Membaca tentang stack_alloc, saya sekarang menyadari bagaimana cara kerjanya. Ini bukan apa yang biasanya orang maksud dengan SmallVec (di mana buffer disimpan secara inline dalam koleksi), itulah sebabnya saya melewatkan opsi itu, tetapi itu merupakan masalah samping karena harus memperbarui pointer ketika koleksi bergerak (dan juga membuat gerakan itu lebih murah ). Perhatikan juga bahwa short_alloc memungkinkan beberapa koleksi untuk berbagi satu arena , yang membuatnya semakin tidak seperti tipe SmallVec pada umumnya. Ini lebih seperti pengalokasi linear / bump-pointer dengan fallback yang anggun ke alokasi heap saat kehabisan ruang yang dialokasikan.

Saya tidak setuju bahwa pengalokasi semacam ini dan cache_aligned_allocator pada dasarnya menambahkan fitur baru. Mereka digunakan secara berbeda, dan bergantung pada definisi Anda tentang "pola alokasi", mereka mungkin tidak dioptimalkan untuk pola alokasi tertentu. Namun, mereka pasti mengoptimalkan untuk kasus penggunaan tertentu dan mereka tidak memiliki perbedaan perilaku yang signifikan dari pengalokasi heap tujuan umum.

Namun saya setuju bahwa kasus penggunaan seperti alokasi memori yang ditangguhkan dari Sutter, yang secara substansial mengubah apa arti "pointer", adalah aplikasi terpisah yang mungkin memerlukan desain terpisah jika kita ingin mendukungnya.

Membaca tentang stack_alloc, saya sekarang menyadari bagaimana cara kerjanya. Ini bukan apa yang biasanya orang maksud dengan SmallVec (di mana buffer disimpan secara inline dalam koleksi), itulah sebabnya saya melewatkan opsi itu, tetapi itu merupakan masalah samping karena harus memperbarui pointer ketika koleksi bergerak (dan juga membuat gerakan itu lebih murah ).

Saya menyebutkan stack_alloc karena ini adalah satu-satunya pengalokasi dengan "kertas", tetapi dirilis pada tahun 2009 dan mendahului C ++ 11 (C ++ 03 tidak mendukung pengalokasi stateful dalam koleksi).

Singkatnya, cara kerjanya di C ++ 11 (yang mendukung pengalokasi stateful) adalah:

  • toko std :: vector sebuah Allocator objek di dalamnya seperti Rust RawVec tidak .
  • antarmuka Allocator memiliki properti tidak jelas yang disebut Allocator :: propagate_on_container_move_assignment (POCMA mulai sekarang) yang dapat disesuaikan oleh pengalokasi yang ditentukan pengguna; properti ini adalah true secara default. Jika properti ini adalah false , pada pemindahan tugas, pengalokasi tidak dapat disebarkan, jadi koleksi diperlukan oleh standar untuk memindahkan setiap elemennya ke penyimpanan baru secara manual.

Jadi ketika vektor dengan pengalokasi sistem dipindahkan, pertama penyimpanan untuk vektor baru di tumpukan dialokasikan, kemudian pengalokasi dipindahkan (yang berukuran nol), dan kemudian 3 penunjuk dipindahkan, yang masih valid. Langkah tersebut adalah O(1) .

OTOHO, ketika vektor dengan POCMA == true pengalokasi dipindahkan, pertama-tama penyimpanan untuk vektor baru pada tumpukan dialokasikan dan diinisialisasi dengan vektor kosong, kemudian koleksi lama adalah drain ed ke dalam yang baru, sehingga yang lama kosong, dan yang baru penuh. Ini memindahkan setiap elemen koleksi satu per satu, menggunakan operator tugas pemindahannya. Langkah ini adalah O(N) dan memperbaiki pointer internal elemen. Akhirnya, koleksi asli yang sekarang kosong dibuang. Perhatikan bahwa ini terlihat seperti klon, tetapi bukan karena elemen itu sendiri tidak digandakan, tetapi dipindahkan dalam C ++.

Apakah itu masuk akal?

Masalah utama dengan pendekatan ini di C ++ adalah:

  • implementasi kebijakan pertumbuhan vektor didefinisikan
  • API pengalokasi tidak memiliki _excess metode
  • kombinasi dari dua masalah di atas berarti bahwa jika Anda mengetahui bahwa vektor Anda dapat menampung paling banyak 9 elemen, Anda tidak dapat memiliki pengalokasi tumpukan yang dapat menampung 9 elemen, karena vektor Anda mungkin mencoba bertambah jika memiliki 8 dengan faktor pertumbuhan sebesar 1,5 sehingga Anda perlu memperkirakan dan mengalokasikan ruang untuk 18 elemen.
  • kompleksitas operasi vektor berubah bergantung pada properti pengalokasi (POCMA hanyalah salah satu dari banyak properti yang dimiliki C ++ Allocator API; penulisan pengalokasi C ++ tidak sepele). Hal ini membuat penentuan API vektor menjadi sulit karena terkadang menyalin atau memindahkan elemen antara pengalokasi berbeda dari jenis yang sama memiliki biaya tambahan, yang mengubah kompleksitas operasi. Itu juga membuat membaca spesifikasi sangat menyakitkan. Banyak sumber dokumentasi online seperti cppreference menempatkan kasus umum di muka, dan detail yang tidak jelas tentang apa yang berubah jika satu properti pengalokasi benar atau salah dalam huruf kecil untuk menghindari gangguan 99% pengguna dengannya.

Ada banyak orang yang bekerja untuk meningkatkan API pengalokasi C ++ untuk memperbaiki masalah ini, misalnya, dengan menambahkan metode _excess dan menjamin bahwa koleksi yang sesuai standar menggunakannya.

Saya tidak setuju bahwa pengalokasi dan cache_aligned_allocator semacam ini pada dasarnya menambahkan fitur baru.

Mungkin yang saya maksud adalah mereka mengizinkan Anda untuk menggunakan koleksi std dalam situasi atau untuk jenis yang tidak dapat Anda gunakan sebelumnya. Misalnya, di C ++ Anda tidak dapat meletakkan elemen vektor di segmen memori statis biner Anda tanpa sesuatu seperti pengalokasi tumpukan (namun Anda dapat menulis koleksi Anda sendiri yang melakukannya). OTOH, standar C ++ tidak mendukung tipe over-aligned seperti tipe SIMD, dan jika Anda mencoba mengalokasikan heap dengan new Anda akan memanggil perilaku tidak terdefinisi (Anda perlu menggunakan posix_memalign atau yang serupa) . Menggunakan objek biasanya memanifestasikan perilaku tidak terdefinisi melalui segfault (*). Hal-hal seperti aligned_allocator memungkinkan Anda untuk mengalokasikan heap jenis ini, dan bahkan meletakkannya di koleksi std, tanpa memanggil perilaku tidak terdefinisi, dengan menggunakan pengalokasi berbeda. Tentu pengalokasi baru akan memiliki pola alokasi yang berbeda (pengalokasi ini pada dasarnya menyelaraskan semua memori btw ...), tetapi tujuan orang menggunakannya adalah untuk dapat melakukan sesuatu yang tidak dapat mereka lakukan sebelumnya.

Jelas, Rust bukanlah C ++. Dan C ++ memiliki masalah yang tidak dimiliki Rust (dan sebaliknya). Pengalokasi yang menambahkan fitur baru di C ++ mungkin tidak diperlukan di Rust, yang, misalnya, tidak memiliki masalah dengan jenis SIMD.

(*) Pengguna Eigen3 sangat menderita, karena untuk menghindari perilaku tidak terdefinisi saat menggunakan container C ++ dan STL, Anda perlu menjaga container dari jenis SIMD, atau jenis yang berisi jenis SIMD ( dokumen Eigen3 ) dan juga Anda perlu menjaga diri dari pernah menggunakan new pada tipe Anda dengan membebani operator new untuk mereka ( lebih banyak dokumen Eigen3 ).

@gnzlbg thanks, saya juga bingung dengan contoh smallvec. Itu akan membutuhkan tipe yang tidak dapat dipindahkan dan semacam alloca di Rust --- dua RFC dalam peninjauan dan kemudian lebih banyak pekerjaan tindak lanjut --- jadi saya tidak ragu untuk membicarakannya untuk saat ini. Strategi smallvec yang ada untuk selalu menggunakan semua ruang tumpukan yang Anda perlukan tampaknya baik-baik saja untuk saat ini.

Saya juga setuju dengan @rkruppe bahwa dalam daftar Anda yang telah direvisi, kemampuan baru pengalokasi tidak perlu diketahui oleh koleksi menggunakan pengalokasi. Kadang-kadang Collection<Allocator> memiliki properti baru (katakanlah seluruhnya ada dalam memori yang disematkan) tetapi itu hanya konsekuensi alami dari penggunaan pengalokasi.

Satu-satunya pengecualian di sini yang saya lihat adalah pengalokasi yang hanya mengalokasikan satu ukuran / jenis (yang NVidia melakukan ini, seperti halnya pengalokasi pelat). Kita bisa memiliki ciri ObjAlloc<T> yang diterapkan selimut untuk pengalokasi normal: impl<A: Alloc, T> ObjAlloc<T> for A . Kemudian, koleksi akan menggunakan batas ObjAlloc jika mereka hanya perlu mengalokasikan beberapa item. Tapi, saya merasa agak konyol bahkan mengungkit ini karena seharusnya bisa dilakukan mundur secara compatibly nanti.

Apakah itu masuk akal?

Tentu tetapi itu tidak terlalu relevan dengan Rust karena kami tidak memiliki konstruktor bergerak. Jadi pengalokasi (bergerak) yang secara langsung berisi memori yang diberikan petunjuk tidak mungkin, titik.

Misalnya, di C ++ Anda tidak dapat meletakkan elemen vektor di segmen memori statis biner Anda tanpa sesuatu seperti pengalokasi tumpukan (namun Anda dapat menulis koleksi Anda sendiri yang melakukannya).

Ini bukanlah perubahan perilaku. Ada banyak alasan yang valid untuk mengontrol di mana koleksi mendapatkan memorinya, tetapi semuanya terkait dengan "eksternalitas" seperti kinerja, skrip penaut, kontrol atas seluruh tata letak memori program, dll.

Hal-hal seperti aligned_allocator memungkinkan Anda untuk mengalokasikan heap jenis ini, dan bahkan meletakkannya di koleksi std, tanpa meminta perilaku tidak ditentukan, dengan menggunakan pengalokasi yang berbeda.

Inilah sebabnya mengapa saya secara khusus menyebutkan cache_aligned_allocator TBB dan bukan aligned_allocator Eigen. cache_aligned_allocator tampaknya tidak menjamin penyelarasan tertentu dalam dokumentasinya (hanya mengatakan bahwa "biasanya" 128 byte), dan bahkan jika itu dilakukan biasanya tidak akan digunakan untuk tujuan ini (karena penyelarasannya mungkin terlalu besar untuk umum Jenis SIMD dan terlalu kecil untuk hal-hal seperti DMA sesuai halaman). Tujuannya, seperti yang Anda nyatakan, adalah untuk menghindari berbagi palsu.

@bayu_joo

FWIW, pengalokasi parametrik sangat berguna untuk mengimplementasikan pengalokasi.

Bisakah Anda menjelaskan lebih lanjut tentang apa yang Anda maksud? Adakah alasan mengapa Anda tidak dapat mengatur pengalokasi kustom Anda dengan sifat Alloc? Atau sifat pengalokasi khusus Anda sendiri dan cukup terapkan ciri Alokasi pada pengalokasi akhir (kedua ciri ini tidak harus sama)?

Saya pikir saya tidak jelas; izinkan saya mencoba menjelaskan dengan lebih baik. Katakanlah saya menerapkan pengalokasi yang ingin saya gunakan:

  • Sebagai pengalokasi global
  • Di lingkungan no-std

Dan katakanlah saya ingin menggunakan Vec untuk mengimplementasikan pengalokasi ini. Saya tidak bisa langsung menggunakan Vec seperti yang ada saat ini karena

  • Jika saya pengalokasi global, maka menggunakannya hanya akan memperkenalkan ketergantungan rekursif pada diri saya sendiri
  • Jika saya berada di lingkungan no-std, tidak ada Vec seperti yang ada saat ini

Jadi, yang saya butuhkan adalah dapat menggunakan Vec yang diparameterisasi pada pengalokasi lain yang saya gunakan secara internal untuk pembukuan internal sederhana. Ini adalah tujuan dari bsalloc (dan sumber namanya - digunakan untuk bootstrap pengalokasi lain).

Di elfmalloc, kami masih dapat menjadi pengalokasi global dengan:

  • Saat mengompilasi diri sendiri, kompilasi jemalloc secara statis sebagai pengalokasi global
  • Menghasilkan file objek bersama yang dapat dimuat secara dinamis oleh program lain

Perhatikan bahwa dalam kasus ini, penting bagi kita untuk tidak mengkompilasi diri kita sendiri menggunakan pengalokasi sistem sebagai pengalokasi global karena kemudian, setelah dimuat, kita akan memperkenalkan kembali ketergantungan rekursif karena, pada titik itu, kita adalah pengalokasi sistem.

Tapi itu tidak berhasil jika:

  • Seseorang ingin menggunakan kami sebagai pengalokasi global di Rust dengan cara "resmi" (bukan dengan membuat file objek bersama terlebih dahulu)
  • Kami berada di lingkungan standar

OTOH, standar C ++ tidak mendukung tipe over-aligned seperti tipe SIMD, dan jika Anda mencoba mengalokasikan heap dengan yang baru, Anda akan memanggil perilaku tidak terdefinisi (Anda perlu menggunakan posix_memalign atau serupa).

Karena sifat kita saat ini Alloc mengambil kesejajaran sebagai parameter, saya berasumsi kelas masalah ini (masalah "Saya tidak dapat bekerja tanpa perataan berbeda") hilang untuk kita?

@gnzlbg - artikel lengkap (terima kasih) tetapi tidak ada kasus penggunaan yang mencakup memori yang terus-menerus *.

Kasus penggunaan ini harus dipertimbangkan. Secara khusus, ini sangat memengaruhi hal yang benar untuk dilakukan: -

  • Lebih dari satu pengalokasi sedang digunakan, dan terutama, bila pengalokasi digunakan untuk memori persisten, pengalokasi tidak akan beberapa pengalokasi memori persisten)
  • Biaya 'penerapan ulang' koleksi standar tinggi, dan menyebabkan kode yang tidak kompatibel dengan pustaka pihak ketiga.
  • Umur pengalokasi belum tentu 'static .
  • Objek yang disimpan dalam memori persisten membutuhkan status tambahan yang harus diisi dari heap, yaitu mereka memerlukan status diinisialisasi ulang. Hal ini terutama berlaku untuk mutex dan sejenisnya. Apa yang tadinya bisa dibuang tidak lagi dibuang.

Rust memiliki peluang luar biasa untuk mengambil inisiatif di sini, dan menjadikannya platform kelas satu untuk apa yang akan menggantikan HD, SSD, dan bahkan penyimpanan yang terpasang ke PCI.

* Tidak mengherankan, sungguh, karena hingga saat ini agak istimewa. Sekarang didukung secara luas di Linux, FreeBSD dan Windows.

@bayu_joo

Ini bukanlah tempat untuk melatih ingatan yang terus-menerus. Anda bukan satu-satunya aliran pemikiran mengenai antarmuka ke memori persisten - misalnya, mungkin ternyata pendekatan yang berlaku hanyalah memperlakukannya seperti disk yang lebih cepat, untuk alasan integritas data.

Jika Anda memiliki kasus penggunaan untuk menggunakan memori persisten dengan cara ini mungkin akan lebih baik untuk membuat kasus itu di tempat lain terlebih dahulu. Buat prototipe itu, buat beberapa perubahan yang lebih konkret pada antarmuka pengalokasi, dan idealnya membuat kasus bahwa perubahan itu sepadan dengan dampaknya pada kasus rata-rata.

@tokopedia

Saya tidak setuju. Ini persis seperti tempatnya. Saya ingin menghindari keputusan yang dibuat yang menciptakan desain yang merupakan hasil dari fokus yang terlalu sempit dan mencari bukti.

Intel PMDK saat ini - di mana banyak upaya untuk dukungan ruang pengguna tingkat rendah difokuskan - mendekati itu jauh lebih sebagai memori biasa yang dialokasikan dengan pointer - memori yang mirip dengan itu melalui mmap , katakanlah. Memang, jika seseorang ingin bekerja dengan memori persisten di Linux, maka, saya percaya itu adalah satu-satunya tempat panggilan Anda saat ini. Intinya, salah satu toolkit paling canggih untuk menggunakannya - yang berlaku jika Anda mau - memperlakukannya sebagai memori yang dialokasikan.

Adapun untuk membuat prototipe - yah, itulah yang saya katakan telah saya lakukan: -

Saya baru-baru ini mengerjakan pembungkus Rust untuk pengalokasi memori persisten (khususnya, libpmemcto).

(Anda dapat menggunakan versi awal dari peti saya di https://crates.io/crates/nvml . Ada lebih banyak eksperimen dalam kontrol sumber di modul cto_pool ).

Prototipe saya dibuat dengan memikirkan apa yang diperlukan untuk menggantikan mesin penyimpanan data dalam sistem berskala besar dunia nyata. Pola pikir serupa ada di balik banyak proyek open source saya. Saya telah menemukan selama bertahun-tahun perpustakaan terbaik, seperti standar terbaik, yang memperoleh penggunaan nyata _from_.

Tidak ada yang mencoba menyesuaikan pengalokasi dunia nyata ke antarmuka saat ini. Sejujurnya, pengalaman menggunakan antarmuka Alloc , lalu menyalin seluruh Vec , lalu mengubahnya, sangat menyakitkan. Banyak tempat mengasumsikan bahwa pengalokasi tidak dikirimkan, misalnya Vec::new() .

Dalam melakukannya, saya membuat beberapa pengamatan dalam komentar asli saya tentang apa yang diperlukan dari pengalokasi, dan apa yang akan dibutuhkan dari pengguna pengalokasi tersebut. Saya pikir itu sangat valid pada utas diskusi tentang antarmuka pengalokasi.

Kabar baiknya adalah 3 poin pertama Anda dari https://github.com/rust-lang/rust/issues/32838#issuecomment -358940992 dibagikan oleh kasus penggunaan lainnya.

Saya hanya ingin menambahkan bahwa saya tidak menambahkan memori non-volatile ke dalam daftar
karena daftar tersebut mencantumkan kasus penggunaan pengalokasi parameter penampung dalam
dunia C ++ yang "banyak digunakan", setidaknya berdasarkan pengalaman saya (itu
pengalokasi yang saya sebutkan sebagian besar berasal dari perpustakaan yang sangat populer yang digunakan oleh banyak orang).
Sementara saya tahu tentang upaya Intel SDK (beberapa perpustakaan mereka
target C ++) Saya pribadi tidak tahu proyek apa pun yang menggunakannya (apakah mereka punya
sebuah pengalokasi yang dapat digunakan dengan std :: vector? Saya tidak tahu). Ini tidak
berarti mereka tidak digunakan atau penting. Saya tertarik untuk mengetahuinya
tentang ini, tetapi poin utama dari posting saya adalah parametrizing itu
pengalokasi oleh kontainer sangat kompleks, dan kita harus mencoba membuatnya
maju dengan pengalokasi sistem tanpa menutup pintu apa pun untuk kontainer
(tapi kita harus mengatasinya nanti).

Pada Sun 21. Jan 2018 pukul 17:36, John Ericson [email protected] menulis:

Kabar baiknya adalah 3 poin pertama Anda dari # 32838 (komentar)
https://github.com/rust-lang/rust/issues/32838#issuecomment-358940992
dibagikan oleh kasus penggunaan lain.

-
Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/rust-lang/rust/issues/32838#issuecomment-359261305 ,
atau nonaktifkan utasnya
https://github.com/notifications/unsubscribe-auth/AA3Npk95PZBZcm7tknNp_Cqrs_3T1UkEks5tM2ekgaJpZM4IDYUN
.

Saya mencoba membaca sebagian besar dari apa yang telah ditulis jadi ini mungkin sudah ada di sini dan dalam hal ini saya minta maaf jika saya melewatkannya tetapi begini:

Sesuatu yang cukup umum untuk game (dalam C / C ++) adalah menggunakan "alokasi awal per bingkai" Artinya, ada pengalokasi linier / lonjakan yang digunakan untuk alokasi yang aktif selama periode waktu tertentu (dalam bingkai permainan) dan kemudian "dihancurkan".

Hancur dalam hal ini berarti Anda mengatur ulang pengalokasi kembali ke posisi awalnya. Tidak ada "penghancuran" objek sama sekali karena objek ini harus berjenis POD (sehingga tidak ada destruktor yang dijalankan)

Saya ingin tahu apakah sesuatu seperti ini akan cocok dengan desain pengalokasi saat ini di Rust?

(sunting: Seharusnya TIDAK ADA penghancuran objek)

@oon

Sesuatu yang cukup umum untuk game (dalam C / C ++) adalah menggunakan "alokasi awal per bingkai" Artinya, ada pengalokasi linier / lonjakan yang digunakan untuk alokasi yang aktif selama periode waktu tertentu (dalam bingkai permainan) dan kemudian "dihancurkan".

Hancur dalam hal ini berarti Anda mengatur ulang pengalokasi kembali ke posisi awalnya. Ada "penghancuran" objek sama sekali karena objek ini harus berjenis POD (sehingga tidak ada destruktor yang dijalankan)

Harus bisa dilakukan. Di luar kepalaku, Anda memerlukan satu objek untuk arena itu sendiri dan objek lain yang merupakan pegangan per bingkai di arena. Kemudian, Anda dapat menerapkan Alloc untuk pegangan itu, dan dengan asumsi Anda menggunakan pembungkus aman tingkat tinggi untuk alokasi (misalnya, bayangkan Box menjadi parametrik pada Alloc ), masa pakai akan memastikan bahwa semua objek yang dialokasikan dijatuhkan sebelum pegangan per frame dijatuhkan. Perhatikan bahwa dealloc akan tetap dipanggil untuk setiap objek, tetapi jika dealloc adalah no-op, maka seluruh logika drop-and-deallocate mungkin sepenuhnya atau sebagian besar dioptimalkan.

Anda juga dapat menggunakan jenis penunjuk pintar khusus yang tidak menerapkan Drop , yang akan membuat banyak hal lebih mudah di tempat lain.

Terima kasih! Saya salah ketik di postingan asli saya. Ini untuk mengatakan bahwa tidak ada perusakan objek.

Untuk orang yang tidak ahli dalam pengalokasi, dan tidak dapat mengikuti utas ini, apa konsensus saat ini: apakah kami berencana untuk mendukung pengalokasi khusus untuk jenis koleksi stdlib?

@alexreg Saya tidak yakin apa rencana terakhirnya, tetapi ada 0 kesulitan teknis yang dikonfirmasi untuk melakukannya. OTOH kami tidak memiliki cara yang baik untuk kemudian mengeksposnya di std karena variabel tipe default dicurigai, tetapi saya tidak punya masalah dengan hanya membuatnya menjadi alloc -hanya untuk saat ini jadi kami dapat membuat kemajuan di sisi lib tanpa hambatan.

@ Ericson2314 Oke, senang mendengarnya. Apakah variabel jenis default sudah diterapkan? Atau mungkin pada tahap RFC? Seperti yang Anda katakan, jika mereka hanya dibatasi pada hal-hal yang berkaitan dengan alokasi / std::heap , semuanya akan baik-baik saja.

Menurut saya, AllocErr seharusnya Error. Ini akan lebih konsisten dengan modul lain (mis. Io).

impl Error for AllocError mungkin masuk akal dan tidak menyakitkan, tetapi secara pribadi saya menganggap sifat Error tidak berguna.

Saya melihat fungsi Layout :: from_size_align hari ini, dan " align tidak boleh melebihi 2 ^ 31 (yaitu 1 << 31 )," batasan tidak masuk akal bagi saya. Dan git menyalahkan menunjuk ke # 30170.

Saya harus mengatakan itu adalah pesan komit yang cukup menipu di sana, berbicara tentang align pas di u32, yang hanya insidental, ketika hal yang sebenarnya sedang "diperbaiki" (lebih banyak dikerjakan) adalah pengalokasi sistem berperilaku buruk.

Yang membawa saya ke catatan ini: Item "OSX / assign_system is buggy on huge alignments" di sini sebaiknya tidak dicentang. Meskipun masalah langsung telah ditangani, menurut saya perbaikannya tidak tepat untuk jangka panjang: Karena pengalokasi sistem berperilaku tidak semestinya tidak mencegah penerapan pengalokasi yang berperilaku. Dan batasan sewenang-wenang pada Layout :: from_size_align melakukannya.

@glandium Apakah berguna untuk meminta penyelarasan ke kelipatan 4 gigbyte atau lebih?

Saya dapat membayangkan kasus-kasus di mana seseorang mungkin ingin memiliki alokasi 4GiB yang selaras dengan 4GiB, yang tidak mungkin dilakukan saat ini, tetapi hampir tidak lebih. Tetapi saya tidak berpikir batasan sewenang-wenang harus ditambahkan hanya karena kami tidak memikirkan alasan seperti itu sekarang.

Saya dapat membayangkan kasus di mana seseorang mungkin ingin memiliki alokasi 4GiB yang selaras di 4GiB

Kasus apa itu?

Saya dapat membayangkan kasus di mana seseorang mungkin ingin memiliki alokasi 4GiB yang selaras di 4GiB

Kasus apa itu?

Secara konkret, saya baru saja menambahkan dukungan untuk penyelarasan besar yang sewenang-wenang di mmap-alloc untuk mendukung pengalokasian lempengan memori yang besar dan selaras untuk digunakan di elfmalloc . Idenya adalah agar lempengan memori disejajarkan dengan ukurannya sehingga, dengan penunjuk ke objek yang dialokasikan dari lempengan itu, Anda hanya perlu menutupi bit rendah untuk menemukan lempengan yang berisi. Kami saat ini tidak menggunakan lembaran yang berukuran 4GB (untuk objek sebesar itu, kami langsung menuju ke mmap), tetapi tidak ada alasan kami tidak bisa, dan saya benar-benar dapat membayangkan aplikasi dengan persyaratan RAM besar yang ingin melakukannya. itu (yaitu, jika itu mengalokasikan objek multi-GB cukup sering sehingga tidak ingin menerima overhead mmap).

Berikut adalah kasus penggunaan yang mungkin untuk> penyelarasan 4GiB: penyelarasan ke batas halaman besar. Sudah ada platform yang mendukung> 4 GiB halaman. Dokumen IBM ini mengatakan "prosesor POWER5 + mendukung empat ukuran halaman memori virtual: 4 KB, 64 KB, 16 MB, dan 16 GB." Bahkan x86-64 tidak jauh: "halaman besar" biasanya berukuran 2 MiB, tetapi juga mendukung 1 GiB.

Semua fungsi yang tidak diketik dalam sifat Alloc berhubungan dengan *mut u8 . Yang berarti mereka dapat mengambil atau mengembalikan petunjuk nol, dan semua neraka akan lepas. Haruskah mereka menggunakan NonNull sebagai gantinya?

Ada banyak petunjuk bahwa mereka bisa kembali dari mana semua neraka akan melakukannya
membebaskan diri.
Pada hari Minggu, 4 Mar 2018 jam 3:56 AM Mike Hommey [email protected] menulis:

Semua fungsi yang tidak diketik dalam sifat Alloc berhubungan dengan * mut u8.
Yang berarti mereka dapat mengambil atau mengembalikan pointer nol, dan semua akan melakukannya
membebaskan diri. Haruskah mereka menggunakan NonNull sebagai gantinya?

-
Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/rust-lang/rust/issues/32838#issuecomment-370223269 ,
atau nonaktifkan utasnya
https://github.com/notifications/unsubscribe-auth/ABY2UR2dRxDtdACeRUh_djM-DExRuLxiks5ta9aFgaJpZM4IDYUN
.

Alasan yang lebih kuat untuk menggunakan NonNull adalah bahwa hal itu akan memungkinkan Result s saat ini dikembalikan dari Alloc metode (atau Options , jika kita beralih ke metode tersebut di masa depan) menjadi lebih kecil.

Alasan yang lebih kuat untuk menggunakan NonNull adalah karena ini akan memungkinkan Hasil yang saat ini dikembalikan dari metode Alloc (atau Opsi, jika kita beralih ke metode tersebut di masa mendatang) menjadi lebih kecil.

Saya tidak berpikir itu akan terjadi karena AllocErr memiliki dua varian.

Ada banyak petunjuk bahwa mereka bisa kembali dari mana semua neraka akan lepas.

Tapi pointer nol jelas lebih salah daripada pointer lainnya.

Saya suka berpikir bahwa sistem tipe karat membantu dengan footguns, dan digunakan untuk mengkodekan invarian. Dokumentasi untuk alloc dengan jelas mengatakan "Jika metode ini mengembalikan Ok(addr) , maka addr yang dikembalikan akan menjadi alamat non-null", tetapi tipe kembaliannya tidak. Sebetulnya, Ok(malloc(layout.size())) akan menjadi implementasi yang valid, padahal sebenarnya tidak.

Catatan, ada juga catatan tentang ukuran Layout harus bukan nol, jadi saya juga berpendapat itu harus menyandikannya sebagai NonZero.

Bukan karena semua fungsi itu secara inheren tidak aman sehingga kita tidak boleh memiliki pencegahan senjata api.

Dari semua kemungkinan kesalahan dalam menggunakan (edit: dan implementasi) pengalokasi, melewatkan pointer nol adalah salah satu yang termudah untuk dilacak (Anda selalu mendapatkan segfault bersih pada dereferensi, setidaknya jika Anda memiliki MMU dan tidak melakukannya hal-hal yang sangat aneh dengannya), dan biasanya salah satu yang paling sepele untuk diperbaiki juga. Memang benar bahwa antarmuka yang tidak aman dapat mencoba mencegah footgun, tetapi footgun ini tampak sangat kecil (dibandingkan dengan kemungkinan kesalahan lainnya, dan dengan verbositas pengkodean yang tidak tetap ini dalam sistem tipe).

Selain itu, tampaknya implementasi pengalokasi hanya akan menggunakan konstruktor yang tidak dicentang dari NonNull "untuk kinerja": karena dalam pengalokasi yang benar tidak akan pernah mengembalikan nol, ia akan ingin melewati NonNell::new(...).unwrap() . Dalam hal ini Anda tidak akan benar-benar mendapatkan pencegahan footgun yang nyata, hanya lebih banyak boilerplate. (Manfaat ukuran Result , jika nyata, mungkin masih menjadi alasan yang kuat untuk itu.)

implementasi pengalokasi hanya akan menggunakan konstruktor NonNull yang tidak dicentang

Intinya lebih sedikit untuk membantu implementasi pengalokasi daripada membantu penggunanya. Jika MyVec berisi NonNull<T> dan Heap.alloc() sudah mengembalikan NonNull , panggilan yang tidak dicentang atau tidak aman itu harus saya lakukan.

Perhatikan bahwa pointer tidak hanya mengembalikan tipe, tetapi juga tipe input misalnya dealloc dan realloc . Apakah fungsi-fungsi itu seharusnya mengeras terhadap masukan mereka yang mungkin nol atau tidak? Dokumentasi cenderung mengatakan tidak, tetapi sistem tipe cenderung mengatakan ya.

Mirip dengan layout.size (). Apakah fungsi alokasi seharusnya menangani ukuran yang diminta menjadi 0, atau tidak?

(Manfaat ukuran hasil, jika nyata, mungkin masih menjadi alasan kuat untuk itu.)

Saya ragu ada manfaat ukuran, tetapi dengan sesuatu seperti # 48741, akan ada manfaat codegen.

Jika kita melanjutkan prinsip agar lebih fleksibel bagi pengguna API, pointer harus NonNull dalam tipe kembalian tetapi tidak dalam argumen. (Ini tidak berarti bahwa argumen tersebut harus diperiksa dengan null pada waktu proses.)

Saya pikir pendekatan hukum Postel adalah yang salah untuk diambil di sini. Apakah ada
kasus di mana melewatkan pointer nol ke metode Alloc valid? Jika tidak,
kemudian fleksibilitas itu pada dasarnya hanya memberikan sedikit tambahan pada footgun
pemicu sensitif.

Pada 5 Mar 2018 8:00, "Simon Sapin" [email protected] menulis:

Jika kami melanjutkan prinsip menjadi lebih fleksibel untuk pengguna API,
pointer harus NonNull dalam tipe kembalian tetapi tidak dalam argumen. (Ini
tidak berarti bahwa argumen tersebut harus diperiksa dengan null pada waktu proses.)

-
Anda menerima ini karena Anda berlangganan utas ini.
Balas email ini secara langsung, lihat di GitHub
https://github.com/rust-lang/rust/issues/32838#issuecomment-370327018 ,
atau nonaktifkan utasnya
https://github.com/notifications/unsubscribe-auth/AA_2L8zrOLyUv5mUc_kiiXOAn1f60k9Uks5tbOJ0gaJpZM4IDYUN
.

Intinya lebih sedikit untuk membantu implementasi pengalokasi daripada membantu penggunanya. Jika MyVec berisi NonNulldan Heap.alloc () sudah mengembalikan NonNull, panggilan yang kurang dicentang atau tidak aman-tidak dicentang yang perlu saya lakukan.

Ah ini masuk akal. Tidak memperbaiki footgun, tetapi memusatkan tanggung jawab untuk itu.

Perhatikan bahwa pointer tidak hanya mengembalikan tipe, tetapi juga tipe input misalnya dealloc dan realloc. Apakah fungsi-fungsi itu seharusnya mengeras terhadap masukan mereka yang mungkin nol atau tidak? Dokumentasi cenderung mengatakan tidak, tetapi sistem tipe cenderung mengatakan ya.

Apakah ada kasus di mana meneruskan pointer nol ke metode Alloc adalah valid? Jika tidak, maka fleksibilitas itu pada dasarnya hanya memberikan pemicu yang sedikit lebih sensitif pada footgun.

Pengguna benar-benar harus membaca dokumentasi dan mengingat invariannya. Banyak invarian yang tidak dapat diterapkan melalui sistem tipe sama sekali - jika mereka bisa, fungsi tersebut tidak akan menjadi tidak aman untuk memulai. Jadi ini hanya pertanyaan apakah menempatkan NonNull di antarmuka tertentu akan benar-benar membantu pengguna

  • mengingatkan mereka untuk membaca dokumen dan memikirkan tentang invarian
  • menawarkan kenyamanan (nilai pengembalian alokasi poin wrt @SimonSapin )
  • memberikan beberapa keuntungan material (misalnya, pengoptimalan tata letak)

Saya tidak melihat adanya keuntungan yang kuat dalam membuat misalnya, argumen dealloc menjadi NonNull . Saya melihat kira-kira dua kelas penggunaan API ini:

  1. Penggunaan yang relatif sepele, di mana Anda memanggil alloc , menyimpan penunjuk yang dikembalikan di suatu tempat, dan setelah beberapa saat meneruskan penunjuk yang disimpan ke dealloc .
  2. Skenario rumit yang melibatkan FFI, banyak aritmatika penunjuk, dll. Di mana ada logika signifikan yang terlibat dalam memastikan Anda meneruskan hal yang benar ke dealloc di akhir.

Mengambil NonNull sini pada dasarnya hanya membantu jenis penggunaan pertama, karena itu akan menyimpan NonNull di beberapa tempat yang bagus dan hanya meneruskannya ke NonNull perubahan. Secara teoritis ini dapat mencegah beberapa kesalahan ketik (melewati foo ketika yang Anda maksud adalah bar ) jika Anda menyulap beberapa petunjuk dan hanya salah satunya adalah NonNull , tetapi ini tampaknya tidak terlalu umum atau penting. Kerugian dari dealloc mengambil pointer mentah (dengan asumsi alloc mengembalikan NonNull yang @SimonSapin yakin saya harus terjadi) adalah bahwa itu membutuhkan as_ptr dalam panggilan dealloc, yang berpotensi mengganggu tetapi tidak memengaruhi keamanan.

Jenis kasus penggunaan kedua tidak membantu karena kemungkinan besar tidak dapat terus menggunakan NonNull selama seluruh proses, jadi harus membuat ulang secara manual NonNull dari penunjuk mentah yang didapatnya dengan cara apapun. Seperti yang saya katakan sebelumnya, ini kemungkinan akan menjadi pernyataan yang tidak dicentang / unsafe daripada pemeriksaan waktu berjalan yang sebenarnya, jadi tidak ada senapan kaki yang dicegah.

Ini bukan untuk mengatakan saya mendukung dealloc mengambil pointer mentah. Saya hanya tidak melihat keuntungan yang diklaim wrt footguns. Konsistensi jenis mungkin hanya menang secara default.

Maaf, tapi saya membaca ini sebagai "Banyak invarian tidak dapat diterapkan melalui sistem tipe sama sekali ... oleh karena itu, jangan coba-coba". Jangan biarkan yang sempurna menjadi musuh kebaikan!

Saya pikir ini lebih tentang pengorbanan antara jaminan yang diberikan oleh NonNull dan ergonomi yang hilang karena harus bertransisi bolak-balik antara NonNull dan petunjuk mentah. Saya juga tidak memiliki pendapat yang kuat - tidak ada pihak yang tampaknya tidak masuk akal.

@cramertj Ya, tapi saya tidak Alloc adalah untuk kasus penggunaan yang tidak jelas, tersembunyi, dan sebagian besar tidak aman. Nah, dalam kode yang tidak jelas dan sulit dibaca, saya ingin memiliki keamanan sebanyak mungkin --- justru karena mereka sangat jarang disentuh kemungkinan besar penulis aslinya tidak akan ada. Sebaliknya, jika kode dibaca bertahun-tahun kemudian, sekrup egonomics. Jika ada, itu kontraproduktif. Kode harus berusaha untuk menjadi sangat eksplisit sehingga pembaca yang tidak dikenal dapat lebih memahami apa yang sedang terjadi. Lebih sedikit kebisingan <invarian yang lebih jelas.

Jenis kasus penggunaan kedua tidak membantu karena kemungkinan besar tidak dapat terus menggunakan NonNull selama seluruh proses, jadi harus membuat ulang secara manual NonNull dari penunjuk mentah yang didapatnya dengan cara apapun.

Ini hanyalah kegagalan koordinasi, bukan keniscayaan teknis. Tentu, saat ini banyak API tidak aman yang mungkin menggunakan pointer mentah. Jadi sesuatu harus mengarahkan cara beralih ke antarmuka yang lebih unggul menggunakan NonNull atau pembungkus lainnya. Kemudian kode lain dapat lebih mudah mengikuti. Saya melihat 0 alasan untuk terus menggunakan petunjuk mentah yang sulit dibaca dan tidak informatif di greenfield, semua-Rust, kode tidak aman.

Hai!

Saya hanya ingin mengatakan bahwa, sebagai penulis / pengelola pengalokasi khusus Rust, saya mendukung NonNull . Cukup banyak untuk semua alasan yang telah ditetapkan di utas ini.

Juga, saya ingin menunjukkan bahwa @glandium adalah pengelola fork firefox dari jemalloc, dan memiliki banyak pengalaman meretas pengalokasi juga.

Saya dapat menulis lebih banyak lagi untuk menanggapi @ Ericson2314 dan lainnya, tetapi ini dengan cepat menjadi debat yang sangat terpisah dan filosofis, jadi saya memotongnya di sini. Saya berdebat melawan apa yang saya yakini sebagai pernyataan berlebihan tentang manfaat keamanan NonNull dalam API semacam ini (tentu saja ada manfaat lain). Bukan berarti tidak ada manfaat keamanan, tetapi seperti yang dikatakan @cramertj , ada trade off dan saya pikir sisi "pro" dilebih-lebihkan. Terlepas dari itu, saya telah mengatakan bahwa saya cenderung menggunakan NonNull di berbagai tempat karena alasan lain - untuk alasan @SimonSapin memberikan alloc , dalam dealloc untuk konsistensi. Jadi mari kita lakukan itu dan jangan pergi ke garis singgung lagi.

Jika ada beberapa kasus penggunaan NonNull yang semua orang setuju, itu awal yang bagus.

Kami mungkin ingin memperbarui Unique dan teman-teman untuk menggunakan NonNull alih-alih NonZero untuk menjaga gesekan setidaknya dalam liballoc rendah. Tapi ini memang terlihat seperti sesuatu yang sudah kita lakukan pada satu tingkat abstraksi di atas pengalokasi, dan saya tidak dapat memikirkan alasan apa pun untuk tidak melakukan ini di tingkat pengalokasi juga. Sepertinya perubahan yang wajar bagi saya.

(Sudah ada dua implementasi dari sifat From yang mengonversi dengan aman antara Unique<T> dan NonNull<T> .)

Mempertimbangkan saya membutuhkan sesuatu yang sangat mirip dengan API pengalokasi pada karat stabil, saya mengekstrak kode dari repo karat dan meletakkannya di peti terpisah:

Ini / bisa / digunakan untuk mengulangi perubahan eksperimental ke API, tetapi untuk sekarang, ini adalah salinan biasa dari apa yang ada di repo rust.

[Off topic] Ya alangkah baiknya jika std dapat menggunakan peti kode stabil pohon seperti itu, sehingga kita dapat bereksperimen dengan antarmuka yang tidak stabil dalam kode stabil. Ini adalah salah satu alasan saya suka memiliki fasad std .

std dapat bergantung pada salinan peti dari crates.io, tetapi jika program Anda juga bergantung pada peti yang sama itu tidak akan "terlihat seperti" peti / jenis / sifat yang sama dengan rustc, jadi saya tidak tidak melihat bagaimana itu akan membantu. Bagaimanapun, terlepas dari fasadnya, membuat fitur yang tidak stabil tidak tersedia di saluran stabil adalah pilihan yang sangat disengaja, bukan kebetulan.

Tampaknya kami memiliki beberapa persetujuan untuk menggunakan NonNull. Apa jalan ke depan agar hal ini benar-benar terjadi? hanya seorang PR yang melakukannya? sebuah RFC?

Tidak terkait, saya telah melihat beberapa rakitan yang dihasilkan dari hal-hal Boxing, dan jalur kesalahannya agak besar. Haruskah dilakukan sesuatu tentang itu?

Contoh:

pub fn bar() -> Box<[u8]> {
    vec![0; 42].into_boxed_slice()
}

pub fn qux() -> Box<[u8]> {
    Box::new([0; 42])
}

dikompilasi ke:

example::bar:
  sub rsp, 56
  lea rdx, [rsp + 8]
  mov edi, 42
  mov esi, 1
  call __rust_alloc_zeroed<strong i="11">@PLT</strong>
  test rax, rax
  je .LBB1_1
  mov edx, 42
  add rsp, 56
  ret
.LBB1_1:
  mov rax, qword ptr [rsp + 8]
  movups xmm0, xmmword ptr [rsp + 16]
  movaps xmmword ptr [rsp + 32], xmm0
  mov qword ptr [rsp + 8], rax
  movaps xmm0, xmmword ptr [rsp + 32]
  movups xmmword ptr [rsp + 16], xmm0
  lea rdi, [rsp + 8]
  call __rust_oom<strong i="12">@PLT</strong>
  ud2

example::qux:
  sub rsp, 104
  xorps xmm0, xmm0
  movups xmmword ptr [rsp + 58], xmm0
  movaps xmmword ptr [rsp + 48], xmm0
  movaps xmmword ptr [rsp + 32], xmm0
  lea rdx, [rsp + 8]
  mov edi, 42
  mov esi, 1
  call __rust_alloc<strong i="13">@PLT</strong>
  test rax, rax
  je .LBB2_1
  movups xmm0, xmmword ptr [rsp + 58]
  movups xmmword ptr [rax + 26], xmm0
  movaps xmm0, xmmword ptr [rsp + 32]
  movaps xmm1, xmmword ptr [rsp + 48]
  movups xmmword ptr [rax + 16], xmm1
  movups xmmword ptr [rax], xmm0
  mov edx, 42
  add rsp, 104
  ret
.LBB2_1:
  movups xmm0, xmmword ptr [rsp + 16]
  movaps xmmword ptr [rsp + 80], xmm0
  movaps xmm0, xmmword ptr [rsp + 80]
  movups xmmword ptr [rsp + 16], xmm0
  lea rdi, [rsp + 8]
  call __rust_oom<strong i="14">@PLT</strong>
  ud2

Itu jumlah kode yang cukup besar untuk ditambahkan ke tempat mana pun yang membuat kotak. Bandingkan dengan 1,19, yang tidak memiliki api pengalokasi:

example::bar:
  push rax
  mov edi, 42
  mov esi, 1
  call __rust_allocate_zeroed<strong i="18">@PLT</strong>
  test rax, rax
  je .LBB1_2
  mov edx, 42
  pop rcx
  ret
.LBB1_2:
  call alloc::oom::oom<strong i="19">@PLT</strong>

example::qux:
  sub rsp, 56
  xorps xmm0, xmm0
  movups xmmword ptr [rsp + 26], xmm0
  movaps xmmword ptr [rsp + 16], xmm0
  movaps xmmword ptr [rsp], xmm0
  mov edi, 42
  mov esi, 1
  call __rust_allocate<strong i="20">@PLT</strong>
  test rax, rax
  je .LBB2_2
  movups xmm0, xmmword ptr [rsp + 26]
  movups xmmword ptr [rax + 26], xmm0
  movaps xmm0, xmmword ptr [rsp]
  movaps xmm1, xmmword ptr [rsp + 16]
  movups xmmword ptr [rax + 16], xmm1
  movups xmmword ptr [rax], xmm0
  mov edx, 42
  add rsp, 56
  ret
.LBB2_2:
  call alloc::oom::oom<strong i="21">@PLT</strong>

Jika ini sebenarnya signifikan, maka itu memang mengganggu. Namun mungkin LLVM mengoptimalkan ini untuk program yang lebih besar?

Ada 1.439 panggilan ke __rust_oom di Firefox setiap malam. Firefox tidak menggunakan pengalokasi rust, jadi kami mendapatkan panggilan langsung ke malloc / calloc, diikuti dengan pemeriksaan nol bahwa lompatan ke kode persiapan oom, yang biasanya dua movq dan lea, mengisi AllocErr dan mendapatkan alamatnya untuk meneruskannya ke __rust__oom . Itu skenario kasus terbaik, pada dasarnya, tapi itu masih 20 byte kode mesin untuk dua movq dan lea.

Jika saya melihat ripgrep, ada 85, dan semuanya identik dengan fungsi _ZN61_$LT$alloc..heap..Heap$u20$as$u20$alloc..allocator..Alloc$GT$3oom17h53c76bda5 0c6b65aE.llvm.nnnnnnnnnnnnnnn . Semuanya memiliki panjang 16 byte. Ada 685 panggilan ke fungsi wrapper tersebut, yang sebagian besar diawali dengan kode yang mirip dengan yang saya tempel di https://github.com/rust-lang/rust/issues/32838#issuecomment -377097485.

@nox hari ini sedang mencari cara untuk mengaktifkan pass mergefunc llvm, saya ingin tahu apakah itu ada bedanya di sini.

mergefunc tampaknya tidak menyingkirkan beberapa fungsi _ZN61_$LT$alloc..heap..Heap$u20$as$u20$alloc..allocator..Alloc$GT$3oom17h53c76bda5 0c6b65aE.llvm.nnnnnnnnnnnnnnn identik (dicoba dengan -C passes=mergefunc dalam RUSTFLAGS ).

Tapi yang membuat perbedaan besar adalah LTO, yang sebenarnya membuat Firefox memanggil malloc secara langsung, membiarkan pembuatan AllocErr ke kanan sebelum memanggil __rust_oom . Itu juga membuat pembuatan Layout tidak diperlukan sebelum memanggil pengalokasi, membiarkannya saat mengisi AllocErr .

Ini membuat saya berpikir fungsi alokasi, kecuali __rust_oom mungkin harus ditandai sebaris.

BTW, setelah melihat kode yang dihasilkan untuk Firefox, saya pikir idealnya akan lebih baik untuk menggunakan moz_xmalloc daripada malloc . Ini tidak mungkin terjadi tanpa kombinasi dari ciri-ciri Allocator dan dapat menggantikan pengalokasi heap global, tetapi membawa kemungkinan kebutuhan akan jenis kesalahan khusus untuk sifat Pengalokasi: moz_xmalloc tidak bisa salah dan tidak pernah kembali jika terjadi kegagalan. IOW, itu menangani OOM itu sendiri, dan kode karat tidak perlu memanggil __rust_oom dalam kasus itu. Yang akan membuatnya diinginkan untuk fungsi pengalokasi untuk secara opsional mengembalikan ! daripada AllocErr .

Kita telah membahas membuat AllocErr struct berukuran nol, yang mungkin juga membantu di sini. Dengan pointer juga menghasilkan NonNull , seluruh nilai yang dikembalikan bisa berukuran pointer.

https://github.com/rust-lang/rust/pull/49669 membuat sejumlah perubahan pada API ini, dengan tujuan menstabilkan subset yang mencakup pengalokasi global. Masalah pelacakan untuk subset tersebut: https://github.com/rust-lang/rust/issues/49668. Secara khusus, sifat baru GlobalAlloc diperkenalkan.

Akankah PR ini memungkinkan kita melakukan hal-hal seperti Vec::new_with_alloc(alloc) mana alloc: Alloc segera?

@alexreg no

@sfackler Hmm, kenapa tidak? Apa yang kita butuhkan sebelum bisa melakukannya? Saya tidak benar-benar mengerti maksud dari PR ini, kecuali itu hanya untuk mengubah pengalokasi global.

@exreg

Saya tidak benar-benar mengerti maksud dari PR ini, kecuali itu hanya untuk mengubah pengalokasi global.

Saya pikir ini hanya untuk mengubah pengalokasi global.

@alexreg Jika maksud Anda stabil, ada sejumlah pertanyaan desain yang belum terselesaikan yang belum siap kami stabilkan. Pada Nightly, ini didukung oleh RawVec dan mungkin baik untuk ditambahkan sebagai #[unstable] untuk Vec bagi siapa saja yang ingin mengerjakannya.

Dan ya, seperti yang disebutkan dalam PR, intinya adalah mengizinkan perubahan pengalokasi global, atau pengalokasian (mis. Dalam tipe koleksi kustom) tanpa mengurangi Vec::with_capacity .

FWIW, peti allocator_api disebutkan di https://github.com/rust-lang/rust/issues/32838#issuecomment -376793369 memiliki RawVec<T, A> dan Box<T, A> pada master cabang (belum dirilis). Saya menganggapnya sebagai inkubator untuk tampilan koleksi generik atas tipe alokasi (ditambah fakta bahwa saya membutuhkan tipe Box<T, A> untuk karat yang stabil). Saya belum mulai mem-port vec.rs untuk menambahkan Vec<T, A> dulu, tetapi PR dipersilakan. vec.rs besar.

Saya perhatikan bahwa "masalah" codegen yang disebutkan di https://github.com/rust-lang/rust/issues/32838#issuecomment -377097485 akan hilang dengan perubahan di # 49669.

Sekarang, dengan lebih banyak pemikiran yang diberikan untuk menggunakan sifat Alloc untuk membantu menerapkan pengalokasi dalam lapisan, ada dua hal yang menurut saya akan berguna (setidaknya bagi saya):

  • seperti yang disebutkan sebelumnya, secara opsional dapat menentukan tipe AllocErr . Ini dapat berguna untuk membuatnya ! , atau, sekarang AllocErr kosong, untuk secara opsional membuatnya menyampaikan lebih banyak informasi daripada "gagal".
  • secara opsional dapat menentukan jenis Layout . Bayangkan Anda memiliki dua lapisan pengalokasi: satu untuk alokasi halaman, dan satu lagi untuk wilayah yang lebih besar. Yang terakhir dapat mengandalkan yang pertama, tetapi jika keduanya mengambil tipe Layout , maka kedua lapisan perlu melakukan validasinya sendiri: pada tingkat terendah, ukuran dan penyelarasan itu adalah kelipatan dari ukuran halaman, dan tingkat yang lebih tinggi, ukuran dan kesejajaran tersebut sesuai dengan persyaratan untuk wilayah yang lebih luas. Tapi pemeriksaan itu mubazir. Dengan jenis khusus Layout , validasi dapat didelegasikan ke pembuatan Layout alih-alih di pengalokasi itu sendiri, dan konversi antara jenis Layout akan memungkinkan untuk melewati pemeriksaan yang berlebihan.

@cramertj @SimonSapin @glandium Oke terima kasih klarifikasi. Saya mungkin hanya mengirimkan PR untuk beberapa tipe koleksi-prime lainnya. Apakah yang terbaik adalah melakukan ini terhadap repo / peti alokasi -api Anda,

@alexreg mempertimbangkan jumlah perubahan yang melanggar pada sifat Alloc di # 49669, mungkin lebih baik menunggu sampai itu bergabung terlebih dahulu.

@glandium Cukup adil. Itu sepertinya tidak terlalu jauh dari pendaratan. Saya baru saja melihat https://github.com/pnkfelix/collections-prime repo juga ... apa hubungannya dengan milik Anda?

Saya akan menambahkan satu pertanyaan terbuka lagi:

  • Apakah Alloc::oom dibiarkan panik? Saat ini dokumen mengatakan bahwa metode ini harus membatalkan proses. Ini berimplikasi pada kode yang menggunakan pengalokasi karena mereka kemudian harus dirancang untuk menangani pelepasan dengan benar tanpa membocorkan memori.

Saya pikir kita harus membiarkan kepanikan karena kegagalan dalam pengalokasi lokal tidak berarti bahwa pengalokasi global juga akan gagal. Dalam kasus terburuk, pengalokasi global oom akan dipanggil yang akan membatalkan proses (melakukan sebaliknya akan merusak kode yang ada).

@ alexreg Ini tidak. Ini sepertinya merupakan salinan biasa dari apa yang ada di std / alokasi / koleksi. Nah, salinannya yang berumur dua tahun. Cakupan kandang saya jauh lebih terbatas (versi yang diterbitkan hanya memiliki sifat Alloc pada beberapa minggu yang lalu, cabang master hanya memiliki RawVec dan Box di atasnya itu), dan salah satu tujuan saya adalah menjaganya tetap membangun dengan karat yang stabil.

@glandium Oke, kalau begitu mungkin masuk akal bagi saya untuk menunggu sampai PR itu mendarat, lalu buat PR melawan ahli karat dan beri tag Anda, jadi Anda tahu kapan itu digabung menjadi master (dan kemudian dapat menggabungkannya ke dalam peti Anda) adil?

@alexreg masuk akal. Anda / bisa / mulai mengerjakannya sekarang, tetapi itu kemungkinan akan menyebabkan beberapa churn di pihak Anda jika / ketika bikeshedding mengubah banyak hal dalam PR itu.

@glandium Saya punya hal lain yang membuat saya sibuk dengan Rust untuk saat ini, tapi saya akan

Apakah Alloc :: oom dibiarkan panik? Saat ini dokumen mengatakan bahwa metode ini harus membatalkan proses. Ini berimplikasi pada kode yang menggunakan pengalokasi karena mereka kemudian harus dirancang untuk menangani pelepasan dengan benar tanpa membocorkan memori.

@Amanieu RFC ini telah digabungkan: https://github.com/rust-lang/rfcs/pull/2116 Dokumen dan implementasi mungkin saja belum diperbarui.

Ada satu perubahan pada API yang saya pertimbangkan untuk mengirimkan PR untuk:

Pisahkan ciri Alloc dua bagian: "implementasi" dan "pembantu". Yang pertama akan menjadi fungsi seperti alloc , dealloc , realloc , dll. Dan yang terakhir, alloc_one , dealloc_one , alloc_array , dll. Meskipun ada beberapa manfaat hipotetis dari dapat menerapkan kustomisasi untuk yang terakhir, ini jauh dari kebutuhan yang paling umum, dan ketika Anda perlu mengimplementasikan pembungkus generik (yang menurut saya sangat umum, sampai pada titik saya sebenarnya mulai menulis turunan khusus untuk itu), Anda masih perlu menerapkan semuanya karena pembungkus mungkin menyesuaikannya.

OTOH, jika pelaksana sifat Alloc mencoba melakukan hal-hal mewah misalnya alloc_one , mereka tidak dijamin bahwa dealloc_one akan dipanggil untuk alokasi itu. Ada beberapa alasan untuk ini:

  • Pembantu tidak digunakan secara konsisten. Hanya satu contoh, raw_vec menggunakan campuran alloc_array , alloc / alloc_zeroed , tetapi hanya menggunakan dealloc .
  • Bahkan dengan penggunaan yang konsisten misalnya alloc_array / dealloc_array , seseorang masih dapat dengan aman mengonversi Vec menjadi Box , yang kemudian akan menggunakan dealloc .
  • Lalu ada beberapa bagian API yang tidak ada (tidak ada versi nol dari alloc_one / alloc_array )

Jadi, meskipun ada kasus penggunaan aktual untuk spesialisasi misalnya alloc_one (dan pada kenyataannya, saya memang memiliki kebutuhan untuk mozjemalloc), lebih baik menggunakan pengalokasi khusus sebagai gantinya.

Sebenarnya, ini lebih buruk dari itu, dalam repo karat, hanya ada satu penggunaan alloc_array , dan tidak ada penggunaan alloc_one , dealloc_one , realloc_array , dealloc_array . Bahkan sintaks kotak tidak menggunakan alloc_one , itu menggunakan exchange_malloc , yang membutuhkan size dan align . Jadi fungsi tersebut lebih dimaksudkan sebagai kenyamanan bagi klien daripada untuk pelaksana.

Dengan sesuatu seperti impl<A: Alloc> AllocHelpers for A (atau AllocExt , apa pun nama yang dipilih), kami masih memiliki kemudahan fungsi tersebut untuk klien, sementara tidak mengizinkan pelaksana untuk menembak diri sendiri jika mereka berpikir mereka akan melakukan hal-hal mewah dengan menimpanya (dan mempermudah orang yang menerapkan pengalokasi proxy).

Ada satu perubahan pada API yang saya pertimbangkan untuk dikirimi PR

Melakukannya di # 50436

@bayu_joo

(dan sebenarnya, saya sangat membutuhkan mozjemalloc),

Bisakah Anda menjelaskan kasus penggunaan ini?

mozjemalloc memiliki pengalokasi dasar yang sengaja bocor. Kecuali untuk satu jenis objek, yang menyimpan daftar gratis. Saya dapat melakukannya dengan melapisi pengalokasi daripada melakukan trik dengan alloc_one .

Apakah diperlukan untuk melepas alokasi dengan keselarasan persis seperti yang Anda alokasikan?

Hanya untuk memperkuat bahwa jawaban atas pertanyaan ini adalah YA , saya memiliki kutipan indah dari Microsoft sendiri :

aligned_alloc () mungkin tidak akan pernah diimplementasikan, karena C11 menetapkannya dengan cara yang tidak sesuai dengan implementasi kami (yaitu, free () harus dapat menangani alokasi yang sangat selaras)

Menggunakan pengalokasi sistem di Windows akan selalu memerlukan pengetahuan penyelarasan saat membatalkan alokasi agar dapat dengan benar membatalkan alokasi alokasi yang sangat selaras, jadi dapatkah kita cukup menandai pertanyaan itu sebagai terselesaikan?

Menggunakan pengalokasi sistem di Windows akan selalu memerlukan pengetahuan penyelarasan saat membatalkan alokasi agar dapat dengan benar membatalkan alokasi alokasi yang sangat selaras, jadi dapatkah kita cukup menandai pertanyaan itu sebagai terselesaikan?

Ini memalukan, tapi begitulah adanya. Mari kita menyerah pada vektor yang terlalu selaras. :bingung:

Mari kita menyerah pada vektor yang terlalu selaras

Bagaimana bisa? Anda hanya perlu Vec<T, OverAlignedAlloc<U16>> yang dialokasikan dan dialokasikan dengan overalignment.

Bagaimana bisa? Anda hanya perlu Vec<T, OverAlignedAlloc<U16>> yang dialokasikan dan dialokasikan dengan overalignment.

Saya seharusnya lebih spesifik. Maksud saya memindahkan vektor yang terlalu selaras ke dalam API di luar kendali Anda, yaitu yang membutuhkan Vec<T> dan bukan Vec<T, OverAlignedAlloc<U16>> . (Misalnya CString::new() .)

Anda lebih baik menggunakan

#[repr(align(16))]
struct OverAligned16<T>(T);

dan kemudian Vec<OverAligned16<T>> .

Anda lebih baik menggunakan

Itu tergantung. Misalkan Anda ingin menggunakan intrinsik AVX (lebar 256 bit, persyaratan penyelarasan 32 byte) pada vektor f32 s:

  • Vec<T, OverAlignedAlloc<U32>> memecahkan masalah, seseorang dapat menggunakan intrinsik AVX secara langsung pada elemen vektor (khususnya, beban memori yang selaras), dan vektor masih membentuk potongan &[f32] sehingga ergonomis untuk digunakan.
  • Vec<OverAligned32<f32>> tidak benar-benar menyelesaikan masalah. Setiap f32 membutuhkan 32 byte ruang karena persyaratan penyelarasan. Padding yang diperkenalkan mencegah penggunaan langsung operasi AVX karena f32 s tidak lagi berada di memori berkelanjutan. Dan saya pribadi menemukan deref ke &[OverAligned32<f32>] agak membosankan untuk ditangani.

Untuk satu elemen dalam Box , Box<T, OverAligned<U32>> vs Box<OverAligned32<T>> , kedua pendekatan tersebut lebih setara, dan pendekatan kedua mungkin lebih disukai. Bagaimanapun, bagus untuk memiliki kedua opsi.

Posting pelacakan di bagian atas masalah ini sangat ketinggalan zaman (terakhir diedit pada 2016). Kami membutuhkan daftar masalah aktif yang diperbarui untuk melanjutkan diskusi secara produktif.

Diskusi ini juga akan mendapatkan keuntungan yang signifikan dari dokumen desain terbaru, yang berisi pertanyaan-pertanyaan yang belum terselesaikan saat ini, dan alasan keputusan desain.

Ada beberapa utas perbedaan dari "apa yang saat ini diterapkan pada malam hari" hingga "apa yang diusulkan dalam Alloc RFC asli" yang menghasilkan ribuan komentar di saluran yang berbeda (repo rfc, masalah pelacakan karat-lang, alokasi global RFC, pos internal, banyak PR besar, dll.), dan apa yang distabilkan dalam GlobalAlloc RFC tidak terlalu terlihat dari apa yang diusulkan dalam RFC asli.

Ini adalah sesuatu yang kami perlukan untuk menyelesaikan pembaruan dokumen dan referensi, dan akan membantu dalam diskusi saat ini juga.

Saya pikir bahkan sebelum kita berpikir tentang menstabilkan sifat Alloc , pertama-tama kita harus mencoba menerapkan dukungan pengalokasi di semua koleksi perpustakaan standar. Ini akan memberi kita pengalaman tentang bagaimana sifat ini akan digunakan dalam praktik.

Saya pikir bahkan sebelum kita berpikir tentang menstabilkan sifat Alloc , pertama-tama kita harus mencoba menerapkan dukungan pengalokasi di semua koleksi perpustakaan standar. Ini akan memberi kita pengalaman tentang bagaimana sifat ini akan digunakan dalam praktik.

Ya, tentu saja. Terutama Box , karena kita belum tahu bagaimana menghindari Box<T, A> mengambil dua kata.

Ya, tentu saja. Khususnya Box, karena kita belum tahu bagaimana menghindari Boxmengambil dua kata.

Saya tidak berpikir kita harus mengkhawatirkan ukuran Box<T, A> untuk implementasi awal, tetapi ini adalah sesuatu yang dapat ditambahkan nanti dengan cara yang kompatibel dengan mundur dengan menambahkan sifat DeAlloc yang hanya mendukung deallocation.

Contoh:

trait DeAlloc {
    fn dealloc(&mut self, ptr: NonNull<Opaque>, layout: Layout);
}

trait Alloc {
    // In addition to the existing trait items
    type DeAlloc: DeAlloc = Self;
    fn into_dealloc(self) -> Self::DeAlloc {
        self
    }
}

impl<T: Alloc> DeAlloc for T {
    fn dealloc(&mut self, ptr: NonNull<Opaque>, layout: Layout) {
        Alloc::dealloc(self, ptr, layout);
    }
}

Saya pikir bahkan sebelum kita berpikir tentang menstabilkan sifat Alloc, pertama-tama kita harus mencoba menerapkan dukungan pengalokasi di semua koleksi perpustakaan standar. Ini akan memberi kita pengalaman tentang bagaimana sifat ini akan digunakan dalam praktik.

Saya pikir @ Ericson2314 telah mengerjakan ini, sesuai https://github.com/rust-lang/rust/issues/42774. Akan menyenangkan mendapatkan pembaruan darinya.

Saya tidak berpikir kita harus mengkhawatirkan ukuran Box<T, A> untuk implementasi awal, tetapi ini adalah sesuatu yang dapat ditambahkan nanti dengan cara yang kompatibel dengan mundur dengan menambahkan sifat DeAlloc yang hanya mendukung deallocation.

Itu satu pendekatan, tetapi sama sekali tidak jelas bagi saya bahwa itu pasti yang terbaik. Ini memiliki kelemahan yang berbeda, misalnya, bahwa a) hanya berfungsi ketika pointer -> pencarian pengalokasi dimungkinkan (ini tidak benar, misalnya, sebagian besar pengalokasi arena) dan, b) menambahkan overhead yang signifikan ke dealloc (yaitu, untuk melakukan pencarian terbalik). Mungkin pada akhirnya solusi terbaik untuk masalah ini adalah efek tujuan umum atau sistem konteks seperti proposal ini atau Alloc .

@joshlf Mempertimbangkan fakta bahwa Box<T, A> hanya memiliki akses ke dirinya sendiri ketika dijatuhkan, ini adalah hal terbaik yang dapat kita lakukan dengan kode aman saja. Pola seperti itu mungkin berguna untuk pengalokasi mirip arena yang memiliki no-op dealloc dan hanya mengosongkan memori ketika pengalokasi dihapus.

Untuk sistem yang lebih rumit di mana pengalokasi dimiliki oleh sebuah wadah (misalnya LinkedList ) dan mengatur banyak alokasi, saya berharap bahwa Box tidak akan digunakan secara internal. Sebaliknya, internal LinkedList akan menggunakan pointer mentah yang dialokasikan dan dibebaskan dengan instance Alloc yang terdapat dalam objek LinkedList . Ini akan menghindari penggandaan ukuran setiap penunjuk.

Mengingat fakta bahwa Box<T, A> hanya memiliki akses ke dirinya sendiri ketika dijatuhkan, ini adalah hal terbaik yang dapat kita lakukan hanya dengan kode yang aman. Pola seperti itu mungkin berguna untuk pengalokasi mirip arena yang memiliki no-op dealloc dan hanya mengosongkan memori ketika pengalokasi dihapus.

Benar, tapi Box tidak tahu bahwa dealloc adalah no-op.

Untuk sistem yang lebih rumit di mana pengalokasi dimiliki oleh sebuah wadah (misalnya LinkedList ) dan mengatur banyak alokasi, saya berharap bahwa Box tidak akan digunakan secara internal. Sebaliknya, internal LinkedList akan menggunakan pointer mentah yang dialokasikan dan dibebaskan dengan instance Alloc yang terdapat dalam objek LinkedList . Ini akan menghindari penggandaan ukuran setiap penunjuk.

Saya pikir akan sangat memalukan untuk meminta orang menggunakan kode yang tidak aman untuk menulis koleksi apa pun. Jika tujuannya adalah untuk membuat semua koleksi (mungkin termasuk yang berada di luar perpustakaan standar) secara opsional parametrik pada pengalokasi, dan Box bukan pengalokasi-parametrik, maka pembuat koleksi tidak boleh menggunakan Box sama sekali atau gunakan kode yang tidak aman (dan perlu diingat bahwa mengingat untuk selalu membebaskan sesuatu adalah salah satu jenis ketidakamanan memori yang paling umum di C dan C ++, jadi sulit untuk mendapatkan kode yang tidak aman saat itu). Itu sepertinya tawaran yang tidak menguntungkan.

Benar, tapi Box tidak tahu bahwa dealloc adalah no-op.

Mengapa tidak menyesuaikan apa yang dilakukan C ++ unique_ptr ?
Yaitu: untuk menyimpan pointer ke pengalokasi jika "stateful", dan tidak menyimpannya jika pengalokasi "stateless"
(misalnya pembungkus global sekitar malloc atau mmap ).
Ini akan membutuhkan untuk membagi Alloc traint saat ini menjadi dua sifat: StatefulAlloc dan StatelessAlloc .
Saya menyadari bahwa itu sangat kasar dan tidak elegan (dan mungkin seseorang telah mengusulkannya dalam diskusi sebelumnya).
Meskipun inelegance solusi ini sederhana dan kompatibel ke belakang (tanpa penalti kinerja).

Saya pikir akan sangat memalukan untuk meminta orang menggunakan kode yang tidak aman untuk menulis koleksi apa pun. Jika tujuannya adalah untuk membuat semua koleksi (mungkin termasuk yang berada di luar pustaka standar) secara opsional parametrik pada pengalokasi, dan Box bukan pengalokasi-parametrik, maka penulis koleksi tidak boleh menggunakan Box sama sekali atau menggunakan kode yang tidak aman (dan perlu diingat bahwa mengingat untuk selalu membebaskan sesuatu adalah salah satu jenis ketidakamanan memori yang paling umum di C dan C ++, jadi sulit untuk mendapatkan kode yang tidak aman saat itu). Itu sepertinya tawaran yang tidak menguntungkan.

Saya khawatir bahwa penerapan efek atau sistem konteks yang memungkinkan seseorang untuk menulis wadah berbasis simpul seperti daftar, pohon, dll dengan cara yang aman mungkin memakan waktu terlalu banyak (jika mungkin pada prinsipnya).
Saya tidak melihat makalah atau bahasa akademis yang menangani masalah ini (tolong, perbaiki saya jika karya seperti itu benar-benar ada).

Jadi menggunakan unsafe dalam implementasi kontainer berbasis node mungkin merupakan kejahatan yang diperlukan, setidaknya dalam perspektif jangka pendek.

@eucpp Perhatikan bahwa unique_ptr tidak menyimpan pengalokasi-- ia menyimpan Deleter :

Deleter harus berupa referensi FunctionObject atau lvalue ke FunctionObject atau referensi lvalue ke fungsi, dapat dipanggil dengan argumen tipe unique_ptr:: pointer`

Saya melihat ini kira-kira sama dengan kami memberikan split Alloc dan Dealloc .

@cramertj Ya, Anda benar. Namun, dua sifat yang dibutuhkan - stateful dan stateless Dealloc .

Bukankah ZST Dealloc cukup?

Pada Sel, 12 Jun 2018 jam 15.08 Evgeniy Moiseenko [email protected]
menulis:

@cramertj https://github.com/cramertj Ya, Anda benar. Tetap saja, dua
sifat yang diperlukan - Dealloc stateful dan stateless.

-
Anda menerima ini karena Anda disebutkan.
Balas email ini secara langsung, lihat di GitHub
https://github.com/rust-lang/rust/issues/32838#issuecomment-396716689 ,
atau nonaktifkan utasnya
https://github.com/notifications/unsubscribe-auth/AEAJtWkpF0ofVc18NwbfV45G4QY6SCFBks5t8B_AgaJpZM4IDYUN
.

Bukankah ZST Dealloc cukup?

@remex Saya kira itu akan :)

Saya tidak tahu bahwa kompiler karat mendukung ZST di luar kotak.
Di C ++, dibutuhkan setidaknya beberapa trik seputar pengoptimalan basis kosong.
Saya cukup baru di Rust jadi maaf untuk beberapa kesalahan yang jelas.

Saya tidak berpikir kita membutuhkan ciri-ciri terpisah untuk stateful vs stateless.

Dengan Box ditambah dengan parameter tipe A , itu akan berisi nilai A secara langsung, bukan referensi atau penunjuk ke A . Tipe itu bisa berukuran nol untuk pengalokasi stateless (de). Atau A itu sendiri bisa menjadi sesuatu seperti referensi atau pegangan untuk pengalokasi stateful yang bisa dibagi antara beberapa objek yang dialokasikan. Jadi, alih-alih impl Alloc for MyAllocator , Anda mungkin ingin melakukan sesuatu seperti impl<'r> Alloc for &'r MyAllocator

Omong-omong, Box yang hanya tahu cara membatalkan alokasi dan bukan cara mengalokasikan tidak akan mengimplementasikan Clone .

@SimonSapin Saya berharap bahwa Clone ing akan memerlukan penetapan pengalokasi lagi, dengan cara yang sama seperti membuat Box (yaitu, itu tidak akan dilakukan dengan menggunakan Clone sifat).

@cramertj Bukankah tidak konsisten dibandingkan dengan Vec dan wadah lain yang mengimplementasikan Clone ?
Apa kerugian dari menyimpan contoh Alloc di dalam Box daripada Dealloc ?
Kemudian Box mungkin menerapkan Clone dan juga clone_with_alloc .

Saya tidak mengatakan bahwa ciri-ciri perpecahan benar-benar memengaruhi Klon secara besar-besaran - implikasinya hanya akan terlihat seperti impl<T, A> Clone for Box<T, A> where A: Alloc + Dealloc + Clone { ... } .

@sfackler Saya tidak akan menentang impl itu, tetapi saya juga berharap memiliki clone_into atau sesuatu yang menggunakan pengalokasi yang disediakan.

Apakah masuk akal jika metode alloc_copy menjadi Alloc ? Ini dapat digunakan untuk menyediakan implementasi memcpy ( Copy/Clone ) yang lebih cepat untuk alokasi yang besar, misalnya dengan melakukan klon halaman salin-saat-menulis.

Itu akan sangat keren, dan sepele untuk menyediakan implementasi default.

Apa yang akan menggunakan fungsi alloc_copy ? impl Clone for Box<T, A> ?

Ya, ditto seharga Vec .

Setelah memeriksanya lebih jauh, sepertinya pendekatan untuk membuat halaman copy-on-write dalam rentang proses yang sama antara hacky dan tidak mungkin, setidaknya jika Anda ingin melakukannya lebih dari satu level. Jadi alloc_copy tidak akan menjadi keuntungan yang besar.

Alih-alih jalan keluar yang lebih umum yang memungkinkan kejahatan memori virtual di masa depan mungkin berguna. Yaitu jika alokasi besar, tetap didukung oleh mmap dan stateless maka pengalokasi dapat berjanji untuk tidak menyadari perubahan alokasi di masa mendatang. Pengguna kemudian dapat memindahkan memori itu ke pipa, menghapus peta atau hal serupa.
Atau mungkin ada pengalokasi mmap-all-the-things bodoh dan fungsi coba-transfer.

Alih-alih pintu keluar yang lebih umum yang memungkinkan memori virtual di masa depan

Pengalokasi memori (malloc, jemalloc, ...) biasanya tidak membiarkan Anda mencuri memori apa pun darinya, dan mereka biasanya tidak mengizinkan Anda menanyakan atau mengubah properti apa dari memori yang mereka miliki. Jadi apa hubungan jalan keluar umum ini dengan pengalokasi memori?

Selain itu, dukungan memori virtual sangat berbeda antar platform, sehingga penggunaan memori virtual secara efektif sering kali memerlukan algoritme yang berbeda per platform, sering kali dengan jaminan yang sangat berbeda. Saya telah melihat beberapa abstraksi portabel melalui memori virtual, tetapi saya belum melihat satu pun yang tidak lumpuh hingga tidak berguna dalam beberapa situasi karena "portabilitas" mereka.

Kamu benar. Kasus penggunaan seperti itu (saya sebagian besar memikirkan pengoptimalan khusus platform) mungkin paling baik dilayani dengan menggunakan pengalokasi khusus di tempat pertama.

Adakah pendapat tentang Composable Allocator API yang dijelaskan oleh Andrei Alexandrescu dalam presentasi CppCon? Video tersebut tersedia di YouTube di sini: https://www.youtube.com/watch?v=LIb3L4vKZ7U (dia mulai mendeskripsikan desain yang diusulkannya sekitar pukul 26:00, tetapi pembicaraannya cukup menghibur sehingga Anda mungkin lebih suka menontonnya) .

Sepertinya kesimpulan yang tak terhindarkan dari semua ini adalah bahwa perpustakaan koleksi harus berupa alokasi WRT umum, dan pemrogram aplikasi sendiri harus dapat membuat pengalokasi dan koleksi secara bebas di lokasi konstruksi.

Adakah pendapat tentang Composable Allocator API yang dijelaskan oleh Andrei Alexandrescu dalam presentasi CppCon?

API Alloc memungkinkan penulisan pengalokasi yang dapat disusun (mis. MyAlloc<Other: Alloc> ) dan Anda dapat menggunakan ciri dan spesialisasi untuk mencapai hampir semua yang dicapai dalam percakapan Andreis. Namun, di luar "gagasan" bahwa seseorang harus dapat melakukan itu, hampir tidak ada dari pembicaraan Andrei yang dapat diterapkan pada Rust karena cara Andrei membangun API menggunakan obat generik yang tidak dibatasi + SFINAE / statis jika dari awal dan sistem generik Rust benar-benar berbeda dengan yang itu.

Saya ingin mengusulkan untuk menstabilkan sisa metode Layout . Ini sudah berguna dengan API pengalokasi global saat ini.

Apakah ini semua metode yang Anda maksud?

  • pub fn align_to(&self, align: usize) -> Layout
  • pub fn padding_needed_for(&self, align: usize) -> usize
  • pub fn repeat(&self, n: usize) -> Result<(Layout, usize), LayoutErr>
  • pub fn extend(&self, next: Layout) -> Result<(Layout, usize), LayoutErr>
  • pub fn repeat_packed(&self, n: usize) -> Result<Layout, LayoutErr>
  • pub fn extend_packed(&self, next: Layout) -> Result<(Layout, usize), LayoutErr>
  • pub fn array<T>(n: usize) -> Result<Layout, LayoutErr>

@juliaputri

@Amanieu Kedengarannya oke bagi saya, tetapi masalah ini sudah sangat besar. Pertimbangkan untuk mengajukan masalah terpisah (atau bahkan PR stabilisasi) bahwa kita dapat FCP secara terpisah?

dari Alokator dan masa hidup :

  1. (untuk pengalokasi impls): memindahkan nilai pengalokasi tidak boleh membatalkan blok memori yang luar biasa.

    Semua klien dapat mengasumsikan ini dalam kode mereka.

    Jadi jika klien mengalokasikan blok dari pengalokasi (sebut a1) dan kemudian a1 pindah ke tempat baru (misalnya vialet a2 = a1;), maka tetap sehat bagi klien untuk membatalkan alokasi blok itu melalui a2.

Apakah ini menyiratkan, bahwa Allocator harus Unpin ?

Tangkapan yang bagus!

Karena sifat Alloc masih tidak stabil, saya pikir kita masih bisa mengubah aturan jika kita ingin dan memodifikasi bagian RFC ini. Tapi itu memang sesuatu yang perlu diingat.

@gnzlbg Ya, saya menyadari perbedaan besar dalam sistem generik, dan tidak semua detailnya dapat diimplementasikan dengan cara yang sama di Rust. Saya telah bekerja di perpustakaan berkali-kali sejak memposting, dan saya membuat kemajuan yang baik.

Apakah ini menyiratkan, bahwa Allocator _must_ menjadi Unpin ?

Tidak. Unpin adalah tentang perilaku suatu tipe ketika dibungkus dalam Pin , tidak ada koneksi khusus ke API ini.

Tetapi tidak bisakah Unpin digunakan untuk menjalankan batasan yang disebutkan?

Pertanyaan lain tentang dealloc_array : Mengapa fungsi mengembalikan Result ? Dalam implementasi saat ini, ini mungkin gagal dalam dua kasus:

  • n adalah nol
  • kapasitas melimpah sebesar n * size_of::<T>()

Untuk kasus pertama kami memiliki dua kasus (seperti dalam dokumentasi, pelaksana dapat memilih di antara ini):

  • Alokasi mengembalikan Ok pada nol n => dealloc_array juga harus mengembalikan Ok .
  • Alokasi mengembalikan Err pada nol n => tidak ada penunjuk yang dapat diteruskan ke dealloc_array .

Yang kedua dipastikan oleh kendala keamanan berikut:

tata letak [T; n] harus sesuai dengan blok memori itu.

Artinya, kita harus memanggil dealloc_array dengan n sama seperti dalam alokasi. Jika array dengan n elemen dapat dialokasikan, n berlaku untuk T . Jika tidak, alokasi akan gagal.

Edit: Mengenai poin terakhir: Meskipun usable_size mengembalikan nilai yang lebih tinggi dari n * size_of::<T>() , ini masih berlaku. Jika tidak, implementasi melanggar batasan sifat ini:

Ukuran blok harus berada pada kisaran [use_min, use_max] , dimana:

  • [...]
  • use_max adalah kapasitas yang (atau akan) dikembalikan ketika (jika) blok dialokasikan melalui panggilan ke alloc_excess atau realloc_excess .

Ini hanya berlaku, karena sifat tersebut membutuhkan unsafe impl .

Untuk kasus pertama kami memiliki dua kasus (seperti dalam dokumentasi, pelaksana dapat memilih di antara ini):

  • Alokasi mengembalikan Ok pada nol n

Darimana Anda mendapatkan informasi ini?

Semua Alloc::alloc_ metode di dokumen menetapkan bahwa perilaku alokasi berukuran nol tidak ditentukan di bawah klausa "Keamanan" mereka.

Docs of core::alloc::Alloc (bagian relevan yang disorot):

Catatan mengenai tipe berukuran nol dan tata letak berukuran nol: banyak metode dalam status sifat Alloc bahwa permintaan alokasi harus berukuran bukan nol, atau perilaku tidak terdefinisi dapat terjadi.

  • Namun, beberapa metode alokasi tingkat yang lebih tinggi ( alloc_one , alloc_array ) didefinisikan dengan baik pada tipe berukuran nol dan secara opsional dapat mendukungnya : terserah pelaksana apakah akan mengembalikan Err , atau untuk mengembalikan Ok dengan beberapa pointer.
  • Jika implementasi Alloc memilih untuk mengembalikan Ok dalam kasus ini (yaitu pointer menunjukkan blok berukuran nol yang tidak dapat diakses) maka pointer yang dikembalikan harus dianggap "saat ini dialokasikan". Pada pengalokasi seperti itu, semua metode yang mengambil pointer yang saat ini dialokasikan sebagai input harus menerima pointer berukuran nol ini, tanpa menyebabkan perilaku yang tidak ditentukan.

  • Dengan kata lain, jika pointer berukuran nol dapat mengalir keluar dari sebuah pengalokasi, maka pengalokasi tersebut juga harus menerima pointer yang mengalir kembali ke metode deallocation dan realokasi .

Jadi salah satu kondisi kesalahan dealloc_array pasti mencurigakan:

/// # Safety
///
/// * the layout of `[T; n]` must *fit* that block of memory.
///
/// # Errors
///
/// Returning `Err` indicates that either `[T; n]` or the given
/// memory block does not meet allocator's size or alignment
/// constraints.

Jika [T; N] tidak memenuhi ukuran pengalokasi atau batasan penyelarasan, maka AFAICT tidak sesuai dengan blok memori alokasi, dan perilaku tidak ditentukan (sesuai klausa keamanan).

Kondisi kesalahan lainnya adalah "Selalu mengembalikan Err saat aritmatika meluap." yang cukup umum. Sulit untuk mengatakan apakah ini merupakan kondisi kesalahan yang berguna. Untuk setiap implementasi sifat Alloc seseorang mungkin dapat menghasilkan yang berbeda yang dapat melakukan aritmatika yang dapat dibungkus teori, jadi 🤷‍♂️


Docs of core::alloc::Alloc (bagian relevan yang disorot):

Memang. Saya merasa aneh bahwa begitu banyak metode (misalnya Alloc::alloc ) menyatakan bahwa alokasi berukuran nol adalah perilaku yang tidak terdefinisi, tetapi kemudian kami hanya menyediakan Alloc::alloc_array(0) dengan perilaku yang ditentukan implementasi. Dalam beberapa hal Alloc::alloc_array(0) adalah tes lakmus untuk memeriksa apakah pengalokasi mendukung alokasi berukuran nol atau tidak.

Jika [T; N] tidak memenuhi ukuran pengalokasi atau batasan penyelarasan, maka AFAICT tidak sesuai dengan blok memori alokasi, dan perilaku tidak ditentukan (sesuai klausul keamanan).

Yup, menurut saya kondisi error ini bisa dihilangkan karena mubazir. Entah kita memerlukan klausa keamanan atau kondisi kesalahan, tetapi tidak keduanya.

Kondisi kesalahan lainnya adalah "Selalu mengembalikan Err pada luapan aritmatika." yang cukup umum. Sulit untuk mengatakan apakah ini merupakan kondisi kesalahan yang berguna.

IMO, itu dijaga oleh klausul keamanan yang sama seperti di atas; jika kapasitas [T; N] akan meluap, itu tidak sesuai dengan blok memori untuk membatalkan alokasi. Mungkin @pnkfelix bisa menjelaskan lebih lanjut tentang ini?

Dalam beberapa hal Alloc::alloc_array(1) adalah tes lakmus untuk memeriksa apakah pengalokasi mendukung alokasi berukuran nol atau tidak.

Apakah maksud Anda Alloc::alloc_array(0) ?

IMO, itu dijaga oleh klausul keamanan yang sama seperti di atas; jika kapasitas [T; N] akan meluap, _fit_ blok memori itu tidak akan dialokasikan.

Perhatikan bahwa sifat ini dapat diterapkan oleh pengguna untuk pengalokasi khusus mereka sendiri, dan bahwa pengguna ini dapat mengganti penerapan default metode ini. Jadi ketika mempertimbangkan apakah ini harus mengembalikan Err untuk aritmatika overflow atau tidak, seseorang tidak hanya harus fokus pada apa yang dilakukan implementasi default saat ini dari metode default, tetapi juga mempertimbangkan apa yang mungkin masuk akal bagi pengguna yang menerapkan ini untuk yang lain. pengalokasi.

Apakah maksud Anda Alloc::alloc_array(0) ?

Ya maaf.

Perhatikan bahwa sifat ini dapat diterapkan oleh pengguna untuk pengalokasi khusus mereka sendiri, dan bahwa pengguna ini dapat mengganti penerapan default metode ini. Jadi ketika mempertimbangkan apakah ini harus mengembalikan Err untuk aritmatika overflow atau tidak, seseorang tidak hanya harus fokus pada apa yang dilakukan implementasi default saat ini dari metode default, tetapi juga mempertimbangkan apa yang mungkin masuk akal bagi pengguna yang menerapkan ini untuk yang lain. pengalokasi.

Begitu, tetapi menerapkan Alloc memerlukan unsafe impl dan pelaksana harus mengikuti aturan keamanan yang disebutkan di https://github.com/rust-lang/rust/issues/32838#issuecomment -467093527 .

Setiap API yang tersisa di sini untuk masalah pelacakan adalah sifat Alloc atau terkait dengan sifat Alloc . @ rust-lang / libs, apakah menurut Anda berguna untuk tetap membuka ini selain https://github.com/rust-lang/rust/issues/42774?

Pertanyaan latar belakang sederhana: Apa motivasi di balik fleksibilitas dengan ZST? Tampaknya bagi saya, mengingat bahwa kita tahu pada waktu kompilasi bahwa suatu tipe adalah ZST, kita dapat sepenuhnya mengoptimalkan alokasi (untuk mengembalikan nilai konstan) dan deallocation. Mengingat itu, menurut saya kita harus mengatakan salah satu dari yang berikut:

  • Itu selalu tergantung pada pelaksana untuk mendukung ZST, dan mereka tidak dapat mengembalikan Err untuk ZST
  • Itu selalu UB untuk mengalokasikan ZST, dan itu tanggung jawab pemanggil untuk hubungan pendek dalam kasus ini
  • Ada semacam metode alloc_inner yang diterapkan pemanggil, dan metode alloc dengan implementasi default yang melakukan hubungan arus pendek; alloc harus mendukung ZST, tetapi alloc_inner TIDAK boleh dipanggil untuk ZST (ini hanya agar kita dapat menambahkan logika hubung singkat di satu tempat - dalam definisi sifat - secara berurutan untuk menghemat pelaksana beberapa boilerplate)

Adakah alasan diperlukannya fleksibilitas yang kami miliki dengan API saat ini?

Adakah alasan diperlukannya fleksibilitas yang kami miliki dengan API saat ini?

Ini trade-off. Bisa dibilang, sifat Alloc lebih sering digunakan daripada diimplementasikan, jadi masuk akal untuk menggunakan Alloc semudah mungkin dengan menyediakan dukungan bawaan untuk ZST.

Ini berarti bahwa pelaksana sifat Alloc perlu menangani hal ini, tetapi yang lebih penting bagi saya bahwa mereka yang mencoba mengembangkan sifat Alloc perlu mengingat ZST pada setiap perubahan API. Ini juga memperumit dokumen API dengan menjelaskan bagaimana ZST (atau bisa jadi jika "implementasi ditentukan") ditangani.

Pengalokasi C ++ menggunakan pendekatan ini, di mana pengalokasi mencoba untuk memecahkan banyak masalah yang berbeda. Hal ini tidak hanya membuat mereka lebih sulit untuk diterapkan dan lebih sulit untuk berkembang, tetapi juga lebih sulit bagi pengguna untuk benar-benar menggunakannya karena bagaimana semua masalah ini berinteraksi dalam API.

Menurut saya, menangani ZST, dan mengalokasikan / melepas alokasi memori mentah adalah dua masalah ortogonal dan berbeda, dan oleh karena itu kita harus menjaga API sifat Alloc sederhana dengan tidak menanganinya.

Pengguna Alloc seperti libstd perlu menangani ZST, misalnya, pada setiap koleksi. Itu jelas merupakan masalah yang layak dipecahkan, tapi menurut saya sifat Alloc bukan tempatnya. Saya mengharapkan utilitas untuk memecahkan masalah ini muncul dalam libstd karena kebutuhan, dan ketika itu terjadi, kita mungkin dapat mencoba RFC seperti utilitas tersebut dan memaparkannya di std :: heap.

Kedengarannya masuk akal.

Menurut saya, menangani ZST, dan mengalokasikan / melepas alokasi memori mentah adalah dua masalah ortogonal dan berbeda, dan oleh karena itu kita harus menjaga API sifat Alloc sederhana dengan tidak menanganinya.

Bukankah itu menyiratkan bahwa kita harus memiliki API yang secara eksplisit tidak menangani ZST daripada didefinisikan oleh implementasi? IMO, kesalahan "tidak didukung" tidak terlalu membantu pada waktu proses karena sebagian besar pemanggil tidak akan dapat menentukan jalur fallback, dan oleh karena itu harus berasumsi bahwa ZST tidak didukung. Tampak lebih mudah untuk hanya menyederhanakan API dan menyatakan bahwa mereka _never_ didukung.

Apakah spesialisasi akan digunakan oleh alloc pengguna untuk menangani ZST? Atau hanya cek if size_of::<T>() == 0 ?

Apakah spesialisasi akan digunakan oleh alloc pengguna untuk menangani ZST? Atau hanya cek if size_of::<T>() == 0 ?

Yang terakhir harus cukup; jalur kode yang sesuai akan dihapus pada waktu kompilasi.

Bukankah itu menyiratkan bahwa kita harus memiliki API yang secara eksplisit tidak menangani ZST daripada didefinisikan oleh implementasi?

Bagi saya, kendala penting adalah jika kita melarang alokasi berukuran nol, metode Alloc harus dapat mengasumsikan bahwa Layout diberikan kepada mereka bukanlah berukuran nol.

Ada banyak cara untuk mencapai ini. Salah satunya adalah menambahkan klausa Safety ke semua metode Alloc yang menyatakan bahwa jika Layout berukuran nol, perilaku tidak terdefinisi.

Alternatifnya, kita bisa melarang Layout s berukuran nol, kemudian Alloc tidak perlu mengatakan apapun tentang alokasi berukuran nol karena ini tidak dapat terjadi dengan aman, tetapi melakukan itu akan memiliki beberapa kelemahan.

Misalnya, beberapa jenis seperti HashMap membangun Layout dari beberapa Layout s, dan sementara Layout mungkin tidak berukuran nol, yang perantara mungkin (misalnya dalam HashSet ). Jadi tipe ini perlu menggunakan "sesuatu yang lain" (misalnya tipe LayoutBuilder ) untuk membangun Layout s terakhir mereka, dan membayar cek "bukan berukuran nol" (atau menggunakan metode _unchecked ) saat mengonversi menjadi Layout .

Apakah spesialisasi digunakan oleh pengguna alokasi untuk menangani ZST? Atau hanya jika size_of ::() == 0 cek?

Kami belum bisa berspesialisasi pada ZST. Sekarang semua kode menggunakan size_of::<T>() == 0 .

Ada banyak cara untuk mencapai ini. Salah satunya adalah menambahkan klausa Safety ke semua metode Alloc yang menyatakan bahwa jika Layout berukuran nol, perilaku tidak terdefinisi.

Akan menarik untuk mempertimbangkan apakah ada cara untuk menjadikan ini jaminan waktu kompilasi, tetapi bahkan debug_assert bahwa tata letaknya tidak berukuran nol sudah cukup untuk menangkap 99% bug.

Saya belum memperhatikan diskusi tentang pengalokasi, jadi maaf tentang itu. Tapi saya sudah lama berharap bahwa pengalokasi memiliki akses ke jenis nilai pengalokasiannya. Mungkin ada desain pengalokasi yang bisa menggunakannya.

Kemudian kami mungkin memiliki masalah yang sama dengan C ++ dan itu adalah api alokator.

Tapi saya sudah lama berharap bahwa pengalokasi memiliki akses ke jenis nilai pengalokasiannya. T

Untuk apa Anda membutuhkan ini?

@gnzblg @brson Hari ini saya memiliki kasus penggunaan potensial untuk mengetahui _something_ tentang jenis nilai yang dialokasikan.

Saya sedang mengerjakan pengalokasi global yang dapat dialihkan di antara tiga pengalokasi yang mendasari - utas lokal, yang global dengan kunci, dan satu untuk coroutine untuk digunakan - idenya adalah saya dapat membatasi coroutine yang mewakili koneksi jaringan secara maksimal jumlah penggunaan memori dinamis (dengan tidak adanya kemampuan untuk mengontrol pengalokasi dalam koleksi, khususnya dalam kode pihak ketiga) *.

Mungkin akan berguna untuk mengetahui apakah saya mengalokasikan nilai yang mungkin berpindah melintasi utas (misalnya Arc) vs yang tidak. Atau mungkin juga tidak. Tapi ini skenario yang mungkin. Saat ini pengalokasi global memiliki sakelar yang digunakan pengguna untuk memberi tahu pengalokasi mana yang akan membuat alokasi (tidak diperlukan untuk realokasi atau gratis; kita hanya dapat melihat alamat memori untuk itu).

* [Ini juga memungkinkan saya untuk menggunakan memori lokal NUMA jika memungkinkan tanpa penguncian apa pun, dan, dengan model utas 1 inti 1, untuk membatasi total penggunaan memori].

@bayu_joo

Saya sedang mengerjakan pengalokasi global

Saya tidak berpikir semua ini akan (atau dapat) berlaku untuk sifat GlobalAlloc , dan sifat Alloc sudah memiliki metode umum yang dapat menggunakan informasi jenis (mis. alloc_array<T>(1) mengalokasikan satu T , di mana T adalah tipe sebenarnya, sehingga pengalokasi dapat mempertimbangkan tipe tersebut saat melakukan alokasi). Saya pikir akan lebih berguna untuk tujuan diskusi ini untuk benar-benar melihat pengalokasi implementasi kode yang menggunakan informasi tipe. Saya belum pernah mendengar argumen bagus tentang mengapa metode ini perlu menjadi bagian dari beberapa sifat pengalokasi umum, sebagai lawan hanya menjadi bagian dari API pengalokasi, atau beberapa sifat pengalokasi lainnya.

Saya pikir juga akan sangat menarik untuk mengetahui tipe mana yang diparameterisasi oleh Alloc yang ingin Anda gabungkan dengan pengalokasi yang menggunakan informasi tipe, dan apa yang Anda harapkan sebagai hasilnya.

AFAICT, satu-satunya jenis yang menarik untuk itu adalah Box karena mengalokasikan T secara langsung. Hampir semua tipe lain di std tidak pernah mengalokasikan T , tapi beberapa tipe internal pribadi yang pengalokasi Anda tidak tahu apa-apa. Misalnya, Rc dan Arc dapat mengalokasikan (InternalRefCounts, T) , List / BTreeSet / dll. Mengalokasikan jenis simpul internal, Vec / Deque / ... mengalokasikan array T s, tetapi bukan T s itu sendiri, dll.

Untuk Box dan Vec kita dapat menambahkan dengan cara yang kompatibel ke belakang BoxAlloc dan ArrayAlloc dengan implikasi selimut untuk Alloc yang pengalokasi dapat mengkhususkan diri untuk membajak bagaimana mereka berperilaku, jika ada kebutuhan untuk mengatasi masalah ini dengan cara yang umum. Tetapi adakah alasan mengapa memberikan tipe MyAllocBox dan MyAllocVec milik Anda sendiri yang berkonspirasi dengan pengalokasi Anda untuk mengeksploitasi informasi tipe bukanlah solusi yang layak?

Karena kami sekarang memiliki repositori khusus untuk pengalokasi WG , dan daftar di OP sudah kedaluwarsa, masalah ini mungkin ditutup untuk menjaga diskusi dan pelacakan fitur ini di satu tempat?

Poin yang bagus @TimDiekmann! Saya akan melanjutkan dan menutup ini demi utas diskusi di repositori itu.

Ini masih merupakan masalah pelacakan yang dirujuk oleh atribut #[unstable] . Saya pikir itu tidak boleh ditutup sampai fitur ini telah distabilkan atau tidak digunakan lagi. (Atau kami dapat mengubah atribut agar mengarah ke masalah lain.)

Ya, fitur tidak stabil yang direferensikan di git master pasti memiliki masalah pelacakan terbuka.

Sepakat. Juga menambahkan pemberitahuan dan tautan ke OP.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat