Rust: صفات التخصيص والأمراض المنقولة جنسياً :: heap

تم إنشاؤها على ٨ أبريل ٢٠١٦  ·  412تعليقات  ·  مصدر: rust-lang/rust

📢 هذه الميزة لديها مجموعة عمل مخصصة ، يرجى توجيه التعليقات والمخاوف إلى الريبو لمجموعة العمل .

المشاركة الأصلية:


اقتراح FCP: https://github.com/rust-lang/rust/issues/32838#issuecomment -336957415
مربعات اختيار FCP: https://github.com/rust-lang/rust/issues/32838#issuecomment -336980230


مشكلة تتبع لـ rust-lang / rfcs # 1398 والوحدة std::heap .

  • [x] land struct Layout ، trait Allocator والتطبيقات الافتراضية في صندوق alloc (https://github.com/rust-lang/rust/pull/42313)
  • [x] حدد المكان الذي يجب أن تعيش فيه الأجزاء (على سبيل المثال ، تعتمد الضمانات الافتراضية على صندوق alloc ، لكن Layout / Allocator _ يمكن أن يكون في libcore ...) (https://github.com/rust-lang/rust/pull/42313)
  • [] fixme من الكود المصدري: تدقيق عمليات التنفيذ الافتراضية (في Layout لأخطاء تجاوز السعة ، (من المحتمل التبديل إلى overflowing_add و overflowing_mul حسب الضرورة)
  • [x] حدد ما إذا كان يجب استبدال realloc_in_place بـ grow_in_place و shrink_in_place ( تعليق ) (https://github.com/rust-lang/rust/pull/42313)
  • [] استعراض الحجج لـ / مقابل نوع الخطأ المرتبط (انظر الموضوع الفرعي هنا )
  • [] تحديد المتطلبات على المحاذاة المقدمة إلى fn dealloc . (راجع المناقشة حول المخصص rfc والمخصص العام rfc والسمة Alloc PR .)

    • هل مطلوب إلغاء التخصيص مع align الذي تخصصه بالضبط؟ لقد أثيرت مخاوف من أن المخصصين مثل jemalloc لا يتطلبون ذلك ، ومن الصعب تصور وجود مخصص يتطلب ذلك. ( مزيد من المناقشة ). يبدو أنruuda و rkruppe لديهما أكبر قدر من الأفكار حتى الآن حول هذا الموضوع.

  • [] هل يجب أن يكون AllocErr Error بدلاً من ذلك؟ ( تعليق )
  • [x] هل مطلوب إلغاء التخصيص بالحجم الدقيق الذي تخصصه؟ من خلال العمل usable_size قد نرغب في السماح ، على سبيل المثال ، إذا قمت بتخصيص (size, align) فيجب عليك تخصيص حجم في مكان ما في نطاق size...usable_size(size, align) . يبدو أن jemalloc على ما يرام تمامًا مع هذا (لا يتطلب منك تخصيص size محدد size الذي تخصصه معه) وهذا سيسمح أيضًا Vec للاستفادة بشكل طبيعي من السعة الزائدة jemalloc يعطيها عندما تقوم بالتخصيص. (على الرغم من أن القيام بذلك في الواقع أمر متعامد إلى حد ما مع هذا القرار ، إلا أننا نقوم فقط بتمكين Vec ). حتى الآن لدى Gankro معظم الأفكار حول هذا الموضوع. (يعتقد alexcrichton أن هذا قد تمت تسويته في https://github.com/rust-lang/rust/pull/42313 نظرًا لتعريف "يناسب")
  • [] مشابه للسؤال السابق: هل مطلوب إلغاء التخصيص مع المحاذاة الدقيقة التي خصصتها لها؟ (انظر التعليق من 5 يونيو 2017 )
  • [x] OSX / alloc_system عبارة عن عربات التي تجرها الدواب في محاذاة ضخمة (على سبيل المثال محاذاة 1 << 32 ) https://github.com/rust-lang/rust/issues/30170 # 43217
  • [] هل يجب أن يقدم Layout طريقة fn stride(&self) ؟ (انظر أيضًا https://github.com/rust-lang/rfcs/issues/1397 ، https://github.com/rust-lang/rust/issues/17027)
  • [x] Allocator::owns كطريقة؟ https://github.com/rust-lang/rust/issues/44302

حالة std::heap بعد 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

التعليق الأكثر فائدة

alexcrichton قد يكون قرار التبديل من -> Result<*mut u8, AllocErr> إلى -> *mut void مفاجأة كبيرة للأشخاص الذين تابعوا التطوير الأصلي لـ RFC المخصص للمخصص.

لا أختلف مع النقاط التي تثيرها ، ولكن يبدو أن عددًا لا بأس به من الأشخاص سيكونون على استعداد للعيش مع "الوزن الثقيل" Result أكثر من الاحتمال المتزايد لفقدان قيمة خالية- تحقق من القيمة التي تم إرجاعها.

  • إنني أتجاهل مشكلات كفاءة وقت التشغيل التي يفرضها ABI لأنني ، مثل

هل هناك طريقة ما يمكننا من خلالها زيادة وضوح هذا التغيير المتأخر من تلقاء نفسه؟

طريقة واحدة (من أعلى رأسي): قم بتغيير التوقيع الآن ، في العلاقات العامة من تلقاء نفسه ، في الفرع الرئيسي ، بينما لا يزال Allocator غير مستقر. ثم انظر من يشتكي على العلاقات العامة (ومن يحتفل!).

  • هل هذا ثقيل جدا؟ يبدو أنه من خلال التعريفات أقل ثقلًا من اقتران مثل هذا التغيير بالاستقرار ...

ال 412 كومينتر

لسوء الحظ ، لم أكن أولي اهتمامًا كافيًا لذكر ذلك في مناقشة RFC ، لكنني أعتقد أنه يجب استبدال realloc_in_place بوظيفتين ، grow_in_place و shrink_in_place ، لشخصين الأسباب:

  • لا يمكنني التفكير في حالة استخدام واحدة (باستثناء تنفيذ realloc أو realloc_in_place ) حيث لا يُعرف ما إذا كان حجم التخصيص يتزايد أم يتناقص. إن استخدام أساليب أكثر تخصصًا يجعل الأمر أكثر وضوحًا قليلاً ما يجري.
  • تميل مسارات الكود لتنمية وتقلص التخصيصات إلى الاختلاف جذريًا - يتضمن النمو اختبار ما إذا كانت الكتل المجاورة من الذاكرة خالية والمطالبة بها ، بينما يتضمن التقليص نحت كتل فرعية بحجم مناسب وتحريرها. في حين أن تكلفة الفرع داخل realloc_in_place صغيرة جدًا ، فإن استخدام grow و shrink أفضل يجسد المهام المميزة التي يحتاج المخصّص لأدائها.

لاحظ أنه يمكن إضافة هذه الوظائف بشكل عكسي بجوار realloc_in_place ، لكن هذا من شأنه أن يقيد الوظائف التي سيتم تنفيذها افتراضيًا من حيث الوظائف الأخرى.

من أجل التناسق، realloc ربما تريد أيضا أن تكون تنقسم إلى grow و split ، ولكن الميزة الوحيدة إلى وجود overloadable realloc وظيفة وأنا أعلم أن من هو أن تكون قادرًا على استخدام خيار إعادة رسم خريطة mmap ، والذي لا يحتوي على مثل هذا التمييز.

بالإضافة إلى ذلك ، أعتقد أن عمليات التنفيذ الافتراضية لـ realloc و realloc_in_place يجب تعديلها بشكل طفيف - بدلاً من التحقق من usable_size ، realloc يجب أن تحاول أولاً فقط realloc_in_place . في المقابل ، يجب أن يتحقق realloc_in_place افتراضيًا من الحجم القابل للاستخدام ويعيد النجاح في حالة حدوث تغيير صغير بدلاً من إرجاع الفشل عالميًا.

هذا يجعل من السهل إنتاج تنفيذ عالي الأداء realloc : كل ​​ما هو مطلوب هو تحسين realloc_in_place . ومع ذلك ، فإن الأداء الافتراضي لـ realloc لا يتأثر ، حيث لا يزال يتم تنفيذ الشيك مقابل usable_size .

مشكلة أخرى: يوضح مستند fn realloc_in_place أنه إذا عاد "موافق" ، فمن المؤكد أن ptr الآن "يناسب" new_layout .

بالنسبة لي ، هذا يعني أنه يجب التحقق من أن محاذاة العنوان المحدد تطابق أي قيد ضمني بواسطة new_layout .

ومع ذلك ، لا أعتقد أن المواصفات الخاصة بالوظيفة الأساسية fn reallocate_inplace تشير إلى أن _it_ ستقوم بأي فحص من هذا القبيل.

  • علاوة على ذلك ، يبدو من المعقول أن أي عميل يغوص في استخدام fn realloc_in_place سيضمن بنفسه عمل المحاذاة (من الناحية العملية أظن أن ذلك يعني أن نفس المحاذاة مطلوبة في كل مكان لحالة الاستخدام المحددة ...)

لذا ، هل يجب أن يكون تنفيذ fn realloc_in_place مثقلًا بالتحقق من أن محاذاة ptr المعطاة متوافقة مع new_layout ؟ ربما يكون من الأفضل _ في هذه الحالة _ (من هذه الطريقة الواحدة) إعادة هذا المطلب إلى المتصل ...

gereeter قمت بعمل نقاط جيدة ؛ سأضيفها إلى قائمة التحقق التي أراكمها في وصف المشكلة.

(في هذه المرحلة ، أنا في انتظار دعم #[may_dangle] لركوب القطار في قناة beta حتى أتمكن بعد ذلك من استخدامه لمجموعات std كجزء من تكامل المخصص)

أنا جديد على Rust ، لذا سامحني إذا تمت مناقشة هذا في مكان آخر.

هل هناك أي فكرة حول كيفية دعم المخصصات الخاصة بكائنات معينة؟ يرتبط بعض المُخصصات مثل مخصصات الألواح ومخصصات المجلات بنوع معين ، ويقومون بعمل إنشاء كائنات جديدة ، والتخزين المؤقت للكائنات التي تم "تحريرها" (بدلاً من إسقاطها بالفعل) ، وإرجاع الكائنات المخزنة مؤقتًا التي تم إنشاؤها بالفعل ، و إسقاط الكائنات قبل تحرير الذاكرة الأساسية للمخصص الأساسي عند الحاجة.

حاليًا ، لا يتضمن هذا الاقتراح أي شيء على غرار ObjectAllocator<T> ، ولكنه سيكون مفيدًا للغاية. على وجه الخصوص ، أنا أعمل على تنفيذ طبقة التخزين المؤقت للكائنات لمخصص المجلات (الرابط أعلاه) ، وعلى الرغم من أنه يمكنني فقط لف Allocator والقيام بعمل إنشاء وإفلات الكائنات في التخزين المؤقت الطبقة نفسها ، سيكون أمرًا رائعًا إذا كان بإمكاني أيضًا الحصول على مخصصات كائن أخرى ملتفة (مثل مخصص اللوح) وأن أكون حقًا طبقة تخزين مؤقت عامة.

أين يمكن أن يتناسب نوع مخصص أو سمة كائن مع هذا الاقتراح؟ هل سيتم تركها لـ RFC في المستقبل؟ شيء آخر؟

لا أعتقد أن هذا قد نوقش بعد.

يمكنك كتابة ObjectAllocator<T> الخاص بك ، ثم كتابة impl<T: Allocator, U> ObjectAllocator<U> for T { .. } ، بحيث يمكن لكل مخصص عادي أن يعمل كمخصص خاص بكائن لجميع الكائنات.

سيكون العمل المستقبلي هو تعديل المجموعات لاستخدام سماتك لعقدها ، بدلاً من المخصصات البسيطة (العامة) مباشرة.

تضمين التغريدة

(في هذه المرحلة ، أنتظر دعم # [may_dangle] لركوب القطار في قناة بيتا حتى أتمكن بعد ذلك من استخدامه لمجموعات الأمراض المنقولة جنسياً كجزء من تكامل المخصص)

أعتقد أن هذا قد حدث؟

@ Ericson2314 نعم ، كتابة ما لدي هو بالتأكيد خيار للأغراض التجريبية ، لكنني أعتقد أنه سيكون هناك فائدة أكبر بكثير من ObjectAllocator<T> أو شيء من هذا القبيل يستحق المناقشة. على الرغم من أنه يبدو أنه قد يكون من الأفضل لـ RFC مختلف؟ لست على دراية تامة بالإرشادات الخاصة بمدى الانتماء إلى RFC واحد وعندما تنتمي الأشياء إلى RFCs منفصلة ...

تضمين التغريدة

أين يمكن أن يتناسب نوع مخصص أو سمة كائن مع هذا الاقتراح؟ هل سيتم تركها لـ RFC في المستقبل؟ شيء آخر؟

نعم ، سيكون RFC آخر.

لست على دراية تامة بالإرشادات الخاصة بمدى الانتماء إلى RFC واحد وعندما تنتمي الأشياء إلى RFCs منفصلة ...

يعتمد ذلك على نطاق RFC نفسه ، والذي يقرره الشخص الذي يكتبه ، ثم يتم تقديم التعليقات من قبل الجميع.

ولكن في الحقيقة ، نظرًا لأن هذه مشكلة تتبع لـ RFC التي تم قبولها بالفعل ، فإن التفكير في الإضافات وتغييرات التصميم ليس في الواقع لهذا الموضوع ؛ يجب عليك فتح حساب جديد في RFCs repo.

joshlf آه ، اعتقدت أن ObjectAllocator<T> من المفترض أن يكون سمة. قصدت النموذج الأولي للسمة وليس مخصصًا محددًا. نعم ، ستستحق هذه السمة RFC الخاصة بها كما يقول


steveklabnik نعم المناقشة الآن سيكون أفضل في مكان آخر. لكن joshlf كان يثير أيضًا المشكلة خشية أن يكشف عيبًا غير متوقع حتى الآن في تصميم واجهة برمجة التطبيقات المقبولة ولكن غير المنفذة. وبهذا المعنى فإنه يتطابق مع المشاركات السابقة في هذا الموضوع.

@ Ericson2314 نعم ، اعتقدت أن هذا هو ما تعنيه. أعتقد أننا في نفس الصفحة :)

steveklabnik تبدو جيدة ؛ سوف أتطرق إلى تطبيقي الخاص وأرسل RFC إذا انتهى به الأمر وكأنه فكرة جيدة.

joshlf ليس لدي أي سبب يجعل

alexreg لا يتعلق الأمر Allocator ) وهي نوع أي مخصص منخفض المستوى ، أنا أسأل عن سمة ( ObjectAllocator<T> ) هي نوع أي مخصص يمكن تخصيص / إلغاء تخصيص وإنشاء / إسقاط كائنات من النوع T .

alexreg شاهد نقطتي المبكرة حول استخدام مجموعات المكتبة القياسية مع المخصصات المخصصة الخاصة بالكائنات.

بالتأكيد ، لكنني لست متأكدًا من أن ذلك سيكون في المكتبة القياسية. يمكن أن تذهب بسهولة إلى صندوق آخر ، دون فقدان الوظائف أو سهولة الاستخدام.

في 4 يناير 2017 ، الساعة 21:59 ، كتب Joshua Liebow-Feeser [email protected] :

alexreg https://github.com/alexreg لا يتعلق الأمر بمخصص مخصص معين ، بل بالأحرى سمة تحدد نوع جميع المخصصات التي تعتبر حدوديًا لنوع معين. لذلك تمامًا مثلما يحدد RFC 1398 سمة (مخصص) وهي نوع أي مخصص منخفض المستوى ، أنا أسأل عن سمة (ObjectAllocator) هذا هو نوع أي مخصص يمكنه تخصيص / إلغاء تخصيص وإنشاء / إسقاط كائنات من النوع T.

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرةً ، أو قم بعرضه على GitHub https://github.com/rust-lang/rust/issues/32838#issuecomment-270499064 ، أو تجاهل الموضوع https://github.com/notifications/unsubscribe-auth/ AAEF3IhyyPhFgu1EGHr_GM_Evsr0SRzIks5rPBZGgaJpZM4IDYUN .

أعتقد أنك تريد استخدام مجموعات المكتبة القياسية (أي قيمة مخصصة للكومة) مع مخصص مخصص تعسفي ؛ أي لا يقتصر على الكائنات الخاصة.

في 4 يناير 2017 ، الساعة 22:01 ، كتب John Ericson [email protected] :

alexreg https://github.com/alexreg اطلع على نقطتي المبكرة حول استخدام مجموعات المكتبة القياسية مع المخصصات المخصصة الخاصة بالكائنات.

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذه الرسالة الإلكترونية مباشرةً ، أو قم بعرضها على GitHub https://github.com/rust-lang/rust/issues/32838#issuecomment-270499628 ، أو تجاهل الموضوع https://github.com/notifications/unsubscribe-auth/ AAEF3CrjYIXqcv8Aqvb4VTyPcajJozICks5rPBbOgaJpZM4IDYUN .

بالتأكيد ، لكنني لست متأكدًا من أن ذلك سيكون في المكتبة القياسية. يمكن أن تذهب بسهولة إلى صندوق آخر ، دون فقدان الوظائف أو سهولة الاستخدام.

نعم ، لكن ربما تريد أن تعتمد بعض وظائف المكتبة القياسية عليها (مثل ما اقترحه @ Ericson2314 ).

أعتقد أنك تريد استخدام مجموعات المكتبة القياسية (أي قيمة مخصصة للكومة) مع مخصص مخصص تعسفي ؛ أي لا يقتصر على الكائنات الخاصة.

من الناحية المثالية ، تريد كليهما - قبول أي نوع من المخصصات. هناك فوائد كبيرة جدًا لاستخدام التخزين المؤقت الخاص بالكائن ؛ على سبيل المثال ، يوفر كل من تخصيص الألواح والتخزين المؤقت للمجلات مزايا أداء مهمة للغاية - ألق نظرة على الأوراق التي ربطتها أعلاه إذا كنت مهتمًا.

لكن سمة مخصص الكائن يمكن أن تكون ببساطة صورة فرعية لسمة المخصص العام. الأمر بهذه البساطة ، بقدر ما أشعر بالقلق. بالتأكيد ، يمكن أن تكون أنواع معينة من المخصصات أكثر كفاءة من المخصصات ذات الأغراض العامة ، ولكن لا يحتاج المحول البرمجي ولا المعيار (أو يجب عليهما) معرفة ذلك.

في 4 يناير 2017 ، الساعة 22:13 ، كتب Joshua Liebow-Feeser [email protected] :

بالتأكيد ، لكنني لست متأكدًا من أن ذلك سيكون في المكتبة القياسية. يمكن أن تذهب بسهولة إلى صندوق آخر ، دون فقدان الوظائف أو سهولة الاستخدام.

نعم ، لكن ربما تريد أن تعتمد بعض وظائف المكتبة القياسية عليها (مثل ما اقترحه @ Ericson2314 https://github.com/Ericson2314 ).

أعتقد أنك تريد استخدام مجموعات المكتبة القياسية (أي قيمة مخصصة للكومة) مع مخصص مخصص تعسفي ؛ أي لا يقتصر على الكائنات الخاصة.

من الناحية المثالية ، تريد كليهما - قبول أي نوع من المخصصات. هناك فوائد كبيرة جدًا لاستخدام التخزين المؤقت الخاص بالكائن ؛ على سبيل المثال ، يوفر كل من تخصيص الألواح والتخزين المؤقت للمجلات مزايا أداء مهمة للغاية - ألق نظرة على الأوراق التي ربطتها أعلاه إذا كنت مهتمًا.

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذه الرسالة الإلكترونية مباشرةً ، أو قم بعرضها على GitHub https://github.com/rust-lang/rust/issues/32838#issuecomment-270502231 ، أو تجاهل الموضوع https://github.com/notifications/unsubscribe-auth/ AAEF3L9F9r_0T5evOtt7Es92vw6gBxR9ks5rPBl9gaJpZM4IDYUN .

لكن سمة مخصص الكائن يمكن أن تكون ببساطة صورة فرعية لسمة المخصص العام. الأمر بهذه البساطة ، بقدر ما أشعر بالقلق. بالتأكيد ، يمكن أن تكون أنواع معينة من المخصصات أكثر كفاءة من المخصصات ذات الأغراض العامة ، ولكن لا يحتاج المحول البرمجي ولا المعيار (أو يجب عليهما) معرفة ذلك.

آه ، إذن المشكلة أن الدلالات مختلفة. يخصص Allocator النقط البايت الخام ويحررها. من ناحية أخرى ، سيخصص ObjectAllocator<T> كائنات تم إنشاؤها بالفعل وسيكون مسؤولاً أيضًا عن إسقاط هذه الكائنات (بما في ذلك القدرة على تخزين الكائنات المُنشأة مؤقتًا والتي يمكن تسليمها لاحقًا مقابل إنشاء كائن مخصص حديثًا ، وهو مكلف). ستبدو السمة مثل هذا:

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

هذا غير متوافق مع Allocator ، الذي تتعامل أساليبه مع المؤشرات الأولية وليس لها فكرة عن النوع. بالإضافة إلى ذلك ، مع Allocator s ، تقع على عاتق المتصل مسؤولية تحرير الكائن أولاً drop . هذا مهم حقًا - معرفة نوع T يسمح ObjectAllocator<T> بتنفيذ أشياء مثل استدعاء T 's drop ، ومنذ free(t) التحركات t إلى free ، المتصل _cannot_ إسقاط t أولا - أنها بدلا من ذلك ObjectAllocator<T> الصورة المسؤولية. في الأساس ، هاتان السمتان لا تتوافقان مع بعضهما البعض.

آه صحيح ، فهمت. اعتقدت أن هذا الاقتراح تضمن بالفعل شيئًا من هذا القبيل ، أي مخصص "مستوى أعلى" فوق مستوى البايت. في هذه الحالة ، اقتراح عادل تمامًا!

في 4 يناير 2017 ، الساعة 22:29 ، كتب Joshua Liebow-Feeser [email protected] :

لكن سمة مخصص الكائن يمكن أن تكون ببساطة صورة فرعية لسمة المخصص العام. الأمر بهذه البساطة ، بقدر ما أشعر بالقلق. بالتأكيد ، يمكن أن تكون أنواع معينة من المخصصات أكثر كفاءة من المخصصات ذات الأغراض العامة ، ولكن لا يحتاج المحول البرمجي ولا المعيار (أو يجب عليهما) معرفة ذلك.

آه ، إذن المشكلة أن الدلالات مختلفة. يخصص المخصص ويحرر نقط البايت الخام. ObjectAllocator، من ناحية أخرى ، سيخصص كائنات تم إنشاؤها بالفعل وسيكون مسؤولاً أيضًا عن إسقاط هذه الكائنات (بما في ذلك القدرة على تخزين الكائنات المنشأة مؤقتًا والتي يمكن تسليمها لاحقًا في ليو لبناء كائن مخصص حديثًا ، وهو مكلف) ستبدو السمة مثل هذا:

خاصية ObjectAllocator{
fn تخصيص () -> T ؛
fn free (t T) ؛
}
هذا لا يتوافق مع Allocator ، التي تتعامل أساليبها مع المؤشرات الأولية وليس لها فكرة عن النوع. بالإضافة إلى ذلك ، مع أداة التخصيص ، تقع على عاتق المتصل مسؤولية إسقاط الكائن الذي تم تحريره أولاً. هذا مهم حقًا - معرفة النوع T يسمح بتخصيص ObjectAllocatorللقيام بأشياء مثل طريقة إسقاط الاستدعاء T ، وبما أن (t) يتحرك مجانًا ، لا يمكن للمتصل إسقاط t أولاً - إنه بدلاً من ذلك كائن تخصيصمسؤولية. في الأساس ، هاتان السمتان لا تتوافقان مع بعضهما البعض.

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذه الرسالة الإلكترونية مباشرةً ، أو قم بعرضها على GitHub https://github.com/rust-lang/rust/issues/32838#issuecomment-270505704 ، أو تجاهل الموضوع https://github.com/notifications/unsubscribe-auth/ AAEF3GViJBefuk8IWgPauPyL5tV78Fn5ks5rPB08gaJpZM4IDYUN .

alexreg آه ، نعم ، كنت أتمنى ذلك أيضًا :) حسنًا - سيتعين عليه انتظار RFC آخر.

نعم ، ابدأ هذا RFC ، أنا متأكد من أنه سيحصل على الكثير من الدعم! وشكرًا على التوضيح (لم أواكب تفاصيل RFC على الإطلاق).

في 5 كانون الثاني (يناير) 2017 ، الساعة 00:53 ، كتب Joshua Liebow-Feeser [email protected] :

alexreg https://github.com/alexreg آه ، نعم ، كنت أتمنى ذلك أيضًا :) حسنًا - سيتعين عليها انتظار طلب طلب تقديمي آخر.

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذه الرسالة الإلكترونية مباشرةً ، أو قم بعرضها على GitHub https://github.com/rust-lang/rust/issues/32838#issuecomment-270531535 ، أو تجاهل الموضوع https://github.com/notifications/unsubscribe-auth/ AAEF3MQQeXhTliU5CBsoheBFL26Ee9WUks5rPD8RgaJpZM4IDYUN .

سيكون من المفيد وجود صندوق لاختبار المخصصات المخصصة.

سامحني إذا فقدت شيئًا واضحًا ، ولكن هل هناك سبب لخاصية Layout الموصوفة في RFC هذا لعدم تنفيذ Copy بالإضافة إلى Clone ، لأنها مجرد جراب؟

لا أستطيع التفكير في أي شيء.

آسف لكوني أثيرت هذا في وقت متأخر جدًا من هذه العملية ، ولكن ...

هل يستحق الأمر إضافة دعم لوظيفة شبيهة dealloc ليست طريقة ، بل وظيفة؟ ستكون الفكرة هي استخدام المحاذاة لتكون قادرًا على الاستدلال من مؤشر حيث يكون المخصص الأصلي في الذاكرة وبالتالي يكون قادرًا على التحرير دون الحاجة إلى مرجع مخصص صريح.

قد يكون هذا مكسبًا كبيرًا لهياكل البيانات التي تستخدم مخصصات مخصصة. سيسمح لهم ذلك بعدم الاحتفاظ بمرجع للمخصص نفسه ، ولكن بدلاً من ذلك يحتاجون فقط إلى أن تكون حدوديًا على النوع _ من المخصص حتى يتمكنوا من استدعاء الوظيفة dealloc الصحيحة. على سبيل المثال ، إذا تم تعديل Box أخيرًا لدعم المخصصات المخصصة ، فسيكون بإمكانها الاحتفاظ بكلمة واحدة فقط (المؤشر فقط) بدلاً من الحاجة إلى التوسيع إلى كلمتين لتخزين مرجع للمخصص أيضًا.

في ملاحظة ذات صلة ، قد يكون من المفيد أيضًا دعم وظيفة غير تابعة للطريقة alloc للسماح بالمخصصات العامة. سيؤلف هذا بشكل جيد مع دالة dealloc غير الأسلوب - بالنسبة للمخصصين العالميين ، لن تكون هناك حاجة للقيام بأي نوع من الاستدلال من المؤشر إلى المخصص حيث سيكون هناك مثيل ثابت واحد فقط من مخصص للبرنامج بأكمله.

joshlf يسمح لك التصميم الحالي بالحصول على ذلك بمجرد جعل المُخصص الخاص بك من نوع وحدة (بحجم صفري) - على سبيل المثال struct MyAlloc; التي تقوم بعد ذلك بتنفيذ السمة Allocator عليها.
دائمًا ما يكون تخزين المراجع أو لا شيء على الإطلاق أقل عمومية من تخزين المخصص حسب القيمة.

يمكنني أن أرى أن هذا صحيح بالنسبة لنوع مضمّن بشكل مباشر ، ولكن ماذا لو قررت بنية البيانات الاحتفاظ بمرجع بدلاً من ذلك؟ هل الإشارة إلى النوع ذي الحجم الصفري تشغل مساحة صفرية؟ هذا إذا كان لدي:

struct Foo()

struct Blah{
    foo: &Foo,
}

هل حجم Blah يساوي صفرًا؟

في الواقع ، حتى لو كان ذلك ممكنًا ، فقد لا ترغب في أن يكون حجم مخصصك صفريًا. على سبيل المثال ، قد يكون لديك مُخصص بحجم غير صفري تقوم بتخصيصه _ من _ ، ولكن لديه القدرة على تحرير الكائنات دون معرفة المُخصص الأصلي. سيظل هذا مفيدًا لجعل Box يأخذ كلمة واحدة فقط. سيكون لديك شيء مثل Box::new_from_allocator والذي يجب أن يأخذ المُخصص كوسيطة - وقد يكون مُخصصًا بحجم غير صفري - ولكن إذا كان المُخصص قد دعم التحرير بدون مرجع المُخصص الأصلي ، فسيتم إرجاع Box<T> يمكن أن يتجنب Box::new_from_allocator .

على سبيل المثال ، قد يكون لديك مُخصص بحجم غير صفري تقوم بتخصيصه منه ، ولكن لديه القدرة على تحرير الكائنات دون معرفة المُخصص الأصلي.

أتذكر منذ فترة طويلة ، منذ فترة طويلة ، اقتراحًا لعوملة سمات مخصص ومخصص منفصل (مع أنواع مرتبطة تربط الاثنين) لهذا السبب أساسًا.

هل / هل يجب السماح للمترجم بتحسين التخصيصات البعيدة مع هؤلاء المخصصين؟

هل / هل يجب السماح للمترجم بتحسين التخصيصات البعيدة مع هؤلاء المخصصين؟

Zoxc ماذا تقصد؟

أتذكر منذ فترة طويلة ، منذ فترة طويلة ، اقتراحًا لعوملة سمات مخصص ومخصص منفصل (مع أنواع مرتبطة تربط الاثنين) لهذا السبب أساسًا.

للأجيال القادمة ، اسمحوا لي أن أوضح هذا البيان (تحدثت إلى @ Ericson2314 حول هذا الموضوع في وضع عدم الاتصال): الفكرة هي أن Box يمكن أن يكون حدوديًا على أداة التخصيص. لذلك يمكنك الحصول على التنفيذ التالي:

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()
        }
    }
}

بهذه الطريقة ، عند استدعاء new_from_allocator ، إذا كان A::D من النوع الصفري ، فإن الحقل d من Box<T, A::D> يأخذ حجمًا صفريًا ، وبالتالي حجم الناتج Box<T, A::D> هو كلمة واحدة.

هل هناك جدول زمني لموعد هبوط هذا؟ أنا أعمل على بعض الأشياء المخصصة ، وسيكون من الجيد أن تكون هذه الأشياء موجودة لي لأبني عليها.

إذا كان هناك اهتمام ، فسأكون سعيدًا بإعارة بعض الدورات لهذا ، لكنني جديد نسبيًا على Rust ، لذلك قد يؤدي ذلك إلى خلق المزيد من العمل للمشرفين من حيث الاضطرار إلى مراجعة كود مبتدئ. لا أريد أن أخطو على أصابع قدم أي شخص ، ولا أريد المزيد من العمل للناس.

حسنًا ، لقد اجتمعنا مؤخرًا

أحد الأشياء التي ناقشناها هو أن التغيير على جميع أنواع libstd قد يكون سابقًا لأوانه بعض الشيء بسبب مشكلات الاستدلال المحتملة ، ولكن بغض النظر عن ذلك يبدو أنها فكرة جيدة للحصول على السمة Allocator و Layout اكتب std::heap المقترحة للتجربة في مكان آخر في النظام البيئي!

@ joshlf إذا كنت ترغب في المساعدة هنا أعتقد أنه سيكون أكثر من موضع ترحيب! من المحتمل أن يكون الجزء الأول هو النوع / السمة الأساسية من RFC هذا في المكتبة القياسية ، ومن ثم يمكننا البدء في التجريب والتلاعب بالمجموعات في libstd أيضًا.

alexcrichton أعتقد أن الرابط الخاص بك معطل؟ يشير مرة أخرى هنا.

شيء واحد ناقشناه هو أن التغيير على جميع أنواع libstd قد يكون سابقًا لأوانه بعض الشيء بسبب مشكلات الاستدلال المحتملة

تعد إضافة السمة خطوة أولى جيدة ، ولكن بدون إعادة بناء واجهات برمجة التطبيقات الحالية لاستخدامها ، لن ترى الكثير من الاستخدام. في https://github.com/rust-lang/rust/issues/27336#issuecomment -300721558 أقترح أنه يمكننا إعادة تصنيع الصناديق خلف الواجهة على الفور ، ولكن إضافة أغلفة من النوع الجديد في std . القيام بذلك أمر مزعج ، ولكنه يسمح لنا بإحراز تقدم.

alexcrichton ما هي عملية الحصول على هنا إلى الاعتقاد بأنه سيكون هناك تناسق شبه مثالي بين سمات المخصص والكائن سمات المخصص. على سبيل المثال ، سيكون لديك شيء مثل (لقد قمت بتغيير Address إلى *mut u8 للتماثل مع *mut T من ObjectAllocator<T> ؛ ربما ينتهي بنا الأمر بـ Address<T> أو شيء من هذا القبيل):

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);
}

وبالتالي ، أعتقد أن تجربة كل من المخصصات ومخصصات الكائنات في نفس الوقت قد يكون مفيدًا. لست متأكدًا مما إذا كان هذا هو المكان المناسب لذلك ، أو ما إذا كان يجب أن يكون هناك RFC آخر أو ، على الأقل ، علاقات عامة منفصلة.

يا قصدت الارتباط هنا الذي يحتوي أيضًا على معلومات حول المخصص العالمي. @ joshlf هل هذا ما تفكر فيه؟

يبدو أن alexcrichton يريد Allocator ونوع Layout ، حتى لو لم يتم دمجها في أي مجموعة في libstd .

إذا فهمت ذلك بشكل صحيح ، فعندئذ يمكنني طرح العلاقات العامة لذلك. لم أفعل ذلك لأنني ما زلت أحاول الحصول على تكامل على الأقل مع النموذج الأولي RawVec و Vec . (في هذه المرحلة ، قمت بإنجاز RawVec ، لكن Vec يمثل تحديًا أكثر قليلاً بسبب العديد من الهياكل الأخرى التي تنشأ منه ، مثل Drain و IntoIter إلخ ...)

في الواقع ، يبدو أن فرعي الحالي قد يبني بالفعل (وقد اجتاز الاختبار الوحيد للتكامل مع RawVec ) ، لذلك تقدمت ونشرته: # 42313

hawkw سأل:

سامحني إذا فقدت شيئًا واضحًا ، ولكن هل هناك سبب لخاصية Layout الموضحة في RFC هذه لعدم تنفيذ Copy وكذلك Clone ، نظرًا لأنها مجرد POD؟

السبب في أنني جعلت من Layout تطبيق Clone وليس Copy هو أنني أردت ترك إمكانية إضافة المزيد من البنية إلى النوع Layout مفتوحًا. على وجه الخصوص ، ما زلت مهتمًا بمحاولة الحصول على محاولة Layout لتتبع أي نوع من الهياكل المستخدمة في إنشائها (على سبيل المثال ، 16 مجموعة من struct { x: u8, y: [char; 215] } ) ، بحيث يكون للمخصصين خيار يعرض إجراءات الأجهزة التي تُبلغ عن الأنواع التي يتم تكوين محتوياتها الحالية منها.

من شبه المؤكد أن تكون هذه ميزة اختيارية ، أي يبدو أن التيار يتعارض بشدة مع إجبار المطورين على استخدام المنشئات Layout المخصب بالنوع. لذا فإن أي جهاز من هذا النموذج يجب أن يتضمن شيئًا مثل فئة "كتل ذاكرة غير معروفة" للتعامل مع التخصيصات التي لا تحتوي على معلومات النوع.

ولكن مع ذلك ، كانت ميزات مثل هذه هي السبب الرئيسي لعدم اختيار جعل Layout ينفذ Copy ؛ لقد أدركت بشكل أساسي أن تنفيذ Copy سيكون قيدًا سابقًا لأوانه على Layout نفسه.

تضمين التغريدة

يبدو أن pnkfelix قد غطى هذا ، وأن العلاقات العامة ننتقل إلى ذلك. أنا أتفحصها وأدلي بتعليقات الآن ، وهي تبدو رائعة!

حاليًا توقيع Allocator::oom هو:

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

ومع ذلك ، تم لفت انتباهي إلى أن Gecko يحب على الأقل معرفة حجم التخصيص وكذلك على OOM. قد نرغب في النظر في هذا عند الاستقرار ربما لإضافة سياق مثل Option<Layout> لسبب حدوث OOM.

تضمين التغريدة
هل من المفيد وجود متغيرات متعددة oom_xxx أو تعداد أنواع مختلفة من الوسائط؟ هناك توقيعان مختلفان للطرق التي قد تفشل (على سبيل المثال ، يأخذ alloc تخطيطًا ، ويأخذ realloc مؤشرًا وتخطيطًا أصليًا وتخطيطًا جديدًا ، وما إلى ذلك) ، وقد يكون هناك هي الحالات التي تريد فيها طريقة oom -like التعرف عليها جميعًا.

joshlf هذا صحيح ، نعم ، لكنني لست متأكدًا مما إذا كان مفيدًا. لا أريد إضافة ميزات فقط لأننا نستطيع ذلك ، يجب أن يستمروا في التحفيز الجيد.


نقطة التثبيت هنا هي أيضًا "تحديد المتطلبات على المحاذاة المقدمة إلى fn dealloc " ، والتنفيذ الحالي لـ dealloc على Windows يستخدم align لتحديد كيفية بشكل صحيح مجاني. ruuda قد تكون مهتمًا بهذه الحقيقة.

نقطة التثبيت هنا هي أيضًا "تحديد المتطلبات على المحاذاة المقدمة إلى fn dealloc " ، والتنفيذ الحالي لـ dealloc على Windows يستخدم align لتحديد كيفية بشكل صحيح مجاني.

نعم ، أعتقد أن هذه هي الطريقة التي واجهتني بها في البداية ؛ تعطل برنامجي على Windows بسبب هذا. نظرًا لأن HeapAlloc لا يقدم أي ضمانات محاذاة ، فإن allocate يخصص منطقة أكبر ويخزن المؤشر الأصلي في رأس ، ولكن كتحسين ، يتم تجنب ذلك إذا كان سيتم تلبية متطلبات المحاذاة على أي حال. أتساءل عما إذا كانت هناك طريقة لتحويل HeapAlloc إلى مُخصص مدرك للمواءمة لا يتطلب المحاذاة مجانًا ، دون فقدان هذا التحسين.

روضة

نظرًا لأن HeapAlloc لا يقدم أي ضمانات محاذاة

إنه يوفر حدًا أدنى من ضمان المحاذاة يبلغ 8 بايت لـ 32 بت أو 16 بايت لـ 64 بت ، ولا يوفر أي طريقة لضمان محاذاة أعلى من ذلك.

يمكن أن يوفر _aligned_malloc المقدم من CRT على Windows تخصيصات لمحاذاة أعلى ، ولكن بشكل خاص يجب إقرانه بـ _aligned_free ، باستخدام free غير قانوني. لذلك إذا كنت لا تعرف ما إذا كان التخصيص قد تم عبر malloc أو _aligned_malloc فأنت عالق في نفس اللغز المتمثل في أن alloc_system موجود على Windows إذا لم تفعل لا تعرف المحاذاة لـ deallocate . وCRT لا يوفر القياسية aligned_alloc ظيفة التي يمكن إرفاقها مع free ، لذلك لم يكن حتى مايكروسوفت قادرة على حل هذه المشكلة. (على الرغم من أنها دالة C11 ولا تدعم Microsoft C11 ، فهذه حجة ضعيفة.)

لاحظ أن deallocate يهتم فقط بالمحاذاة لمعرفة ما إذا كان محاذيًا ، فالقيمة الفعلية نفسها غير ذات صلة. إذا كنت تريد deallocate الذي كان حقًا محاذاة مستقلة ، فيمكنك ببساطة معاملة جميع التخصيصات على أنها متوازنة ، لكنك ستضيع الكثير من الذاكرة على التخصيصات الصغيرة.

alexcrichton كتب :

حاليًا توقيع Allocator::oom هو:

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

ومع ذلك ، تم لفت انتباهي إلى أن Gecko يحب على الأقل معرفة حجم التخصيص وكذلك على OOM. قد نرغب في النظر في هذا عند الاستقرار ربما لإضافة سياق مثل Option<Layout> لسبب حدوث OOM.

يحمل AllocErr بالفعل Layout في المتغير AllocErr::Exhausted . يمكننا فقط إضافة Layout إلى المتغير AllocErr::Unsupported أيضًا ، والذي أعتقد أنه سيكون أبسط من حيث توقعات العميل. (هناك عيب في زيادة جانب التعداد AllocErr نفسه بشكل طفيف ، لكن ربما لا ينبغي أن نقلق بشأن ذلك ...)

أوه أظن أن هذا كل ما هو مطلوب ، شكرًا على التصحيحpnkfelix!

سأبدأ في إعادة تحديد الغرض من هذه المشكلة لمسألة التتبع مقابل std::heap بشكل عام كما سيكون بعد https://github.com/rust-lang/rust/pull/42727 الأراضي. سأغلق بعض القضايا الأخرى ذات الصلة لصالح هذا.

هل هناك مشكلة في تتبع تحويل المجموعات؟ الآن بعد أن تم دمج PRs ، أود ذلك

  • ناقش نوع الخطأ المرتبط
  • ناقش تحويل المجموعات لاستخدام أي مخصص محلي (خاصة الاستفادة من نوع الخطأ المرتبط)

لقد فتحت https://github.com/rust-lang/rust/issues/42774 لتتبع تكامل Alloc في مجموعات std. من خلال المناقشة التاريخية في فريق libs ، من المحتمل أن يكون على مسار منفصل للاستقرار من التمرير الأولي للوحدة std::heap .

أثناء مراجعة المشكلات المتعلقة بالمخصص ، صادفت أيضًا https://github.com/rust-lang/rust/issues/30170 والتي تعود إلى pnkfelix منذ فترة. يبدو أن مخصص نظام OSX هو عربات التي تجرها الدواب مع محاذاة عالية وعند تشغيل هذا البرنامج مع jemalloc ، فإنه يتأثر أثناء إلغاء التخصيص على Linux على الأقل. يستحق النظر أثناء التثبيت!

لقد فتحت # 42794 كمكان لمناقشة السؤال المحدد حول ما إذا كانت التخصيصات ذات الحجم الصفري تحتاج إلى مطابقة المحاذاة المطلوبة.

(انتظر ، التخصيصات ذات الحجم الصفري تعتبر غير قانونية في مخصصات المستخدم!)

منذ أن اختفت وظيفة alloc::heap::allocate والأصدقاء الآن في Nightly ، لقد قمت بتحديث Servo لاستخدام واجهة برمجة التطبيقات الجديدة هذه. هذا جزء من الفرق:

-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;

أشعر أن بيئة العمل ليست رائعة. انتقلنا من استيراد عنصر واحد إلى استيراد ثلاثة من وحدتين مختلفتين.

  • هل من المنطقي أن يكون لديك طريقة ملائمة لـ allocator.alloc(Layout::from_size_align(…)) ؟
  • هل من المنطقي إتاحة طرق <Heap as Alloc>::_ كوظائف مجانية أو طرق متأصلة؟ (للحصول على عنصر واحد أقل للاستيراد ، السمة Alloc .)

بدلاً من ذلك ، هل يمكن أن تكون السمة Alloc في المقدمة أم أنها مناسبة جدًا لحالة الاستخدام؟

SimonSapin IMO ليس هناك الكثير من النقاط في تحسين بيئة العمل لمثل هذا المستوى المنخفض من API.

تضمين التغريدة

أشعر أن بيئة العمل ليست رائعة. انتقلنا من استيراد عنصر واحد إلى استيراد ثلاثة من وحدتين مختلفتين.

كان لدي نفس الشعور بالضبط مع قاعدة الشيفرة الخاصة بي - إنه عديم الجدوى الآن.

هل من المنطقي أن يكون لديك طريقة ملائمة لـ allocator.alloc(Layout::from_size_align(…))?

هل تقصد في السمة Alloc ، أم فقط Heap ؟ شيء واحد يجب مراعاته هنا هو أن هناك الآن حالة خطأ ثالثة: Layout::from_size_align تُرجع Option ، لذلك يمكن أن تُرجع None بالإضافة إلى الأخطاء العادية التي يمكنك الحصول عليها عند التخصيص .

بدلاً من ذلك ، هل يمكن أن تكون السمة Alloc في المقدمة أم أنها مناسبة جدًا لحالة الاستخدام؟

IMO ليس هناك الكثير من النقاط في تحسين بيئة العمل لمثل هذا المستوى المنخفض من API.

أوافق على أنه من المحتمل أن يكون المستوى منخفضًا جدًا لوضعه في المقدمة ، لكن ما زلت أعتقد أن هناك قيمة في تحسين بيئة العمل (بشكل أناني ، على الأقل - كان ذلك إعادة بناء مزعج حقًا 😝).

SimonSapin ألم تتعامل مع OOM من قبل؟ أيضًا في std تتوفر الأنواع الثلاثة في الوحدة النمطية std::heap (من المفترض أن تكون في وحدة واحدة). كما أنك لم تتعامل مع قضية الأحجام الفائضة من قبل؟ أو الأنواع ذات الحجم الصفري؟

ألم تتعامل مع OOM من قبل؟

عند وجودها ، أرجعت الدالة alloc::heap::allocate مؤشرًا بدون Result ولم تترك خيارًا في معالجة OOM. أعتقد أنه أجهض العملية. لقد أضفت الآن .unwrap() لتفزع الموضوع.

من المفترض أن يكونوا في وحدة واحدة

أرى الآن أن heap.rs يحتوي على pub use allocator::*; . ولكن عندما نقرت على Alloc في الضمانة المدرجة في صفحة rustdoc لـ Heap تم إرسالي إلى alloc::allocator::Alloc .

أما البقية فلم أقم بالنظر فيها. أنا أنقل إلى مترجم جديد كومة كبيرة من التعليمات البرمجية التي تمت كتابتها منذ سنوات. أعتقد أن هذه عمليات رد نداء لـ FreeType ، مكتبة سي.

عند وجودها ، أرجع دالة تخصيص :: heap :: تخصيص مؤشر بدون نتيجة ولم تترك خيارًا في معالجة OOM.

لقد أعطتك خيارًا. قد يكون المؤشر الذي تم إرجاعه مؤشرًا فارغًا يشير إلى فشل مخصص الكومة في التخصيص. هذا هو السبب في أنني سعيد للغاية لأنه تحول إلى Result حتى لا ينسى الناس التعامل مع هذه الحالة.

حسنًا ، ربما انتهى الأمر بـ FreeType بإجراء فحص فارغ ، لا أعرف. على أي حال ، نعم ، إعادة النتيجة أمر جيد.

بالنظر إلى # 30170 و # 43097 ، فإنني أميل إلى حل مشكلة OS X بمحاذاة كبيرة يبعث على السخرية من خلال تحديد أنه لا يمكن للمستخدمين طلب محاذاة> = 1 << 32 .

إحدى الطرق السهلة جدًا لفرض هذا: قم بتغيير واجهة Layout بحيث يتم الإشارة إلى align بواسطة u32 بدلاً من usize .

alexcrichton هل لديك أفكار حول هذا؟ هل يجب أن أقوم فقط بعمل علاقات عامة تقوم بهذا؟

pnkfelix Layout::from_size_align سيظل يأخذ usize ويعيد خطأ في u32 الفائض ، أليس كذلك؟

SimonSapin ما سبب استمراره في أخذ محاذاة usize ، إذا كان الشرط المسبق الثابت هو أنه من غير الآمن تمرير قيمة> = 1 << 32 ؟

وإذا كانت الإجابة "حسنًا ، قد يدعم بعض المُخصصين المحاذاة> = 1 << 32 " ، فإننا نعود إلى الوضع الراهن ويمكنك تجاهل اقتراحي. الهدف من اقتراحي هو في الأساس "+1" لتعليقات مثل هذه

لأن std::mem::align_of يُرجع usize

SimonSapin آه ، API مستقرة قديمة جيدة ... تنهد.

pnkfelix الحد من 1 << 32 يبدو معقولًا بالنسبة لي!

تضمينrfcbot fcp

حسنًا ، لقد تم خبز هذه السمة وأنواعها لبعض الوقت الآن وكانت أيضًا التنفيذ الأساسي للمجموعات القياسية منذ إنشائها. أود أن أقترح البدء بعرض أولي متحفظ بشكل خاص ، أي فقط تثبيت الواجهة التالية:

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 {
    // ...
}

الاقتراح الأصلي

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 {
    // ...
}

بشكل خاص:

  • فقط تثبيت طرق alloc و alloc_zeroed و dealloc على السمة Alloc في الوقت الحالي. أعتقد أن هذا يحل المشكلة الأكثر إلحاحًا التي لدينا اليوم ، وهي تحديد مخصص عالمي مخصص.
  • قم بإزالة النوع Error لصالح استخدام المؤشرات الأولية فقط.
  • قم بتغيير النوع u8 في الواجهة إلى void
  • نسخة مجردة من النوع Layout .

لا تزال هناك أسئلة مفتوحة مثل ما يجب فعله بـ dealloc والمحاذاة (المحاذاة الدقيقة؟ تناسبها؟ غير متأكد؟) ، لكنني آمل أن نتمكن من حلها أثناء FCP لأنه من المحتمل ألا يكون API- كسر التغيير.

+1 للحصول على شيء مستقر!

يعيد تسمية AllocErr إلى Error وينقل الواجهة لتكون أكثر تحفظًا.

هل هذا يلغي خيار تحديد المخصصين Unsupported ؟ مع خطر العزف على شيء كنت أعزف عليه كثيرًا ، أعتقد أن # 44557 لا يزال يمثل مشكلة.

Layout

يبدو أنك أزلت بعض الطرق من Layout . هل تقصد إزالة العناصر التي تركتها بالفعل ، أو تركها غير مستقرة؟

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

هل هذا مُنشئ لما هو اليوم AllocErr::Exhausted ؟ إذا كان الأمر كذلك ، ألا يجب أن يحتوي على معلمة Layout ؟

اقترح عضو الفريق alexcrichton دمج هذا. الخطوة التالية هي المراجعة بواسطة باقي الفرق الموسومة:

  • [x] @ بورنتسوشي
  • [x] Kimundi
  • [x]alexcrichton
  • [x]aturon
  • [x]cramertj
  • [x] dtolnay
  • [x]eddyb
  • [x]nikomatsakis
  • [x]nrc
  • [x]pnkfelix
  • [x]sfackler
  • [x] @ بدون قوارب

اهتمامات:

بمجرد أن يتوصل هؤلاء المراجعون إلى إجماع ، سيدخل هذا فترة التعليق النهائية. إذا اكتشفت مشكلة كبيرة لم تُثار في أي وقت في هذه العملية ، فيرجى التحدث!

راجع هذا المستند للحصول على معلومات حول الأوامر التي يمكن أن يقدمها لي أعضاء الفريق.

أنا متحمس حقًا لتحقيق الاستقرار في بعض هذا العمل!

سؤال واحد: في الموضوع أعلاه ، أثارjoshlf و @ Ericson2314 نقطة مثيرة للاهتمام حول إمكانية فصل السمات Alloc و Dealloc أجل تحسين الحالات التي يكون فيها alloc يتطلب dealloc لا يتطلب أي معلومات إضافية ، لذلك يمكن أن يكون النوع Dealloc بحجم صفري.

هل تم حل هذا السؤال من قبل؟ ما هي مساوئ فصل السمتين؟

تضمين التغريدة

هل يؤدي ذلك إلى إلغاء خيار تحديد "غير مدعوم" للمُخصّصين؟

نعم ولا ، فهذا يعني أن مثل هذه العملية ليست مدعومة في صدأ مستقر على الفور ، ولكن يمكننا الاستمرار في دعمها في حالة الصدأ غير المستقر.

هل تقصد إزالة العناصر التي تركتها بالفعل ، أو تركها غير مستقرة؟

في الواقع! مرة أخرى ، على الرغم من أنني أقترح فقط مساحة سطح مستقرة لواجهة برمجة التطبيقات ، يمكننا ترك جميع الطرق الأخرى غير مستقرة. بمرور الوقت يمكننا الاستمرار في تثبيت المزيد من الوظائف. أعتقد أنه من الأفضل أن نبدأ بأسلوب محافظ قدر الإمكان.


تضمين التغريدة

هل هذا المنشئ لما هو اليوم AllocErr :: Exhausted؟ إذا كان الأمر كذلك ، ألا يجب أن يحتوي على معلمة Layout؟

اها نقطة جيدة! أردت نوعًا ما أن أترك الاحتمالية على الرغم من جعل Error نوعًا بحجم صفري إذا كنا في حاجة إليه حقًا ، لكن يمكننا بالطبع الحفاظ على أساليب أخذ التخطيط غير مستقرة واستقرارها إذا لزم الأمر. أو هل تعتقد أنه يجب تثبيت Error الذي يحافظ على التخطيط في التمريرة الأولى؟


تضمين التغريدة

لم أر مثل هذا السؤال / القلق بشكل شخصي حتى الآن (أعتقد أنني فاتني ذلك!) ، لكنني لن أرى الأمر شخصيًا على أنه يستحق ذلك. صفتان هي ضعف الصيغة المعيارية بشكل عام ، حيث يتعين على الجميع الآن كتابة Alloc + Dealloc في المجموعات على سبيل المثال. أتوقع أن مثل هذا الاستخدام المتخصص لن يرغب في إبلاغ الواجهة بأن ينتهي الأمر باستخدام جميع المستخدمين الآخرين شخصيًا.

تضمين التغريدة

لم أر مثل هذا السؤال / القلق بشكل شخصي حتى الآن (أعتقد أنني فاتني ذلك!) ، لكنني لن أرى الأمر شخصيًا على أنه يستحق ذلك.

بشكل عام ، أوافق على أنه لا يستحق ذلك مع استثناء واحد صارخ: Box . Box<T, A: Alloc> ، نظرًا للتعريف الحالي لـ Alloc ، على الأقل كلمتين كبيرتين (المؤشر الموجود بالفعل ومرجع إلى Alloc على الأقل ) باستثناء حالة المفردات العالمية (التي يمكن تنفيذها على أنها ZSTs). إن انفجار 2x (أو أكثر) في المساحة المطلوبة لتخزين مثل هذا النوع الشائع والأساسي يثير قلقي.

تضمين التغريدة

كما هو الحال الآن على الجميع كتابة Alloc + Dealloc في المجموعات على سبيل المثال

يمكننا إضافة شيء مثل هذا:

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

انفجار 2x (أو أكثر) في المساحة المطلوبة لتخزين مثل هذا النوع الشائع والأساسي

فقط عند استخدام مخصص مخصص غير عمومي. std::heap::Heap (الافتراضي) بحجم صفري.

أو هل تعتقد أنه يجب تثبيت خطأ الحفاظ على التخطيط في التمريرة الأولى؟

alexcrichton أنا لا أفهم حقًا سبب وجود هذا التمرير الأول المقترح على الإطلاق. بالكاد يوجد أكثر مما يمكن فعله بالفعل عن طريق إساءة استخدام Vec ، ولا يكفي على سبيل المثال استخدام https://crates.io/crates/jemallocator.

ما الذي لا يزال بحاجة إلى حل لتحقيق الاستقرار في كل شيء؟

فقط عند استخدام مخصص مخصص غير عمومي. std :: heap :: Heap (الافتراضي) هو حجم صفري.

يبدو أن هذا هو حالة الاستخدام الأساسية لوجود مخصصات حدودية ، أليس كذلك؟ تخيل التعريف البسيط التالي للشجرة:

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

الشجرة التي تم إنشاؤها من تلك التي تحتوي على كلمة واحدة Alloc سيكون حجمها 1.7x تقريبًا لهيكل البيانات بالكامل مقارنة بـ ZST Alloc . هذا يبدو سيئًا جدًا بالنسبة لي ، وهذه الأنواع من التطبيقات هي نوع من الهدف الكامل لوجود Alloc لتكون سمة.

تضمين التغريدة

يمكننا إضافة شيء مثل هذا:

سنحصل أيضًا على أسماء مستعارة فعلية للسمات :) https://github.com/rust-lang/rust/issues/41517

glaebhoerl نعم ، ولكن لا يزال الاستقرار يبدو Allocator أعتقد أنه يمكننا التبديل إلى الأسماء المستعارة للسمات بشكل عكسي عند وصولهم ؛)

تضمين التغريدة

إن انفجار 2x (أو أكثر) في المساحة المطلوبة لتخزين مثل هذا النوع الشائع والأساسي يثير قلقي.

أتخيل أن جميع التطبيقات اليوم هي مجرد نوع بحجم صفري أو مؤشر كبير ، أليس كذلك؟ أليس التحسين المحتمل هو أن بعض أنواع أحجام المؤشرات يمكن أن تكون صفرية الحجم؟ (أو شيء من هذا القبيل؟)


تضمين التغريدة

يمكننا إضافة شيء مثل هذا:

في الواقع! ثم أخذنا سمة واحدة إلى ثلاثة . في الماضي لم نتمتع أبدًا بتجربة رائعة مع مثل هذه السمات. على سبيل المثال ، لا يتم تحويل Box<Both> إلى Box<OnlyOneTrait> . أنا متأكد من أننا يمكن أن ننتظر ميزات اللغة لتسهيل كل هذا ، لكن يبدو أن هذه الطرق بعيدة في أحسن الأحوال.


تضمين التغريدة

ما الذي لا يزال بحاجة إلى حل لتحقيق الاستقرار في كل شيء؟

لا أدري، لا أعرف. أردت أن أبدأ بأصغر شيء على الإطلاق حتى يكون هناك نقاش أقل.

أتخيل أن جميع التطبيقات اليوم هي مجرد نوع بحجم صفري أو مؤشر كبير ، أليس كذلك؟ أليس التحسين المحتمل هو أن بعض أنواع أحجام المؤشرات يمكن أن تكون صفرية الحجم؟ (أو شيء من هذا القبيل؟)

نعم ، الفكرة هي أنه ، بالنظر إلى كائن مخصص من نوع المخصص الخاص بك ، يمكنك معرفة المثال الذي جاء منه (على سبيل المثال ، استخدام البيانات الوصفية المضمنة). وبالتالي ، فإن المعلومات الوحيدة التي تحتاج إلى إلغاء تخصيصها هي معلومات النوع ، وليس معلومات وقت التشغيل.

للرجوع إلى المحاذاة عند إلغاء التخصيص ، أرى طريقتين للمضي قدمًا:

  • الاستقرار كما هو مقترح (مع المحاذاة عند إلغاء التخصيص). سيكون التخلي عن ملكية الذاكرة المخصصة يدويًا أمرًا مستحيلًا ما لم يتم تضمين Layout . على وجه الخصوص ، من المستحيل إنشاء حاوية Vec أو Box أو String أو حاوية أخرى std بمحاذاة أكثر صرامة من المطلوب (على سبيل المثال لأنك لا لا نريد أن يتداخل العنصر المعبأ مع سطر ذاكرة التخزين المؤقت) ، دون تفكيكه وإلغاء تخصيصه يدويًا لاحقًا (وهو ليس خيارًا دائمًا). مثال آخر على شيء قد يكون مستحيلًا ، هو ملء Vec باستخدام عمليات simd ، ثم التخلي عنها.

  • لا تطلب المحاذاة عند إلغاء التخصيص ، وقم بإزالة HeapAlloc -based alloc_system . قم دائمًا بتخزين المحاذاة. alexcrichton ، بينما التزمت بهذا الرمز ، هل تتذكر سبب وضعه هناك في المقام الأول؟ هل لدينا أي دليل على أنه يوفر قدرًا كبيرًا من الذاكرة لتطبيقات العالم الحقيقي؟ (باستخدام علامات الفرشاة الدقيقة ، من الممكن جعل النتائج تظهر في كلتا الحالتين اعتمادًا على حجم التخصيص - إلا إذا كان HeapAlloc يقوم بتقريب الأحجام على أي حال.)

على أي حال ، هذه مقايضة صعبة للغاية ؛ سيعتمد تأثير الذاكرة والأداء بشكل كبير على نوع التطبيق ، وأي تطبيق يجب تحسينه خاص بالتطبيق أيضًا.

أعتقد أننا قد نكون في الواقع مجرد بخير (TM). نقلاً عن مستندات Alloc :

تتطلب بعض الطرق أن يتناسب التخطيط مع كتلة الذاكرة.
ما يعنيه التخطيط "لاحتواء" كتلة ذاكرة يعني (أو
بشكل مكافئ ، بالنسبة لكتلة الذاكرة "لتلائم" التخطيط) هو أن
يجب أن يحمل الشرطان التاليان:

  1. يجب محاذاة عنوان بداية الكتلة مع layout.align() .

  2. يجب أن يقع حجم الكتلة في النطاق [use_min, use_max] ، حيث:

    • use_min هو self.usable_size(layout).0 و

    • use_max هو القدرة التي كانت (أو كان يمكن أن يكون)
      يتم إرجاعها عندما (إذا) تم تخصيص الكتلة عبر مكالمة إلى
      alloc_excess أو realloc_excess .

لاحظ أن:

  • حجم التخطيط المستخدم مؤخرًا لتخصيص الكتلة
    مضمون أن يكون في النطاق [use_min, use_max] و

  • الحد الأدنى على use_max يمكن تقريبه بأمان عن طريق استدعاء
    usable_size .

  • إذا كان التنسيق k يناسب كتلة ذاكرة (يُشار إليها بـ ptr )
    يتم تخصيصها حاليًا عن طريق مخصص a ، إذن فهو قانوني
    استخدم هذا التنسيق لإلغاء تخصيصه ، على سبيل المثال a.dealloc(ptr, k); .

لاحظ أن الرصاصة الأخيرة. إذا قمت بالتخصيص باستخدام تنسيق بمحاذاة a ، فيجب أن يكون قانونيًا بالنسبة لي أن ألغ التخصيص مع المحاذاة b < a لأن الكائن الذي يتماشى مع a يتماشى أيضًا مع b ، وبالتالي فإن التخطيط باستخدام المحاذاة b يناسب كائنًا مخصصًا مع تخطيط مع محاذاة a (وبنفس الحجم).

ما يعنيه هذا هو أنه يجب أن تكون قادرًا على التخصيص باستخدام محاذاة أكبر من الحد الأدنى للمحاذاة المطلوبة لنوع معين ثم السماح لبعض التعليمات البرمجية الأخرى بإلغاء تخصيصها مع الحد الأدنى من المحاذاة ، ويجب أن تعمل.

أليس التحسين المحتمل هو أن بعض أنواع أحجام المؤشرات يمكن أن تكون صفرية الحجم؟ (أو شيء من هذا القبيل؟)

كان هناك RFC لهذا مؤخرًا ويبدو من غير المحتمل جدًا أن يتم ذلك بسبب مخاوف التوافق: https://github.com/rust-lang/rfcs/pull/2040

على سبيل المثال ، لا يتم تحويل Box<Both> إلى Box<OnlyOneTrait> . أنا متأكد من أننا يمكن أن ننتظر ميزات اللغة لتسهيل كل هذا ، لكن يبدو أن هذه الطرق بعيدة في أحسن الأحوال.

من ناحية أخرى ، يبدو أن بث كائن السمة مرغوبًا فيه دون جدال ، وهو في الغالب مسألة جهد / عرض نطاق / قوة إرادة لتنفيذها. كان هناك موضوع مؤخرًا: https://internals.rust-lang.org/t/trait-upcasting/5970

ruuda كنت من كتب أن التنفيذ الأصلي هو alloc_system . قام alexcrichton فقط بتحريكه خلال إعادة بناء معامل التخصيص الكبير <time period> .

يتطلب التطبيق الحالي إلغاء التخصيص بنفس المحاذاة المحددة التي خصصت لها كتلة ذاكرة معينة. بغض النظر عما قد تدعيه الوثائق ، فهذه هي الحقيقة الحالية التي يجب على الجميع الالتزام بها حتى يتم تغيير alloc_system على Windows.

تستخدم عمليات التخصيص على Windows دائمًا مضاعفات MEMORY_ALLOCATION_ALIGNMENT (على الرغم من أنها تتذكر الحجم الذي خصصتها له للبايت). MEMORY_ALLOCATION_ALIGNMENT هو 8 على 32 بت و 16 على 64 بت. بالنسبة للأنواع المتراكبة ، نظرًا لأن المحاذاة أكبر من MEMORY_ALLOCATION_ALIGNMENT ، فإن النفقات العامة الناتجة عن alloc_system هي باستمرار مقدار المحاذاة المحدد ، لذا فإن التخصيص المحاذي لـ 64 بايت سيكون به 64 بايت من النفقات العامة.

إذا قررنا تمديد هذه الخدعة المتراكبة لتشمل جميع التخصيصات (والتي من شأنها أن تتخلص من مطلب إلغاء التخصيص بنفس المحاذاة التي حددتها عند التخصيص) ، فإن المزيد من التخصيصات سيكون لها نفقات زائدة. التخصيصات التي تتطابق محاذاة مع MEMORY_ALLOCATION_ALIGNMENT ستواجه نفقات ثابتة ثابتة قدرها MEMORY_ALLOCATION_ALIGNMENT بايت. التخصيصات التي تكون محاذاةها أقل من MEMORY_ALLOCATION_ALIGNMENT ستعاني من نفقات زائدة قدرها MEMORY_ALLOCATION_ALIGNMENT بايت نصف الوقت تقريبًا. إذا تم تقريب حجم التخصيص لأعلى إلى MEMORY_ALLOCATION_ALIGNMENT أكبر من أو يساوي حجم التخصيص بالإضافة إلى حجم المؤشر ، فلن يكون هناك مقدار حمل ، وإلا فسيكون هناك. بالنظر إلى أن 99.99٪ من التخصيصات لن يتم تجاوزها ، فهل تريد حقًا تحمل هذا النوع من النفقات العامة على كل هذه التخصيصات؟

روضة

أنا شخصياً أشعر أن تنفيذ alloc_system اليوم على Windows هو فائدة أكبر من القدرة على التخلي عن ملكية تخصيص لحاوية أخرى مثل Vec . AFAIK على الرغم من عدم وجود بيانات لقياس تأثير الحشو دائمًا مع المحاذاة ولا يتطلب المحاذاة عند إلغاء التخصيص.

تضمين التغريدة

أعتقد أن هذا التعليق خاطئ ، كما هو مشار إليه alloc_system على Windows يعتمد على نفس المحاذاة التي يتم تمريرها إلى إلغاء التخصيص كما تم تمريره عند التخصيص.

بالنظر إلى أن 99.99٪ من التخصيصات لن يتم تجاوزها ، فهل تريد حقًا تحمل هذا النوع من النفقات العامة على كل هذه التخصيصات؟

يعتمد ذلك على التطبيق ما إذا كان الحمل كبيرًا ، وما إذا كان سيتم تحسين الذاكرة أو الأداء. شكوكي هو أنه بالنسبة لمعظم التطبيقات ، إما أن يكون جيدًا ، لكن أقلية صغيرة تهتم بعمق بالذاكرة ، ولا يمكنهم فعلاً تحمل تلك البايتات الإضافية. وأقلية صغيرة أخرى تحتاج إلى السيطرة على الاصطفاف ، وهم بحاجة إليه حقًا .

تضمين التغريدة

أعتقد أن هذا التعليق خاطئ ، كما هو مشار إليه alloc_system على Windows يعتمد على نفس المحاذاة التي يتم تمريرها إلى إلغاء التخصيص كما تم تمريره عند التخصيص.

ألا يعني ذلك أن alloc_system على نظام التشغيل Windows لا يطبق فعليًا سمة Alloc (وبالتالي ربما يتعين علينا تغيير متطلبات السمة Alloc


يارب

إذا كنت أقرأ تعليقك بشكل صحيح ، ألا توجد هذه المحاذاة العلوية لجميع التخصيصات بغض النظر عما إذا كنا بحاجة إلى أن نكون قادرين على إلغاء التخصيص بمحاذاة مختلفة؟ بمعنى ، إذا قمت بتخصيص 64 بايت بمحاذاة 64 بايت وقمت أيضًا بإلغاء التخصيص بمحاذاة 64 بايت ، فإن الحمل الذي وصفته لا يزال موجودًا. وبالتالي ، فهي ليست ميزة القدرة على إلغاء التخصيص بمحاذاة مختلفة بقدر ما هي ميزة لطلب محاذاة أكبر من المعتاد.

joshlf ترجع النفقات alloc_system حاليًا إلى طلب محاذاة أكبر من المعتاد. إذا كانت المحاذاة أقل من أو تساوي MEMORY_ALLOCATION_ALIGNMENT ، فلا توجد نفقات إضافية ناتجة عن alloc_system .

ومع ذلك ، إذا قمنا بتغيير التنفيذ للسماح بإلغاء التخصيص بمحاذاة مختلفة ، فسيتم تطبيق النفقات العامة على جميع التخصيصات

آه لقد فهمت؛ من المنطقي.

ما معنى تطبيق Alloc لكل من Heap و Heap؟ في أي الحالات قد يستخدم المستخدم أحد هذه الضمانات مقابل الأخرى؟

هل هذه أول واجهة برمجة تطبيقات قياسية للمكتبة حيث يعني *mut u8 "مؤشر لأي شيء"؟ يوجد String :: from_raw_parts لكن هذا يعني حقًا المؤشر إلى بايت. أنا لست من محبي *mut u8 بمعنى "مؤشر لأي شيء" - حتى C تعمل بشكل أفضل. ما هي بعض الخيارات الأخرى؟ ربما يكون المؤشر إلى نوع معتم أكثر فائدة.

rfcbot قلق * موت u8

dtolnay Alloc for Heap نوع من "قياسي" و Alloc for &Heap مثل Write for &T حيث تتطلب السمة &mut self لكن التنفيذ لا يتطلب ذلك. والجدير بالذكر أن هذا يعني أن الأنواع مثل Heap و System هي خيوط آمنة ولا تحتاج إلى مزامنتها عند التخصيص.

والأهم من ذلك ، أن استخدام #[global_allocator] يتطلب أن يكون الثابت المرتبط به ، والذي من النوع T ، لديك Alloc for &T . (الملقب بأن جميع المخصصات العالمية يجب أن تكون خيطية)

بالنسبة إلى *mut u8 ، أعتقد أن *mut () قد يكون أمرًا مثيرًا للاهتمام ، لكنني شخصيًا لا أشعر بأنني مضطر جدًا إلى "الحصول على هذا بشكل صحيح" هنا في حد ذاته.

الميزة الرئيسية لـ *mut u8 هي أنه من الملائم جدًا استخدام .offset مع إزاحة البايت.

بالنسبة إلى *mut u8 ، أعتقد أن *mut () قد يكون أمرًا مثيرًا للاهتمام ، لكنني لا أشعر شخصيًا بأنني مضطر جدًا إلى "الحصول على هذا بشكل صحيح" هنا في حد ذاته.

إذا استخدمنا *mut u8 في واجهة مستقرة ، ألا نحبس أنفسنا؟ بعبارة أخرى ، بمجرد تحقيق الاستقرار في هذا ، لن تتاح لنا فرصة "تصحيح هذا الأمر" في المستقبل.

أيضًا ، يبدو *mut () خطيرًا بعض الشيء بالنسبة لي في حالة إجراء تحسين مثل RFC 2040 في المستقبل.

الميزة الرئيسية لـ *mut u8 هي أنه مناسب جدًا لاستخدام. offset مع إزاحة البايت.

هذا صحيح ، ولكن يمكنك بسهولة القيام بـ let ptr = (foo as *mut u8) ثم الذهاب في طريقك المرح. لا يبدو هذا دافعًا كافيًا للالتزام بـ *mut u8 في واجهة برمجة التطبيقات إذا كانت هناك بدائل مقنعة (ولكي نكون منصفين ، لست متأكدًا من وجودها).

أيضًا ، يبدو * mut () خطيرًا بعض الشيء بالنسبة لي في حالة إجراء تحسين مثل RFC 2040 في المستقبل.

من المحتمل ألا يحدث هذا التحسين أبدًا - فقد يؤدي إلى كسر الكثير من التعليمات البرمجية الموجودة. حتى لو حدث ذلك ، فسيتم تطبيقه على &() و &mut () ، وليس *mut () .

إذا كان RFC 1861 قريبًا من التنفيذ / الاستقرار ، فإنني أقترح استخدامه:

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);
    // ...
}

ربما يكون بعيدًا جدًا ، أليس كذلك؟

joshlf ظننت أنني رأيت DynSized .

هل سيعمل هذا مع هيكل مثل الأشياء؟ لنفترض أن لدي Node<T> يبدو هكذا:

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

ونوع القيمة:

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

الآن أريد تخصيص Node<V> بسلسلة بحجم 7 في تخصيص واحد . من الناحية المثالية ، أرغب في تخصيص حجم 16 محاذاة 4 وتناسب كل شيء بداخله: 4 لـ u32 ، 5 لـ V ، و 7 لسلسلة بايت. يعمل هذا لأن آخر عضو في V لديه محاذاة 1 ولبايت السلسلة أيضًا محاذاة 1.

لاحظ أن هذا غير مسموح به في C / C ++ إذا كانت الأنواع مكونة كما هو مذكور أعلاه ، لأن الكتابة إلى وحدة التخزين المحزومة هي سلوك غير محدد. أعتقد أن هذه فجوة في معيار C / C ++ لا يمكن إصلاحها للأسف. يمكنني التوسع في سبب كسر هذا ولكن دعنا نركز على Rust بدلاً من ذلك. هل هذا العمل؟ :-)

فيما يتعلق بحجم ومحاذاة بنية Node<V> نفسها ، فأنت إلى حد كبير في نزوة مترجم Rust. إنه UB (سلوك غير محدد) للتخصيص بأي حجم أو محاذاة أصغر مما يتطلبه Rust نظرًا لأن Rust قد يقوم بإجراء تحسينات بناءً على افتراض أن أي كائن Node<V> - على المكدس ، على الكومة ، خلف مرجع ، إلخ. - يتطابق حجم ومحاذاة مع ما هو متوقع في وقت الترجمة.

من الناحية العملية ، يبدو أن الإجابة هي للأسف لا: لقد قمت بتشغيل هذا البرنامج ووجدت أنه ، على الأقل في Rust Playground ، يتم إعطاء Node<V> حجم 12 ومحاذاة 4 ، مما يعني أن أي كائنات بعد يجب تعويض Node<V> بـ 12 بايت على الأقل. يبدو أن إزاحة الحقل data.b داخل Node<V> هو 8 بايت ، مما يعني أن البايتات 9-11 هي ترك مساحة زائدة. لسوء الحظ ، على الرغم من أن وحدات البايت المتروكة هذه "غير مستخدمة" بمعنى ما ، فإن المترجم لا يزال يعاملها كجزء من Node<V> ، ويحتفظ بالحق في فعل أي شيء يحبه معهم (والأهم من ذلك ، بما في ذلك الكتابة لهم عند تعيين Node<V> ، مما يعني أنه إذا حاولت التخلص من البيانات الإضافية هناك ، فقد يتم الكتابة فوقها).

(ملاحظة ، راجع للشغل: لا يمكنك التعامل مع النوع على أنه معبأ لا يعتقد مترجم Rust أنه معبأ. ومع ذلك ، يمكنك _يمكنك إخبار المترجم Rust أن شيئًا ما معبأ ، مما يؤدي إلى تغيير تخطيط النوع (إزالة الحشو) ، باستخدام repr(packed) )

ومع ذلك ، فيما يتعلق بوضع كائن واحدًا تلو الآخر دون أن يكون كلاهما جزءًا من نفس نوع Rust ، فأنا متأكد بنسبة 100٪ تقريبًا أن هذا صحيح - بعد كل شيء ، هذا ما يفعله Vec . يمكنك استخدام طرق النوع Layout لحساب مقدار المساحة المطلوبة للتخصيص الإجمالي ديناميكيًا:

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();

هل شيء مثل هذا العمل؟

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

... ثم خصص بحجم أكبر ، واستخدم slice::from_raw_parts_mut(node.bytes.as_mut_ptr(), size) ؟

شكرا joshlf للإجابة التفصيلية! TLDR لحالة الاستخدام الخاصة بي هو أنه يمكنني الحصول على Node<V> بحجم 16 ولكن فقط إذا كان V هو repr(packed) . وإلا فإن أفضل ما يمكنني فعله هو الحجم 19 (12 + 7).

SimonSapin غير متأكد ؛ سوف احاول.

لم أتعامل مع هذا الخيط حقًا ، لكنني عاجز عن تثبيت أي شيء بعد. لم نحرز تقدمًا في تنفيذ المشكلات الصعبة حتى الآن:

  1. مجموعات التخصيص متعددة الأشكال

    • ولا حتى مربع غير منتفخ!

  2. مجموعات معصومة

أعتقد أن التصميم من الصفات الأساسية تؤثر على حلول لتلك: لقد كان وقتا قليلا للصدأ في الأشهر القليلة الماضية، ولكن قال هذا في بعض الأحيان. أشك في أنه سيكون لدي الوقت الكافي لعرض حالتي هنا أيضًا ، لذلك لا يسعني إلا أن آمل أن نكتب أولاً على الأقل حلاً كاملاً لكل هؤلاء: شخص ما يثبت لي خطأ أنه من المستحيل أن أكون صارمًا (قوة الاستخدام الصحيح) ، مرن ، ومريح مع السمات الحالية. أو حتى أنهي فقط التحقق من المربعات في الأعلى.

رد: تعليق @ Ericson2314

أعتقد أن السؤال ذي الصلة المتعلق بالتعارض بين هذا المنظور ورغبةalexcrichton في تثبيت شيء ما هو: ما مدى الفائدة التي نحصل عليها من تثبيت الحد الأدنى من الواجهة؟ على وجه الخصوص ، سيتصل عدد قليل جدًا من المستهلكين بطرق Alloc مباشرة (حتى معظم المجموعات ستستخدم على الأرجح Box أو حاوية أخرى مماثلة) ، لذا يصبح السؤال الحقيقي: ما الذي يجعل شراء الاستقرار للمستخدمين الذين سيفعلون ذلك؟ ألا يتم استدعاء طرق Alloc مباشرة؟ بصراحة ، حالة الاستخدام الجادة الوحيدة التي يمكنني التفكير فيها هي أنها تمهد الطريق للمجموعات متعددة الأشكال المخصصة (والتي من المحتمل أن تستخدمها مجموعة أوسع من المستخدمين) ، ولكن يبدو أنه تم حظرها في # 27336 ، وهو بعيد كل البعد عن يجري حلها. ربما تكون هناك حالات استخدام أخرى كبيرة أفتقدها ، ولكن بناءً على هذا التحليل السريع ، أميل إلى الابتعاد عن التثبيت حيث إن الفوائد الهامشية فقط على حساب حبسنا في تصميم قد نجد لاحقًا أنه دون المستوى الأمثل .

joshlf يسمح للأشخاص بتحديد واستخدام

هممم نقطة جيدة. هل سيكون من الممكن تحقيق الاستقرار عند تحديد المخصص العالمي دون استقرار Alloc ؟ أي أن الكود الذي ينفذ Alloc يجب أن يكون غير مستقر ، ولكن من المحتمل أن يتم تغليفه في الصندوق الخاص به ، وستكون آلية تحديد هذا المخصّص على أنه المخصّص العام ثابتة. أم أنني أسيء فهم كيفية تفاعل المترجم المستقر / غير المستقر والمستقر / المترجم الليلي؟

آه @ joshlf تذكر أن # 27336 هو إلهاء ، حسب https://github.com/rust-lang/rust/issues/42774#issuecomment -317279035. أنا متأكد من أننا سنواجه مشاكل أخرى - مشاكل السمات كما هي ، ولهذا السبب أريد العمل لبدء العمل على ذلك الآن. من الأسهل كثيرًا مناقشة هذه المشكلات بمجرد وصولها ليراها الجميع أكثر من مناقشة العقود الآجلة المتوقعة بعد # 27336.

joshlf لكن لا يمكنك تجميع الصندوق الذي يعرّف المخصّص العام باستخدام مترجم ثابت.

sfackler آه نعم ، هناك سوء تفاهم كنت أخاف منه: P.

أجد أن الاسم Excess(ptr, usize) محيرًا بعض الشيء لأن usize ليس حجم التخصيص المطلوب excess (كما في الحجم الإضافي المخصص) ، لكن total حجم التخصيص.

IMO Total أو Real أو Usable أو أي اسم يشير إلى أن الحجم هو الحجم الإجمالي أو الحجم الحقيقي للتخصيص أفضل من "الفائض" ، وهو ما أجده مضلل. الأمر نفسه ينطبق على طرق _excess .

أتفق مع gnzlbg أعلاه ، وأعتقد أن بنية tuple العادية (ptr ، usize) ستكون جيدة.

لاحظ أنه لا يُقترح تثبيت Excess في التمريرة الأولى

نشر هذا الموضوع للمناقشة على reddit ، الذي لديه بعض الأشخاص الذين لديهم مخاوف: https://www.reddit.com/r/rust/comments/78dabn/custom_allocators_are_on_the_verge_of_being/

بعد مزيد من المناقشة مع @ rust-lang / libs اليوم ، أود إجراء بعض التعديلات على اقتراح التثبيت الذي يمكن تلخيصه بما يلي:

  • أضف alloc_zeroed إلى مجموعة الطرق المستقرة ، وإلا سيكون لديك نفس التوقيع مثل alloc .
  • غيّر *mut u8 إلى *mut void في واجهة برمجة التطبيقات باستخدام دعم extern { type void; } ، وحل مخاوفdtolnay وتوفير طريقة للمضي قدمًا لتوحيد c_void عبر النظام البيئي.
  • قم بتغيير نوع الإرجاع alloc إلى *mut void ، مع إزالة Result و Error

ربما تكون النقطة الأخيرة هي الأكثر إثارة للجدل ، لذا أود أن أوضح ذلك أيضًا. جاء هذا من المناقشة مع فريق libs اليوم وتمحور تحديدًا حول كيفية (أ) الواجهة المستندة إلى Result بها ABI أقل كفاءة من واجهة إرجاع المؤشر و (ب) عدم وجود مخصصات "إنتاج" تقريبًا اليوم توفير القدرة على تعلم أي شيء أكثر من "هذا فقط OOM'd". بالنسبة للأداء ، يمكننا في الغالب أن نغطيها بالتضمين وهكذا ، لكن يبقى أن Error هو حمولة إضافية يصعب إزالتها في الطبقات الدنيا.

إن التفكير في إرجاع حمولات الأخطاء هو أن المخصّصين يمكنهم تقديم استبطان خاص بالتنفيذ لمعرفة سبب فشل التخصيص وإلا فإن جميع المستهلكين تقريبًا يحتاجون فقط إلى معرفة ما إذا كان التخصيص قد نجح أم فشل. بالإضافة إلى ذلك ، يُقصد بهذا أن يكون واجهة برمجة تطبيقات ذات مستوى منخفض جدًا والتي لا تسمى في الواقع كثيرًا (بدلاً من ذلك ، يجب استدعاء واجهات برمجة التطبيقات المكتوبة التي تلتف الأشياء جيدًا بدلاً من ذلك) بهذا المعنى ، ليس من الأهمية بمكان أن يكون لدينا واجهة برمجة التطبيقات الأكثر قابلية للاستخدام والمريحة لهذا الموقع ، بل إنه أكثر أهمية حول تمكين حالات الاستخدام دون التضحية بالأداء.

الميزة الرئيسية لـ *mut u8 هي أنه من الملائم جدًا استخدام .offset مع إزاحة البايت.

في اجتماع libs ، اقترحنا أيضًا impl *mut void { fn offset } والذي لا يتعارض مع offset المحدد لـ T: Sized . يمكن أيضًا أن يكون byte_offset .

+1 لاستخدام *mut void و byte_offset . هل ستكون هناك مشكلة في تثبيت ميزة الأنواع الخارجية ، أم يمكننا تجنب هذه المشكلة لأن التعريف فقط غير مستقر (ويمكن لـ liballoc القيام بأشياء غير مستقرة داخليًا) وليس الاستخدام (على سبيل المثال ، let a: *mut void = ... isn غير مستقر)؟

نعم ، لسنا بحاجة إلى منع التثبيت من النوع الخارجي. حتى إذا تم حذف دعم النوع الخارجي ، فإن void الذي نحدده لهذا يمكن أن يكون دائمًا أسوأ حالة من النوع السحري.

هل كان هناك أي نقاش إضافي في اجتماع libs حول ما إذا كان يجب أن يكون Alloc و Dealloc سمات منفصلة أم لا؟

لم نتطرق إلى ذلك على وجه التحديد ، لكننا شعرنا عمومًا أنه لا ينبغي لنا أن نتحول عن الفن السابق ما لم يكن لدينا سبب مقنع بشكل خاص للقيام بذلك. على وجه الخصوص ، لا يحتوي مفهوم Allocator في C ++ على تقسيم مماثل.

لست متأكدًا من أن هذه مقارنة مناسبة في هذه الحالة. في C ++ ، يتم تحرير كل شيء بشكل صريح ، لذلك لا يوجد ما يعادل Box يحتاج إلى تخزين نسخة من (أو مرجع) المخصص الخاص به. هذا ما يسبب لنا حجم كبير.

std::unique_ptr عام على "Deleter" .

joshlf unique_ptr ما يعادل Box ، vector ما يعادل Vec ، unordered_map ما يعادل HashMap ، إلخ.

cramertj آه ، مثير للاهتمام ، كنت أنظر فقط إلى أنواع المجموعات. يبدو أن هذا قد يكون شيئًا يجب القيام به بعد ذلك. يمكننا دائمًا إضافته لاحقًا عبر الضمانات الشاملة ولكن ربما يكون من الأنظف تجنب ذلك.

قد يكون النهج الضمني الشامل أكثر نظافة ، في الواقع:

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)
    }
}

سمة واحدة أقل للقلق بشأن غالبية حالات الاستخدام.

  • قم بتغيير نوع إرجاع التخصيص إلى * باطل متعدد ، وإزالة النتيجة والخطأ

ربما تكون النقطة الأخيرة هي الأكثر إثارة للجدل ، لذا أود أن أوضح ذلك أيضًا. جاء هذا من المناقشة مع فريق libs اليوم وتمحور تحديدًا حول (أ) أن الواجهة القائمة على النتائج لها مؤشر ABI أقل كفاءة من واجهة إرجاع المؤشر و (ب) لا يوفر أي مخصصات "إنتاج" تقريبًا اليوم القدرة على التعلم أي شيء أكثر من "هذا فقط OOM'd". بالنسبة للأداء ، يمكننا في الغالب الورق فوقها بالتضمين وما إلى ذلك ، لكن يبقى أن الخطأ عبارة عن حمولة إضافية يصعب إزالتها في الطبقات الدنيا.

أنا قلق من أن هذا سيجعل من السهل جدًا استخدام المؤشر المرتجع دون التحقق من القيمة الفارغة. يبدو أنه يمكن أيضًا إزالة النفقات العامة دون إضافة هذه المخاطرة عن طريق إرجاع Result<NonZeroPtr<void>, AllocErr> وجعل AllocErr بحجم صفر؟

( NonZeroPtr هو ptr::Shared و ptr::Unique كما هو مقترح في https://github.com/rust-lang/rust/issues/27730#issuecomment-316236397.)

SimonSapin شيء مثل Result<NonZeroPtr<void>, AllocErr> يتطلب ثلاثة أنواع للاستقرار ، وكلها جديدة تمامًا وبعضها كان يعاني تاريخيًا من الاستقرار. شيء مثل void ليس مطلوبًا حتى وهو أمر جيد (في رأيي).

أوافق على أنه "من السهل استخدامه دون التحقق من القيمة الفارغة" ، ولكن هذه ، مرة أخرى ، واجهة برمجة تطبيقات ذات مستوى منخفض جدًا ولا يُقصد استخدامها بكثرة ، لذلك لا أعتقد أنه يجب علينا تحسين بيئة عمل المتصل.

يمكن للأشخاص أيضًا إنشاء تجريدات ذات مستوى أعلى مثل alloc_one أعلى المستوى المنخفض alloc الذي يمكن أن يكون له أنواع عوائد أكثر تعقيدًا مثل Result<NonZeroPtr<void>, AllocErr> .

أوافق على أن AllocErr لن يكون مفيدًا في الممارسة ، ولكن ماذا عن Option<NonZeroPtr<void>> ؟ واجهات برمجة التطبيقات التي يستحيل إساءة استخدامها عن طريق الخطأ ، بدون النفقات العامة ، هي أحد الأشياء التي تميز Rust عن C ، والعودة إلى المؤشرات الفارغة من النمط C تبدو وكأنها خطوة إلى الوراء بالنسبة لي. إن القول بأنها "واجهة برمجة تطبيقات ذات مستوى منخفض جدًا ولا يُقصد استخدامها بكثرة" يشبه القول بأنه لا ينبغي لنا أن نهتم بأمان الذاكرة في أبنية وحدة التحكم الدقيقة غير الشائعة لأنها منخفضة المستوى جدًا ولا تستخدم بكثرة.

يتضمن كل تفاعل مع المخصص رمزًا غير آمن بغض النظر عن نوع الإرجاع لهذه الوظيفة. من الممكن إساءة استخدام واجهات برمجة التطبيقات ذات المستوى المنخفض للتخصيص سواء كان نوع الإرجاع Option<NonZeroPtr<void>> أو *mut void .

Alloc::alloc وجه الخصوص هو واجهة برمجة التطبيقات ذات المستوى المنخفض وليس المقصود استخدامها بكثرة. طرق مثل Alloc::alloc_one<T> أو Alloc::alloc_array<T> هي البدائل التي سيتم استخدامها بكثرة ولها نوع إرجاع "أفضل".

A جليل AllocError لا يستحق ذلك، ولكن الصفر نوع الحجم التي تنفذ خطأ، ولها Display من allocation failure من الجميل أن يكون. إذا ذهبنا إلى المسار NonZeroPtr<void> ، أرى أن Result<NonZeroPtr<void>, AllocError> هو الأفضل من Option<NonZeroPtr<void>> .

لماذا الاندفاع لتحقيق الاستقرار :( !! Result<NonZeroPtr<void>, AllocErr> هو أفضل بلا منازع بالنسبة للعملاء لاستخدامه. إن القول بأن هذه "واجهة برمجة تطبيقات منخفضة المستوى جدًا" لا يلزم أن تكون لطيفة هو أمر غير طموح بشكل محبط. يجب أن يكون الرمز على جميع المستويات آمنة وقابلة للصيانة قدر الإمكان ؛ شفرة غامضة لا يتم تعديلها باستمرار (وبالتالي يتم ترحيلها في ذكريات الأشخاص قصيرة المدى) أكثر من ذلك!

علاوة على ذلك ، إذا أردنا الحصول على مجموعات متعددة الأشكال مكتوبة من قبل المستخدم ، والتي آمل بالتأكيد في الحصول عليها ، فهذا مقدار مفتوح من التعليمات البرمجية المعقدة إلى حد ما باستخدام المخصصات مباشرة.

إعادة التعامل ، من الناحية التشغيلية ، من شبه المؤكد أننا نريد الإشارة إلى / استنساخ alloactor مرة واحدة فقط لكل مجموعة قائمة على الأشجار. هذا يعني تمرير المخصص إلى كل مربع مخصص مخصص يتم تدميره. لكن ، إنها مشكلة مفتوحة حول أفضل طريقة للقيام بذلك في Rust بدون أنواع خطية. على عكس تعليقي السابق ، سأكون موافقًا على بعض التعليمات البرمجية غير الآمنة في تطبيقات المجموعات لهذا ، لأن حالة الاستخدام المثالية تغير تنفيذ Box ، وليس تنفيذ سمات المُخصص المُقسّم و deallocator. أي يمكننا تحقيق تقدم مستقر دون إعاقة الخطية.

sfackler أعتقد أننا بحاجة إلى بعض الأنواع المرتبطة التي تربط موزع التخصيص بالمخصص ؛ قد لا يكون من الممكن تعديلها.

@ Ericson2314 هناك "اندفاع" لتحقيق الاستقرار لأن الناس يريدون استخدام المخصصات لأشياء حقيقية في العالم الحقيقي. هذا ليس مشروع علمي.

ما من شأنه أن يستخدم هذا النوع المرتبط؟

لا يزال بإمكان sfackler أن يعلق كل ليلة / ونوع الأشخاص الذين يهتمون بهذا النوع من الميزات المتقدمة يجب أن يكونوا مرتاحين للقيام بذلك. [إذا كانت المشكلة هي الصدأ غير المستقر مقابل الصدأ غير المستقر ، فهذه مشكلة مختلفة تحتاج إلى إصلاح للسياسة.] الخبز في واجهات برمجة التطبيقات الرديئة ، على العكس من ذلك ، يعيقنا إلى الأبد ، ما لم نرغب في تقسيم النظام البيئي باستخدام 2.0 قياسي جديد.

ستربط الأنواع المرتبطة موزع التخصيص بالمخصص. يحتاج كل منهم إلى معرفة ذلك الآخر حتى يعمل هذا. [لا تزال هناك مشكلة في استخدام مخصص (de) خاطئ من النوع الصحيح ، لكني أقبل أنه لم يقترح أحد حلاً لذلك عن بعد.]

إذا كان بإمكان الناس التثبيت ليلاً ، فلماذا لدينا هياكل مستقرة على الإطلاق؟ إن مجموعة الأشخاص الذين يتفاعلون بشكل مباشر مع واجهات برمجة التطبيقات للمخصص أصغر بكثير من الأشخاص الذين يرغبون في الاستفادة من واجهات برمجة التطبيقات هذه عن طريق استبدال المُخصص العام على سبيل المثال.

هل يمكنك كتابة بعض التعليمات البرمجية التي توضح سبب حاجة موزع التخصيص إلى معرفة نوع المخصص المرتبط به؟ لماذا لا تحتاج واجهة برمجة تطبيقات مخصص C ++ إلى تعيين مماثل؟

إذا كان بإمكان الناس التثبيت ليلاً ، فلماذا لدينا هياكل مستقرة على الإطلاق؟

للإشارة إلى استقرار اللغة. الكود الذي تكتبه ضد هذه النسخة من الأشياء لن ينكسر أبدًا. على مترجم أحدث. تقوم بتثبيت كل ليلة عندما تحتاج إلى شيء سيء للغاية ، فلا يستحق الانتظار حتى التكرار النهائي للميزة التي تعتبر الجودة التي تستحق هذا الضمان.

إن مجموعة الأشخاص الذين يتفاعلون بشكل مباشر مع واجهات برمجة التطبيقات للمخصص أصغر بكثير من الأشخاص الذين يرغبون في الاستفادة من واجهات برمجة التطبيقات هذه عن طريق استبدال المُخصص العام على سبيل المثال.

آها! سيكون هذا لنقل jemalloc خارج الشجرة ، وما إلى ذلك؟ لم يقترح أحد تثبيت الاختراقات الفظيعة التي تسمح باختيار المخصص العالمي ، فقط الكومة الثابتة نفسها؟ أم أنني قرأت الاقتراح خطأ؟

يُقترح أن يتم تثبيت الاختراقات الفظيعة التي تسمح باختيار المخصص العالمي ، وهو نصف ما يسمح لنا بإخراج jemalloc من الشجرة. هذه القضية هي النصف الآخر.

تثبيت السمة #[global_allocator] : https://github.com/rust-lang/rust/issues/27389#issuecomment -336955367

ييكيس

@ Ericson2314 ما رأيك في طريقة غير مروعة لاختيار المخصص العالمي؟

(تم الرد في https://github.com/rust-lang/rust/issues/27389#issuecomment-342285805)

تم تعديل الاقتراح لاستخدام * mut باطل.

rfcbot تم حله * mut u8

مراجعة rfcbot

بعد بعض المناقشات حول IRC ، أوافق على هذا على أساس أننا _ لا_ نعتزم تثبيت Box عام على Alloc ، ولكن بدلاً من ذلك على بعض السمات Dealloc مع الضمانة المناسبة ، كما هو مقترح من هنا . يرجى إعلامي إذا كنت قد أسأت فهم النية.

cramertj فقط للتوضيح ، من الممكن إضافة ذلك الضمني الشامل بعد الحقيقة وعدم كسر تعريف Alloc الذي نحققه هنا؟

joshlf نعم ، سيبدو هكذا: https://github.com/rust-lang/rust/issues/32838#issuecomment -340959804

كيف سنحدد Dealloc مقابل Alloc ؟ أتخيل شيئًا كهذا؟

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

أعتقد أن هذا يضعنا في منطقة شائكة WRT https://github.com/rust-lang/rust/issues/29661.

نعم ، لا أعتقد أن هناك طريقة لجعل إضافة Dealloc متوافقة مع التعاريف الحالية لـ Alloc (التي لا تحتوي على هذا النوع المرتبط) بدون وجود افتراضي.

إذا أردت أن تكون قادرًا تلقائيًا على الاستيلاء على أداة إلغاء التخصيص المقابلة للمخصص ، فستحتاج إلى أكثر من مجرد نوع مرتبط ، ولكن وظيفة لإنتاج قيمة إلغاء التخصيص.

ولكن ، يمكن التعامل مع هذا في المستقبل مع إرفاق هذه الأشياء بصورة فرعية منفصلة بقيمة Alloc ما أعتقد.

sfackler لست متأكدًا من فهمي. هل يمكنك كتابة توقيع Box::new تحت تصميمك؟

هذا يتجاهل بناء جملة الموضع وكل ذلك ، ولكن إحدى الطرق التي يمكنك القيام بها هي

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)
    }
}

وتجدر الإشارة إلى أننا نحتاج بالفعل إلى أن نكون قادرين على إنتاج مثيل لـ deallocator ، وليس فقط معرفة نوعه. يمكنك أيضًا تحديد معلمات Box فوق Alloc وتخزين A::Dealloc بدلاً من ذلك ، مما قد يساعد في كتابة الاستدلال. يمكننا جعل هذا العمل بعد هذا الاستقرار عن طريق نقل Dealloc و deallocator إلى سمة منفصلة:

pub trait SplitAlloc: Alloc {
    type Dealloc;

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

ولكن كيف سيبدو شكل 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);
        }
    }
}

ولكن بافتراض أننا نحقق الاستقرار في Alloc أولاً ، فلن تنفذ جميع Alloc s Dealloc ، أليس كذلك؟ واعتقدت أن التخصص الضمني لا يزال بعيد المنال؟ بمعنى آخر ، من الناحية النظرية ، هل تريد أن تفعل شيئًا مثل ما يلي ، لكنني لا أعتقد أنه يعمل بعد؟

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

إذا كان هناك أي شيء ، فسنحصل على ملف

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

لكنني لا أعتقد أن هذا سيكون ضروريًا حقًا. حالات استخدام المخصصات المخصصة والمخصصات العالمية متميزة بما يكفي بحيث لا أفترض أنه سيكون هناك الكثير من التداخل بينهما.

أفترض أن هذا يمكن أن يعمل. يبدو أن الأمر أكثر نظافة بالنسبة لي ، مع ذلك ، أن يكون لدينا Dealloc مباشرة من الخفاش حتى يمكننا الحصول على واجهة أبسط. أتخيل أنه يمكن أن يكون لدينا واجهة بسيطة جدًا وغير مثيرة للجدل لا تتطلب أي تغيير في الكود الحالي الذي ينفذ بالفعل 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 }
    ...
}

على الرغم من أن الافتراضات نوع المرتبطة كانت مشكلة؟

يبدو أن A Dealloc الذي يعد مرجعًا متغيرًا للمخصص ليس مفيدًا تمامًا - يمكنك تخصيص شيء واحد فقط في كل مرة ، أليس كذلك؟

على الرغم من أن الافتراضات نوع المرتبطة كانت مشكلة؟

أعتقد أن الإعدادات الافتراضية للنوع المرتبط بعيدة بما يكفي بحيث لا يمكننا الاعتماد عليها.

ومع ذلك ، يمكننا الحصول على أبسط:

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;
    ...
}

وتطلب فقط من المنفذ كتابة القليل من النموذج المعياري.

يبدو أن A Dealloc الذي يعد مرجعًا متغيرًا للمخصص ليس مفيدًا تمامًا - يمكنك تخصيص شيء واحد فقط في كل مرة ، أليس كذلك؟

نعم ، نقطة جيدة. ربما نقطة خلافية على أي حال بالنظر إلى تعليقك الآخر.

هل يجب أن يأخذ deallocator self أو &self أو &mut self ؟

ربما يكون &mut self متوافقًا مع الطرق الأخرى.

هل هناك أي مخصصين يفضلون أخذ الذات بالقيمة حتى لا يضطروا إلى استنساخ الحالة مثلاً؟

تكمن مشكلة أخذ self أنها تمنع الحصول على Dealloc ثم الاستمرار في التخصيص.

أفكر في مخصص افتراضي "ونشوت" ، على الرغم من أنني لا أعرف مقدار الشيء الحقيقي.

قد يوجد مثل هذا المخصص ، ولكن أخذ self من حيث القيمة يتطلب أن يعمل _all_ المخصصين بهذه الطريقة ، وسوف يمنع أي مخصصين يسمحون بالتخصيص بعد استدعاء deallocator .

ما زلت أرغب في رؤية بعض من هذا مطبقًا ومستخدمًا في المجموعات قبل أن نفكر في تثبيته.

هل تعتقد أن https://github.com/rust-lang/rust/issues/27336 أو النقاط التي تمت مناقشتها في https://github.com/rust-lang/rust/issues/32838#issuecomment -339066870 ستسمح لنا المضي قدما في المجموعات؟

إنني قلق بشأن تأثير أسلوب الاسم المستعار على سهولة قراءة الوثائق. هناك طريقة (مطولة جدًا) للسماح بالتقدم وهي التفاف الأنواع:

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

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

أعلم أنه أمر مؤلم ، ولكن يبدو أن التغييرات التي نناقشها هنا كبيرة بما يكفي أنه إذا قررنا المضي قدمًا في سمات تقسيم التخصيص / إلغاء التخصيص ، فيجب أن نجربها أولاً ثم إعادة FCP.

ما هو الجدول الزمني لانتظار هذه الأشياء حتى يتم تنفيذها؟

لا تُرجع طريقة grow_in_place أي نوع من السعة الزائدة. يستدعي حاليًا usable_size مع تخطيط ، ويمتد التخصيص إلى _ على الأقل _ يتلاءم مع هذا التخطيط ، ولكن إذا تم توسيع التخصيص إلى ما بعد هذا التخطيط ، فلن يكون لدى المستخدمين أي طريقة لمعرفة ذلك.

أواجه صعوبة في فهم ميزة طرق alloc و realloc على alloc_excess و realloc_excess .

يحتاج المُخصص إلى إيجاد كتلة ذاكرة مناسبة لإجراء تخصيص: وهذا يتطلب معرفة حجم كتلة الذاكرة. ما إذا كان المُخصص يُرجع مؤشرًا بعد ذلك ، أو لا يُحدث "مؤشر وحجم كتلة الذاكرة" أي اختلافات قابلة للقياس في الأداء.

لذا فإن alloc و realloc فقط يزيدان من سطح واجهة برمجة التطبيقات ويبدو أنهما يشجعان على كتابة تعليمات برمجية أقل أداءً. لماذا لدينا في API على الإطلاق؟ ما هي مصلحتهم؟


EDIT: أو بعبارة أخرى: يجب أن تعيد جميع الوظائف التي يحتمل تخصيصها في واجهة برمجة التطبيقات Excess ، مما يلغي الحاجة إلى جميع طرق _excess .

الزائدة مثيرة للاهتمام فقط لحالات الاستخدام التي تتضمن مصفوفات يمكن أن تنمو. إنها ليست مفيدة أو ذات صلة بـ Box أو BTreeMap ، على سبيل المثال. قد يكون هناك بعض التكلفة في حساب ماهية الفائض ، وهناك بالتأكيد واجهة برمجة تطبيقات أكثر تعقيدًا ، لذلك لا يبدو لي أن الكود الذي لا يهتم بالسعة الزائدة يجب إجباره على دفع ثمنه.

قد يكون هناك بعض التكلفة في حساب ماهية الزيادة

يمكنك ان تعطي مثالا؟ لا أعرف ، ولا أستطيع تخيل ، مُخصص قادر على تخصيص الذاكرة ولكن لا يعرف مقدار الذاكرة التي يخصصها بالفعل (وهو ما هو Excess : المقدار الحقيقي للذاكرة المخصصة ؛ يجب علينا إعادة تسميته).

Alloc ator الوحيد المستخدم بشكل شائع حيث قد يكون هذا مثيرًا للجدل قليلاً هو POSIX malloc ، والذي على الرغم من أنه يحسب دائمًا Excess داخليًا ، فإنه لا يعرضه كجزء من C API. ومع ذلك ، فإن إعادة الحجم المطلوب لأن Excess جيد ، محمول ، بسيط ، لا يتحمل أي تكلفة على الإطلاق ، وهو ما يفترضه كل شخص يستخدم POSIX malloc على أي حال.

jemalloc وأساسًا أي Alloc توفر واجهة برمجة تطبيقات تُرجع Excess بدون تكبد أي تكاليف ، لذلك بالنسبة لهؤلاء المخصصين ، فإن إرجاع Excess هو صفر التكلفة كذلك.

قد يكون هناك بعض التكلفة في حساب ماهية الفائض ، وهناك بالتأكيد واجهة برمجة تطبيقات أكثر تعقيدًا ، لذلك لا يبدو لي أن الكود الذي لا يهتم بالسعة الزائدة يجب إجباره على دفع ثمنه.

في الوقت الحالي ، يدفع الجميع بالفعل ثمن سمة التخصيص التي تحتوي على واجهتي API لتخصيص الذاكرة. وعلى الرغم من أنه يمكن للمرء إنشاء واجهة برمجة تطبيقات بدون Excess بدون واجهة برمجة تطبيقات Excess كاملة` ، فإن العكس ليس صحيحًا. لذلك أتساءل لماذا لا يتم ذلك على النحو التالي:

  • دائمًا ما تعيد طرق السمات Alloc Excess
  • أضف سمة ExcessLessAlloc التي تتخلص من Excess من Alloc لجميع المستخدمين الذين 1) يهتمون بما يكفي لاستخدام Alloc لكن 2) لا اهتم بالمقدار الحقيقي للذاكرة التي يتم تخصيصها حاليًا (يبدو وكأنه مكان مناسب لي ، لكنني ما زلت أعتقد أن مثل هذه واجهة برمجة التطبيقات من الجيد امتلاكها)
  • إذا اكتشف شخص ما يومًا ما طريقة لتنفيذ Alloc ators مع مسارات سريعة لطرق بدون Excess بدون طرق ، يمكننا دائمًا توفير تنفيذ مخصص ExcessLessAlloc لذلك.

FWIW لقد هبطت للتو على هذا الموضوع مرة أخرى لأنني لا أستطيع تنفيذ ما أريده فوق Alloc . لقد ذكرت أنه يفتقد grow_in_place_excess قبل ، لكنني علقت مرة أخرى لأنه يفتقد أيضًا alloc_zeroed_excess (ومن يعرف ماذا أيضًا).

سأكون أكثر راحة إذا كان التثبيت هنا سيركز على تثبيت واجهة برمجة تطبيقات كاملة Excess أولاً. حتى لو لم تكن واجهة برمجة التطبيقات الخاصة بها هي الأكثر ملاءمة لجميع الاستخدامات ، فإن واجهة برمجة التطبيقات هذه ستسمح على الأقل بجميع الاستخدامات التي تعد شرطًا ضروريًا لإظهار أن التصميم غير معيب.

يمكنك ان تعطي مثالا؟ لا أعرف ، ولا أستطيع تخيل ، مُخصص قادر على تخصيص الذاكرة ولكن لا يعرف مقدار الذاكرة التي يخصصها بالفعل (وهو ما هو Excess : المقدار الحقيقي للذاكرة المخصصة ؛ يجب علينا إعادة تسميته).

يستخدم معظم المُخصصين اليوم فئات الحجم ، حيث تخصص كل فئة حجم كائنات ذات حجم ثابت معين فقط ، ويتم تقريب طلبات التخصيص التي لا تتناسب مع فئة حجم معين إلى فئة الحجم الأصغر التي تناسبها في الداخل. في هذا المخطط ، من الشائع القيام بأشياء مثل وجود مصفوفة من كائنات فئة الحجم ثم تنفيذ classes[size / SIZE_QUANTUM].alloc() . في هذا العالم ، يتطلب معرفة فئة الحجم المستخدمة إرشادات إضافية: على سبيل المثال ، let excess = classes[size / SIZE_QUANTUM].size . قد لا يكون الأمر كثيرًا ، ولكن يتم قياس أداء المخصصات عالية الأداء (مثل jemalloc) في دورات ساعة واحدة ، لذلك يمكن أن يمثل عبءًا ذا مغزى ، خاصةً إذا انتهى هذا الحجم بالمرور عبر سلسلة من إرجاع الوظائف.

يمكنك ان تعطي مثالا؟

على الأقل الخروج من العلاقات العامة الخاصة بك إلى custom_jemalloc ، من الواضح أن alloc_excess يشغل كودًا أكثر من alloc : https://github.com/rust-lang/rust/pull/45514/files.

في هذا المخطط ، من الشائع القيام بأشياء مثل وجود مصفوفة من كائنات فئة الحجم ثم عمل الفئات [size / SIZE_QUANTUM] .alloc (). في هذا العالم ، يتطلب معرفة فئة الحجم المستخدمة إرشادات إضافية: على سبيل المثال ، اسمح بالزيادة = الفئات [الحجم / SIZE_QUANTUM] .size

لذا دعني أرى ما إذا كنت أتبع بشكل صحيح:

// 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);

هل هاذا هو؟


على الأقل الخروج من العلاقات العامة الخاصة بك إلى custom_jemalloc ، من الواضح جدًا أن Custom_excess يقوم بتشغيل كود أكثر من تخصيص

كانت العلاقات العامة عبارة عن إصلاح خطأ (وليس إصلاحًا مثاليًا) ، فهناك العديد من الأشياء الخاطئة في الحالة الحالية لطبقة jemalloc الخاصة بنا ولكن منذ ذلك الحين ، فإنها على الأقل ترجع ما يجب أن:

  • nallocx هي دالة const بمعنى دول مجلس التعاون الخليجي ، أي وظيفة نقية حقيقية. هذا يعني أنه ليس له أي آثار جانبية ، وتعتمد نتائجه على حججه فقط ، ولا يصل إلى أي حالة عالمية ، وحججه ليست مؤشرات (لذلك لا يمكن للوظيفة الوصول إلى الحالة العالمية برميها) ، وبالنسبة لبرامج C / C ++ ، يمكن لـ LLVM استخدام هذا معلومات لتجاهل المكالمة إذا لم يتم استخدام النتيجة. لا يمكن لـ AFAIK Rust حاليًا تمييز وظائف FFI C على أنها const fn أو ما شابه ذلك. لذلك هذا هو أول شيء يمكن إصلاحه والذي سيجعل تكلفة realloc_excess صفر بالنسبة لأولئك الذين لا يستخدمون الفائض طالما أن التضمين والتحسينات تعمل بشكل صحيح.
  • nallocx يتم احتساب دائما لمخصصات الانحياز داخل mallocx ، وهذا هو، كل رمز وcomptuing بالفعل، ولكن mallocx يلقي نتيجته بعيدا، لذلك نحن هنا في الواقع الحوسبة مرتين ، وفي بعض الحالات ، يكون سعر nallocx تقريبًا مثل mallocx ... لديّ مفترق من jemallocator يحتوي على بعض المعايير لأشياء مثل هذا في فروعه ، ولكن يجب إصلاح ذلك قبل المنبع بواسطة jemalloc من خلال توفير واجهة برمجة تطبيقات لا تتخلص من ذلك. ومع ذلك ، لا يؤثر هذا الإصلاح إلا على أولئك الذين يستخدمون حاليًا Excess .
  • ثم المشكلة التي نحسبها لعلامات المحاذاة مرتين ، ولكن هذا شيء يمكن لـ LLVM تحسينه من جانبنا (ومن السهل إصلاحه).

لذا ، نعم ، يبدو وكأنه المزيد من الكود ، لكن هذا الرمز الإضافي هو رمز نتصل به بالفعل مرتين ، لأنه في المرة الأولى التي أطلقنا عليها اسمنا ، قمنا بإلقاء النتائج بعيدًا. ليس من المستحيل إصلاحه ، لكنني لم أجد الوقت للقيام بذلك حتى الآن.


تحرير: sfackler تمكنت من توفير بعض الوقت لها اليوم وتمكنت من الحصول على alloc_excess "مجانًا" فيما يتعلق بـ alloc في مسار jemallocs البطيء ، ولدينا فقط تكلفة إضافية قدرها ~ 1ns في مسار jemallocs السريع. لم أنظر حقًا إلى المسار السريع بتفصيل كبير ، ولكن قد يكون من الممكن تحسين ذلك بشكل أكبر. التفاصيل هنا: https://github.com/jemalloc/jemalloc/issues/1074#issuecomment -345040339

هل هاذا هو؟

نعم.

لذلك هذا هو أول شيء يمكن إصلاحه والذي من شأنه أن يجعل realloc_excess صفر تكلفة لأولئك الذين لا يستخدمون الفائض طالما أن التضمين والتحسينات تعمل بشكل صحيح.

عند استخدامه كمخصص عالمي ، لا يمكن تضمين أي من هذا.

حتى لو لم تكن واجهة برمجة التطبيقات الخاصة بها هي الأكثر ملاءمة لجميع الاستخدامات ، فإن واجهة برمجة التطبيقات هذه ستسمح على الأقل بجميع الاستخدامات التي تعد شرطًا ضروريًا لإظهار أن التصميم غير معيب.

يوجد رمز صفري حرفيًا على Github يستدعي alloc_excess . إذا كانت هذه ميزة بالغة الأهمية ، فلماذا لم يستخدمها أحد من قبل؟ لا توفر واجهات برمجة تطبيقات تخصيص C ++ الوصول إلى السعة الزائدة. يبدو من السهل جدًا إضافة / تثبيت هذه الميزات في المستقبل بطريقة متوافقة مع الإصدارات السابقة إذا كان هناك دليل ملموس فعلي على أنها تحسن الأداء وأي شخص يهتم حقًا باستخدامها.

عند استخدامه كمخصص عالمي ، لا يمكن تضمين أي من هذا.

إذن فهذه مشكلة يجب أن نحاول حلها ، على الأقل بالنسبة للبنيات LTO ، لأن المخصصات العالمية مثل jemalloc تعتمد على هذا: nallocx هي الطريقة _ حسب التصميم_ ، والتوصية الأولى لقد جعلنا مطورو jemalloc فيما يتعلق بأداء alloc_excess أنه يجب أن تكون لدينا تلك المكالمات مضمنة ، ويجب أن ننشر سمات C بشكل صحيح ، بحيث يزيل المترجم nallocx المكالمات من مواقع الاتصال التي لا استخدم Excess ، مثل مترجمي C و C ++.

حتى إذا لم نتمكن من القيام بذلك ، فلا يزال من الممكن جعل واجهة برمجة التطبيقات API Excess خالية من التكلفة من خلال تصحيح واجهة برمجة تطبيقات jemalloc (لدي تنفيذ أولي لمثل هذا التصحيح في rust-lang / شوكة jemalloc). يمكننا إما الحفاظ على واجهة برمجة التطبيقات هذه بأنفسنا ، أو محاولة نقلها إلى المنبع ، ولكن من أجل الوصول إلى المنبع ، يجب علينا تقديم حالة جيدة حول سبب قدرة هذه اللغات الأخرى على إجراء هذه التحسينات ولا يمكن لـ Rust. أو يجب أن يكون لدينا حجة أخرى ، مثل واجهة برمجة التطبيقات الجديدة هذه أسرع بكثير من mallocx + nallocx لهؤلاء المستخدمين الذين يحتاجون إلى Excess .

إذا كانت هذه ميزة بالغة الأهمية ، فلماذا لم يستخدمها أحد من قبل؟

هذا سؤال جيد. std::Vec هو صاحب الملصق لاستخدام Excess API ، لكنه لا يستخدمه حاليًا ، وجميع تعليقاتي السابقة تنص على "هذا وما هو مفقود من Excess API "كنت أحاول جعل Vec يستخدمه. واجهة برمجة تطبيقات Excess :

لا أستطيع أن أعرف لماذا لا أحد يستخدم واجهة برمجة التطبيقات هذه. ولكن بالنظر إلى أنه حتى مكتبة std لا يمكنها استخدامها لهيكل البيانات فهي الأنسب لـ ( Vec ) ، إذا كان عليّ التخمين ، فسأقول أن السبب الرئيسي هو ذلك واجهة برمجة التطبيقات هذه معطلة حاليًا.

إذا اضطررت إلى التخمين إلى أبعد من ذلك ، فسأقول إنه حتى أولئك الذين صمموا واجهة برمجة التطبيقات هذه لم يستخدموها ، ويرجع ذلك أساسًا إلى عدم استخدام مجموعة واحدة من std (وهو المكان الذي أتوقع أن يتم اختبار واجهة برمجة التطبيقات هذه في البداية) ، وأيضًا لأن استخدام _excess و Excess كل مكان ليعني usable_size / allocation_size أمر محير / مزعج للغاية بالنسبة للبرنامج.

ربما يكون هذا بسبب وضع المزيد من العمل في واجهات برمجة التطبيقات التي لا تحتوي على Excess ، وعندما يكون لديك اثنين من واجهات برمجة التطبيقات ، فمن الصعب الاحتفاظ بها متزامنة ، ويصعب على المستخدمين اكتشاف كليهما ومعرفة أيهما يستخدم ، وأخيرًا ، يصعب على المستخدمين تفضيل الراحة على فعل الشيء الصحيح.

أو بعبارة أخرى ، إذا كان لدي واجهتا برمجة تطبيقات متنافستان ، وخصصت 100٪ من العمل لتحسين إحداهما ، و 0٪ من العمل في تحسين الأخرى ، فليس من المستغرب أن أصل إلى نتيجة مفادها أن إحداهما في الممارسة العملية بشكل كبير أفضل من الآخر.

بقدر ما أستطيع أن أقول ، هذه هما النداءان الوحيدان إلى nallocx خارج اختبارات jemalloc على 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

لا يشبه أي منهما واجهة برمجة تطبيقات alloc_excess ، ولكن يتم استخدامهما بشكل مستقل لحساب حجم التخصيص قبل إجرائه.

نظر Apache Arrow في استخدام nallocx في تنفيذه ولكنه اكتشف أن الأشياء لم تنجح بشكل جيد:

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

هذه هي في الأساس المراجع الوحيدة إلى nallocx يمكنني العثور عليها. لماذا من المهم أن يدعم التنفيذ الأولي لواجهات برمجة تطبيقات المخصص مثل هذه الميزة الغامضة؟

بقدر ما أستطيع أن أقول ، هذه هما النداءان الوحيدان إلى nallocx خارج اختبارات jemalloc على Github:

من أعلى رأسي ، أعلم أن نوع متجه facebook على الأقل يستخدمه عبر تطبيق malloc في facebook ( سياسة نمو malloc و fbvector ؛ هذا جزء كبير من متجهات C ++ في facebook يستخدم هذا) وأيضًا أن Chapel استخدمته لتحسين أداء نوعهم String ( هنا ومشكلة التتبع ). لذلك ربما لم يكن اليوم أفضل يوم لـ Github؟

لماذا من المهم أن يدعم التنفيذ الأولي لواجهات برمجة تطبيقات المخصص مثل هذه الميزة الغامضة؟

لا يحتاج التنفيذ الأولي لـ API المخصص إلى دعم هذه الميزة.

لكن الدعم الجيد لهذه الميزة يجب أن يمنع استقرار واجهة برمجة التطبيقات هذه.

لماذا يجب أن يمنع التثبيت إذا كان من الممكن إضافته بشكل عكسي لاحقًا؟

لماذا يجب أن يمنع التثبيت إذا كان من الممكن إضافته بشكل عكسي لاحقًا؟

لأنه بالنسبة لي على الأقل هذا يعني أنه تم استكشاف نصف مساحة التصميم فقط بشكل كافٍ.

هل تتوقع أن تتأثر الأجزاء غير الزائدة ذات الصلة من واجهة برمجة التطبيقات بتصميم الوظيفة المتعلقة بالزيادة؟ أعترف أنني تابعت هذا النقاش فقط بفتور ولكن يبدو أنه من غير المرجح بالنسبة لي.

إذا لم نتمكن من إنشاء واجهة برمجة التطبيقات هذه:

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

بنفس كفاءة هذا:

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

ثم لدينا مشاكل أكبر.

هل تتوقع أن تتأثر الأجزاء غير الزائدة ذات الصلة من واجهة برمجة التطبيقات بتصميم الوظيفة المتعلقة بالزيادة؟

لذا ، نعم ، أتوقع أن يكون لواجهة برمجة التطبيقات (API) الزائدة تأثير كبير على تصميم الوظائف غير الزائدة ذات الصلة: ستزيلها تمامًا.

من شأن ذلك أن يمنع الوضع الحالي المتمثل في وجود اثنين من واجهات برمجة التطبيقات (API) غير متزامنتين ، وفيهما يكون لواجهة برمجة التطبيقات الزائدة وظائف أقل من تلك التي لا تتم مزامنتها. بينما يمكن للمرء إنشاء واجهة برمجة تطبيقات أقل فائضًا فوق واجهة برمجة تطبيقات ممتلئة بشكل زائد ، فإن العكس ليس صحيحًا.

أولئك الذين يريدون إسقاط Excess يجب عليهم فقط إسقاطها.

للتوضيح ، إذا كانت هناك طريقة ما لإضافة طريقة alloc_excess بعد الحقيقة بطريقة متوافقة مع الإصدارات السابقة ، فهل ستوافق على ذلك؟ (لكن بالطبع ، الاستقرار بدون alloc_excess يعني أن إضافته لاحقًا سيكون تغييرًا مفاجئًا ؛ أنا فقط أسأل حتى أفهم أسبابك)

@ joshlf من السهل جدًا القيام بذلك.

: bell: هذا يدخل الآن فترة التعليق النهائية ، وفقًا للمراجعة أعلاه . :جرس:

أولئك الذين يريدون إسقاط الزيادة عليهم فقط إسقاطها.

بدلاً من ذلك ، يمكن لـ 0.01٪ من الأشخاص المهتمين بالسعة الزائدة استخدام طريقة أخرى.

sfackler هذا ما أحصل عليه لأخذ استراحة لمدة أسبوعين من الصدأ - أنسى الطريقة الافتراضية الضمنية :)

بدلاً من ذلك ، يمكن لـ 0.01٪ من الأشخاص المهتمين بالسعة الزائدة استخدام طريقة أخرى.

من أين تحصل على هذا الرقم؟

جميع هياكل بيانات الصدأ لدي مسطحة في الذاكرة. القدرة على القيام بذلك هي السبب الوحيد لاستخدام Rust ؛ إذا كان بإمكاني فقط مربع كل شيء ، فسأستخدم لغة مختلفة. لذلك أنا لا أهتم بالقيمة Excess و 0.01% في ذلك الوقت ، فأنا أهتم بها طوال الوقت.

أفهم أن هذا مجال محدد ، وأنه في المجالات الأخرى ، لن يهتم الأشخاص أبدًا بـ Excess ، لكنني أشك في أن 0.01 ٪ فقط من مستخدمي Rust يهتمون بهذا (أعني ، يستخدم الكثير من الأشخاص Vec و String ، وهي هياكل بيانات الملصق الفرعي لـ Excess ).

أحصل على هذا الرقم من حقيقة أن هناك تقريبًا 4 أشياء في المجموع تستخدم nallocx ، مقارنة بمجموعة الأشياء التي تستخدم malloc.

تضمين التغريدة

هل تقترح أنه إذا فعلنا ذلك "بشكل صحيح" من البداية ، فسيكون لدينا fn alloc(layout) -> (ptr, excess) وليس لدينا fn alloc(layout) -> ptr على الإطلاق؟ هذا يبدو بعيدًا عن الوضوح بالنسبة لي. حتى إذا كان الفائض متاحًا ، يبدو من الطبيعي أن يكون لديك واجهة برمجة التطبيقات الأخيرة لحالات الاستخدام حيث لا يهم الزائدة (على سبيل المثال ، معظم هياكل الأشجار) ، حتى لو تم تنفيذها على أنها alloc_excess(layout).0 .

تضمين التغريدة

هذا يبدو بعيدًا عن الوضوح بالنسبة لي. حتى إذا كان الفائض متاحًا ، يبدو من الطبيعي أن يكون لديك API الأخير لحالات الاستخدام حيث لا يهم الزائدة (على سبيل المثال ، معظم هياكل الأشجار) ، حتى لو تم تنفيذها على أنها تخصيص (تخطيط) 0

حاليًا ، يتم تنفيذ واجهة برمجة التطبيقات (API) الكاملة فوق واجهة برمجة التطبيقات (API) الأقل فائضة. يتطلب تنفيذ Alloc لمخصص أقل فائضًا أن يوفر المستخدم طرق alloc و dealloc .

ومع ذلك ، إذا كنت أرغب في تنفيذ Alloc لمخصص فائض ممتلئ ، فأنا بحاجة إلى توفير المزيد من الأساليب (على الأقل alloc_excess ، لكن هذا ينمو إذا ذهبنا إلى realloc_excess ، alloc_zeroed_excess ، grow_in_place_excess ...).

إذا أردنا القيام بذلك في الاتجاه المعاكس ، أي ، تنفيذ واجهة برمجة التطبيقات (API) الأقل فائضًا كجودة أعلى من الواجهة الفائضة الكاملة ، ثم تنفيذ alloc_excess و dealloc يكفي للدعم كلا النوعين من المخصصات.

يمكن للمستخدمين الذين لا يهتمون أو لا يستطيعون العودة أو الاستعلام عن الفائض إرجاع حجم الإدخال أو التخطيط (وهو إزعاج صغير) ، لكن المستخدمين الذين يمكنهم التعامل مع الزيادة ولا يحتاجون إلى التنفيذ أي طرق أخرى.


تضمين التغريدة

أحصل على هذا الرقم من حقيقة أن هناك تقريبًا 4 أشياء في المجموع تستخدم nallocx ، مقارنة بمجموعة الأشياء التي تستخدم malloc.

بالنظر إلى هذه الحقائق حول استخدام _excess في نظام Rust البيئي:

  • 0 أشياء في المجموع تستخدم _excess في النظام البيئي الصدأ
  • 0 أشياء إجمالاً تستخدم _excess في مكتبة الأمراض المنقولة جنسياً
  • لا يمكن حتى لـ Vec و String استخدام واجهة برمجة التطبيقات _excess بشكل صحيح في مكتبة std
  • واجهة برمجة التطبيقات _excess غير مستقرة ، وغير متزامنة مع واجهة برمجة التطبيقات التي تحتوي على نسبة أقل من الزائدة ، وعربات التي تجرها الدواب حتى وقت قريب جدًا (لم تُرجع حتى excess على الإطلاق) ، ...

    وبالنظر إلى هذه الحقائق حول استخدام _excess بلغات أخرى:

  • jemalloc's API غير مدعوم أصلاً بواسطة برامج C أو C ++ بسبب التوافق مع الإصدارات السابقة

  • تحتاج برامج C و C ++ التي ترغب في استخدام واجهة برمجة تطبيقات jemalloc الزائدة إلى الخروج من طريقها لاستخدامها عن طريق:

    • إلغاء الاشتراك في مخصص النظام والانضمام إلى jemalloc (أو tcmalloc)

    • إعادة تنفيذ مكتبة الأمراض المنقولة جنسياً الخاصة بهم (في حالة C ++ ، قم بتنفيذ مكتبة الأمراض المنقولة جنسياً غير المتوافقة)

    • اكتب مكدسهم بالكامل أعلى مكتبة الأمراض المنقولة جنسياً هذه غير المتوافقة

  • بعض المجتمعات (يستخدمها Firefox ، ويعيد facebook تعديل المجموعات الموجودة في مكتبة C ++ القياسية لتتمكن من استخدامها ، ...) لا يزال بعيدًا عن طريقهم لاستخدامه.

هاتان الحجتان تبدو معقولة بالنسبة لي:

  • واجهة برمجة تطبيقات excess في std غير قابلة للاستخدام ، لذلك لا يمكن لمكتبة std استخدامها ، لذلك لا يمكن لأحد استخدامها ، وهذا هو سبب عدم استخدامها ولو مرة واحدة في نظام Rust البيئي .
  • على الرغم من أن C و C ++ تجعل استخدام واجهة برمجة التطبيقات هذه شبه مستحيلة ، فإن المشاريع الكبيرة ذات القوى العاملة تبذل جهودًا كبيرة لاستخدامها ، وبالتالي فإن بعض المجتمعات الصغيرة المحتملة على الأقل يهتمون بها كثيرًا.

حجتك:

  • لا أحد يستخدم واجهة برمجة تطبيقات _excess ، لذلك فإن 0.01٪ فقط من الناس يهتمون بها.

لا.

alexcrichton قد يكون قرار التبديل من -> Result<*mut u8, AllocErr> إلى -> *mut void مفاجأة كبيرة للأشخاص الذين تابعوا التطوير الأصلي لـ RFC المخصص للمخصص.

لا أختلف مع النقاط التي تثيرها ، ولكن يبدو أن عددًا لا بأس به من الأشخاص سيكونون على استعداد للعيش مع "الوزن الثقيل" Result أكثر من الاحتمال المتزايد لفقدان قيمة خالية- تحقق من القيمة التي تم إرجاعها.

  • إنني أتجاهل مشكلات كفاءة وقت التشغيل التي يفرضها ABI لأنني ، مثل

هل هناك طريقة ما يمكننا من خلالها زيادة وضوح هذا التغيير المتأخر من تلقاء نفسه؟

طريقة واحدة (من أعلى رأسي): قم بتغيير التوقيع الآن ، في العلاقات العامة من تلقاء نفسه ، في الفرع الرئيسي ، بينما لا يزال Allocator غير مستقر. ثم انظر من يشتكي على العلاقات العامة (ومن يحتفل!).

  • هل هذا ثقيل جدا؟ يبدو أنه من خلال التعريفات أقل ثقلًا من اقتران مثل هذا التغيير بالاستقرار ...

فيما يتعلق بموضوع ما إذا كان سيتم إرجاع *mut void أو إرجاع Result<*mut void, AllocErr> : من الممكن أن نعيد النظر في فكرة السمات المخصصة "عالية المستوى" و "منخفضة المستوى" ، كما تمت مناقشته في أخذ II من Allocator RFC .

(من الواضح أنه إذا كان لدي اعتراض جاد على القيمة المرتجعة *mut void ، فسأقدمها على أنها مصدر قلق عبر fcpbot. لكن في هذه المرحلة أثق كثيرًا في حكم فريق libs ، ربما في جزء ما بسبب التعب من هذه الملحمة المخصصة.)

تضمين التغريدة

قد يكون قرار التبديل من -> Result<*mut u8, AllocErr> إلى -> *mut void مفاجأة كبيرة للأشخاص الذين تابعوا التطوير الأصلي لـ RFC المخصص.

هذا الأخير يعني ، كما تمت مناقشته ، أن الخطأ الوحيد الذي نهتم بالتعبير عنه هو OOM. وبالتالي ، فإن الوزن الأخف قليلاً في الوسط الذي لا يزال يتمتع بميزة الحماية ضد الفشل العرضي في التحقق من الأخطاء هو -> Option<*mut void> .

تضمين التغريدة

واجهة برمجة التطبيقات (API) الزائدة في الأمراض المنقولة جنسياً غير قابلة للاستخدام ، وبالتالي لا يمكن لمكتبة الأمراض المنقولة جنسياً استخدامها ، وبالتالي لا يمكن لأحد استخدامها ، ولهذا السبب لم يتم استخدامها ولو مرة واحدة في نظام Rust البيئي.

ثم اذهب لإصلاحه.

تضمين التغريدة

فيما يتعلق بموضوع ما إذا كان سيتم إرجاع mut void أو إرجاع النتيجة < mut void، AllocErr>: من الممكن أن نعيد النظر في فكرة السمات المخصصة "عالية المستوى" و "منخفضة المستوى" ، كما تمت مناقشته في Take II من Allocator RFC.

كانت هذه أفكارنا بشكل أساسي ، باستثناء أن واجهة برمجة التطبيقات عالية المستوى ستكون في Alloc نفسها كـ alloc_one ، alloc_array إلخ. يمكننا حتى أن ندع تلك التطبيقات تتطور في النظام البيئي أولاً كإمتداد السمات لمعرفة ما يتقارب عليه الناس واجهات برمجة التطبيقات.

تضمين التغريدة

السبب في أنني جعلت Layout ينفذ فقط Clone وليس Copy هو أنني أردت أن أترك إمكانية إضافة المزيد من البنية إلى نوع Layout مفتوحًا. على وجه الخصوص ، ما زلت مهتمًا بمحاولة الحصول على محاولة Layout لتتبع أي بنية نوع مستخدمة لتكوينها (على سبيل المثال ، 16-array of Struct {x: u8، y: [char؛ 215]}) ، بحيث يكون للمخصصات خيار عرض إجراءات الأجهزة التي تُبلغ عن الأنواع التي يتم تكوين محتوياتها الحالية منها.

هل تم تجربة هذا في مكان ما؟

sfackler لقد فعلت معظمها بالفعل ، ويمكن القيام بكل ذلك باستخدام واجهة برمجة التطبيقات المكررة (لا يوجد فائض + _excess طرق). سأكون على ما يرام مع وجود اثنين من واجهات برمجة التطبيقات وعدم وجود واجهة برمجة تطبيقات كاملة _excess الآن.

الشيء الوحيد الذي لا يزال يقلقني قليلاً هو أنه لتنفيذ مخصص الآن يحتاج المرء إلى تنفيذ alloc + dealloc ، لكن alloc_excess + dealloc يجب أن يعمل أيضًا. هل من الممكن إعطاء alloc تطبيقًا افتراضيًا من حيث alloc_excess لاحقًا أم أن هذا التغيير غير ممكن أو تغيير جذري؟ من الناحية العملية ، سيقوم معظم المخصصين بتنفيذ معظم الأساليب على أي حال ، لذا فهذه ليست مشكلة كبيرة ، ولكنها أشبه برغبة.


jemallocator ينفذ Alloc مرتين (لـ Jemalloc و &Jemalloc ) ، حيث يكون تنفيذ Jemalloc لبعض method مجرد (&*self).method(...) الذي يعيد توجيه استدعاء الطريقة إلى تنفيذ &Jemalloc . هذا يعني أنه يجب على المرء أن يحتفظ يدويًا بكلتا عمليتي التنفيذ Alloc مقابل Jemalloc في المزامنة. ما إذا كان الحصول على سلوكيات مختلفة لتطبيقات &/_ يمكن أن يكون مأساويًا أم لا ، لا أعرف.


لقد وجدت أنه من الصعب جدًا معرفة ما يفعله الأشخاص بالفعل باستخدام سمة Alloc عمليًا. المشاريع الوحيدة التي اكتشفت أنها تستخدمه ستظل مستخدمة ليلاً على أي حال (مؤازرة ، أكسدة اختزال) ، وستستخدمها فقط لتغيير المخصص العالمي. يقلقني كثيرًا أنني لم أتمكن من العثور على أي مشروع يستخدمه كمعامل لنوع المجموعة (ربما كنت غير محظوظ وهناك البعض؟). كنت أبحث بشكل خاص عن أمثلة لتطبيق SmallVec و ArrayVec فوق نوع يشبه Vec (نظرًا لأن std::Vec لا يحتوي على Alloc نوع معلمة حتى الآن) ، وتساءل أيضًا عن كيفية الاستنساخ بين هذه الأنواع ( Vec s بمعامل Alloc ator مختلف) (نفس الشيء ينطبق على الأرجح على استنساخ Box es بمختلف Alloc s). هل توجد أمثلة على كيف ستبدو هذه التطبيقات في مكان ما؟

المشاريع الوحيدة التي اكتشفت أنها تستخدمها ستظل مستخدمة ليلاً على أي حال (مؤازرة ، أكسدة اختزال)

لما يستحق ، يحاول Servo الابتعاد عن الميزات غير المستقرة حيثما أمكن ذلك: https://github.com/servo/servo/issues/5286

هذه أيضًا مشكلة الدجاج والبيض. لا تستخدم العديد من المشاريع Alloc حتى الآن لأنها لا تزال غير مستقرة.

ليس من الواضح حقًا لماذا يجب أن يكون لدينا مجموعة كاملة من واجهات برمجة التطبيقات (APIs) الزائدة في المقام الأول. كانت موجودة في الأصل لتعكس واجهة برمجة التطبيقات التجريبية * المخصصة لـ jemalloc ، ولكن تمت إزالتها في 4.0 قبل عدة سنوات لصالح عدم تكرار سطح API بالكامل. يبدو أننا يمكن أن نتبع تقدمهم؟

هل من الممكن إعطاء تخصيص تنفيذ افتراضي من حيث التخصيص الزائد لاحقًا أم أن هذا التغيير غير ممكن أو تغيير متقطع؟

يمكننا إضافة تطبيق افتراضي alloc بدلالة alloc_excess ، لكن alloc_excess سيحتاج إلى تطبيق افتراضي من حيث alloc . كل شيء يعمل بشكل جيد إذا قمت بتنفيذ أحدهما أو كليهما ، ولكن إذا لم تنفذ أي منهما ، فسيتم تجميع التعليمات البرمجية الخاصة بك ولكن تتكرر بلا حدود. لقد حدث هذا من قبل (ربما مقابل Rand ؟) ، ويمكن أن يكون لدينا طريقة ما للقول أنك بحاجة إلى تنفيذ واحدة على الأقل من هذه الوظائف ولكننا لا نهتم بأي منها.

يقلقني كثيرًا أنني لم أتمكن من العثور على أي مشروع يستخدمه كمعامل لنوع المجموعة (ربما كنت غير محظوظ وهناك البعض؟).

لا أعرف أي شخص يفعل ذلك.

المشاريع الوحيدة التي اكتشفت أنها تستخدمها ستظل مستخدمة ليلاً على أي حال (مؤازرة ، أكسدة اختزال)

هناك شيء كبير يمنع هذا من المضي قدمًا وهو أن مجموعات stdlib لا تدعم المخصصات البارامترية حتى الآن. يستبعد هذا إلى حد كبير معظم الصناديق الأخرى أيضًا ، نظرًا لأن معظم المجموعات الخارجية تستخدم مجموعات داخلية تحت الغطاء ( Box ، Vec ، إلخ).

المشاريع الوحيدة التي اكتشفت أنها تستخدمها ستظل مستخدمة ليلاً على أي حال (مؤازرة ، أكسدة اختزال)

هناك شيء كبير يمنع هذا من المضي قدمًا وهو أن مجموعات stdlib لا تدعم المخصصات البارامترية حتى الآن. هذا يستبعد إلى حد كبير معظم الصناديق الأخرى أيضًا ، نظرًا لأن معظم المجموعات الخارجية تستخدم مجموعات داخلية أسفل الغطاء (Box ، Vec ، إلخ).

هذا ينطبق علي - لدي نواة لعبة ، وإذا كان بإمكاني استخدام Vec<T, A> ، لكن بدلاً من ذلك يجب أن يكون لدي واجهة مخصصة داخلية قابلة للتغيير ، وهذا إجمالي.

remexre كيف

ستظل هناك حالة عالمية قابلة للتغيير داخليًا على ما أفترض ، لكن من الآمن أن يكون لديك إعداد حيث يكون المخصص العام غير قابل للاستخدام حتى يتم تعيين الذاكرة بالكامل بدلاً من الحصول على وظيفة set_allocator عالمية.


تحرير : أدركت للتو أنني لم أجيب على السؤال. الآن ، لدي شيء مثل:

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();

حيث AreaAllocator هي سمة تتيح لي (في وقت التشغيل) التحقق من أن المخصصات لا "تتداخل" عن طريق الخطأ (من حيث نطاقات العناوين التي يخصصونها). BumpAllocator فقط في وقت مبكر جدًا ، لمساحة التسويد عند تعيين باقي الذاكرة لإنشاء RealAllocator s.

من الناحية المثالية ، أرغب في الحصول على Mutex<Option<RealAllocator>> (أو غلاف يجعله "مُدرجًا فقط") ليكون المُخصص الوحيد ، وأن يتم تخصيص كل شيء مبكرًا بواسطة التمهيد المبكر BumpAllocator . سيسمح لي هذا أيضًا بالتأكد من عدم استخدام BumpAllocator بعد التمهيد المبكر ، نظرًا لأن الأشياء التي أخصصها لا يمكن أن تدوم.

تضمين التغريدة

ليس من الواضح حقًا لماذا يجب أن يكون لدينا مجموعة كاملة من واجهات برمجة التطبيقات (APIs) الزائدة في المقام الأول. كانت موجودة في الأصل لتعكس واجهة برمجة التطبيقات التجريبية * المخصصة لـ jemalloc ، ولكن تمت إزالتها في 4.0 قبل عدة سنوات لصالح عدم تكرار سطح API بالكامل. يبدو أننا يمكن أن نتبع تقدمهم؟

حاليًا shrink_in_place يستدعي xallocx والذي يُرجع حجم التخصيص الحقيقي. نظرًا لعدم وجود shrink_in_place_excess ، فإنه يرمي هذا الحجم بعيدًا ، ويجب على المستخدمين الاتصال بـ nallocx لإعادة حسابه ، والذي تعتمد تكلفته حقًا على حجم التخصيص.

لذلك على الأقل بعض وظائف تخصيص jemalloc التي نستخدمها بالفعل تعيد لنا الحجم القابل للاستخدام ، لكن واجهة برمجة التطبيقات الحالية لا تسمح لنا باستخدامه.

تضمين التغريدة

عندما كنت أعمل على نواة لعبتي ، كان تجنب المخصص العالمي لضمان عدم حدوث تخصيص حتى يتم إعداد مخصص هو هدف لي أيضًا. سعيد لسماع أنني لست الشخص الوحيد!

لا أحب الكلمة Heap للمخصص العام الافتراضي. لماذا ليس Default ؟

نقطة أخرى للتوضيح: RFC 1974 يضع كل هذه الأشياء في std::alloc لكنه حاليًا في std::heap . ما هو الموقع المقترح لتحقيق الاستقرار؟

jethrogb "Heap" هو مصطلح أساسي جدًا لـ "هذا الشيء يمنحك malloc مؤشرات إلى" - ما هي اهتماماتك بهذا المصطلح؟

تضمين التغريدة

"هذا الشيء يمنحك malloc مؤشرات إلى"

ماعدا في رأيي هذا ما هو System .

آه بالتأكيد. Global اسم آخر ربما؟ نظرًا لأنك تستخدم #[global_allocator] لتحديده.

يمكن أن يكون هناك العديد من مخصصات الكومة (مثل libc و jemalloc مسبوقة). ماذا عن إعادة تسمية std::heap::Heap إلى std::heap::Default و #[global_allocator] إلى #[default_allocator] ؟

حقيقة أن هذا هو ما تحصل عليه إذا لم تحدد خلاف ذلك (على سبيل المثال ، عندما يكتسب Vec معلمة / حقل نوع إضافي للمخصص) أكثر أهمية من حقيقة أنه لا يحتوي على "لكل حالة (أو حالات فعلاً).

فترة التعليق الأخيرة قد اكتملت الآن.

فيما يتعلق بـ FCP ، أعتقد أن مجموعة API الفرعية التي تم اقتراحها للتثبيت محدودة الاستخدام للغاية. على سبيل المثال ، لا يدعم الصندوق jemallocator .

في أي طريق؟ قد يضطر jemallocator إلى إيقاف بعض إشارات الطرق غير المستقرة وراء علامة الميزة ولكن هذا كل شيء.

إذا كان jemallocator on Stable Rust لا يمكنه تنفيذ على سبيل المثال Alloc::realloc عن طريق استدعاء je_rallocx ولكن يحتاج إلى الاعتماد على التخصيص الافتراضي + copy + dealloc impl ، إذن فهو ليس بديلاً مقبولاً لـ المكتبة القياسية alloc_jemalloc صندوق IMO.

بالتأكيد ، يمكنك الحصول على شيء لتجميعه ، لكنه ليس شيئًا مفيدًا بشكل خاص.

لماذا ا؟ لا تحتوي C ++ على أي مفهوم لإعادة التخصيص على الإطلاق في واجهة برمجة تطبيقات التخصيص الخاصة بها ولا يبدو أن هذا قد شل اللغة. من الواضح أنها ليست مثالية ، لكنني لا أفهم لماذا سيكون غير مقبول.

لا تستخدم مجموعات C ++ بشكل عام realloc لأن منشئي النقل C ++ يمكنهم تشغيل تعليمات برمجية عشوائية ، وليس لأن إعادة التخصيص غير مفيدة.

والمقارنة ليست مع C ++ ، إنها مع مكتبة Rust القياسية الحالية مع دعم jemalloc المدمج. سيكون التبديل إلى المخصص غير القياسي مع هذه المجموعة الفرعية فقط من Alloc API بمثابة انحدار.

و realloc مثال. يقوم jemallocator حاليًا أيضًا بتنفيذ alloc_zeroed ، alloc_excess ، usable_size ، grow_in_place ، إلخ.

يُقترح أن يتم تثبيت Custom_zeroed. بقدر ما أستطيع أن أقول (انظر لأعلى) ، هناك حرفيا صفر استخدامات alloc_excess في الوجود. هل يمكنك إظهار بعض التعليمات البرمجية التي ستتراجع إذا تراجعت إلى التنفيذ الافتراضي.

بشكل عام ، على الرغم من ذلك ، لا أرى سبب اعتبار هذه حجة ضد تثبيت جزء من واجهات برمجة التطبيقات هذه. إذا كنت لا تريد استخدام jemallocator ، فيمكنك الاستمرار في عدم استخدامه.

هل يمكن جعل Layout::array<T>() ثابتًا؟

يمكن أن تصاب بالذعر ، لذلك ليس في الوقت الحالي.

يمكن أن تصاب بالذعر ، لذلك ليس في الوقت الحالي.

أرى ... سأستقر على const fn Layout::array_elem<T>() وهو ما يعادل Layout::<T>::repeat(1).0 .

mzabaluev أعتقد أن ما تصفه يعادل Layout::new<T>() . يمكن أن يصاب بالذعر حاليًا ، لكن هذا فقط لأنه تم تنفيذه باستخدام Layout::from_size_align ثم .unwrap() ، وأتوقع أن يتم ذلك بشكل مختلف.

joshlf أعتقد أن حجم هذا

struct Foo {
    bar: u32,
    baz: u8
}

لست متأكدًا من أن مصفوفة من Foo ستتضمن المساحة المتروكة للعنصر الأخير لحساب حجمه ، ولكن هذا هو توقعي القوي.

في Rust ، يكون حجم الكائن دائمًا من مضاعفات محاذاته بحيث يكون عنوان العنصر n th من المصفوفة دائمًا array_base_pointer + n * size_of<T>() . لذا فإن حجم الكائن في المصفوفة يكون دائمًا هو نفسه حجم هذا الكائن بمفرده. راجع صفحة Rustonomicon على repr (Rust) لمزيد من التفاصيل.

حسنًا ، اتضح أن البنية مبطنة لمحاذاةها ، لكن AFAIK هذا ليس ضمانًا ثابتًا باستثناء #[repr(C)] .
على أي حال ، فإن إنشاء Layout::new a const fn سيكون موضع ترحيب أيضًا.

هذا هو السلوك الموثق (والمضمون للغاية) لوظيفة مستقرة:

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

إرجاع حجم نوع بالبايت.

وبشكل أكثر تحديدًا ، هذا هو الإزاحة بالبايت بين العناصر المتتالية في مصفوفة بنوع العنصر هذا بما في ذلك مساحة المحاذاة. وبالتالي ، بالنسبة لأي نوع T وطوله n ، فإن حجم [T; n] هو n * size_of::<T>() .

شكر. لقد أدركت للتو أن أي قيمة ثابتة تضاعف نتيجة Layout::new ستكون مذعورة بطبيعتها بدورها (ما لم يتم ذلك باستخدام saturating_mul أو شيء من هذا القبيل) ، لذلك عدت إلى المربع الأول. متابعة سؤال حول الذعر في مسألة تتبع fn.

الماكرو panic!() غير مدعوم حاليًا في التعبيرات الثابتة ، ولكن الذعر من العمليات الحسابية التي تم التحقق منها يتم إنشاؤها بواسطة المترجم ولا يتأثر بهذا القيد:

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

هذا مرتبط بـ Alloc::realloc لكن ليس بتثبيت الحد الأدنى من الواجهة ( realloc ليس جزءًا منها):

حاليًا ، نظرًا لأن Vec::reserve/double call RawVec::reserve/double الذي يستدعي Alloc::realloc ، فإن الأداة الافتراضية لـ Alloc::realloc تنسخ عناصر متجه ميتة (في نطاق [len(), capacity()) ) . في الحالة العبثية للمتجه الفارغ الضخم الذي يريد إدخال عناصر capacity() + 1 وبالتالي إعادة التخصيص ، فإن تكلفة لمس كل تلك الذاكرة ليست ضئيلة.

من الناحية النظرية ، إذا كان التنفيذ الافتراضي Alloc::realloc سيأخذ أيضًا نطاق "bytes_used" ، فيمكنه فقط نسخ الجزء ذي الصلة عند إعادة التخصيص. في الممارسة العملية ، يتخطى jemalloc على الأقل Alloc::realloc الضمني الافتراضي باستدعاء rallocx . ما إذا كان إجراء نسخ رقص alloc / dealloc نسخ الذاكرة ذات الصلة فقط هو أسرع أو أبطأ من مكالمة rallocx ربما يعتمد على أشياء كثيرة (هل يدير rallocx لتوسيع الكتلة في مكانها؟ ما مقدار الذاكرة غير الضرورية التي سيتم نسخها rallocx ؟ إلخ).

https://github.com/QuiltOS/rust/tree/allocator-error لقد بدأت في توضيح كيف أعتقد أن نوع الخطأ المرتبط يحل مجموعاتنا ومشاكل معالجة الأخطاء عن طريق القيام بالتعميم نفسه. على وجه الخصوص ، لاحظ كيف أغير ذلك في الوحدات النمطية

  • أعد دائمًا استخدام تنفيذ Result<T, A::Err> T للتثبيت
  • أبدا unwrap أو أي شيء آخر جزئي
  • لا يوجد oom(e) خارج AbortAdapter .

هذا يعني أن التغييرات التي أجريها آمنة تمامًا ، وطائشة أيضًا! لا ينبغي أن يتطلب العمل مع كل من إرجاع الخطأ وإجهاض الخطأ بذل جهد إضافي للحفاظ على الثوابت العقلية - يقوم مدقق النوع بكل العمل.

أذكر --- أعتقد في RFCGankro ؟ أو خيط rfc المسبق - يقول الناس Gecko / Servo إنه من الجيد ألا تكون المجموعات معصومة من الخطأ أن تكون جزءًا من نوعها حسنًا ، يمكنني إضافة #[repr(transparent)] إلى AbortAdapter بحيث يمكن نقل المجموعات بأمان بين Foo<T, A> و Foo<T, AbortAdapter<A>> (داخل أغلفة آمنة) ، مما يسمح للشخص بحرية التبديل ذهابًا وإيابًا دون تكرار كل طريقة. [بالنسبة إلى التوافق الخلفي ، ستحتاج مجموعات المكتبة القياسية إلى التكرار في أي حال ، ولكن لا يلزم أن تكون طرق المستخدم مثل Result<T, !> وهو أمر سهل للغاية هذه الأيام.]

لسوء الحظ ، لن يقوم الكود بكتابة التحقق بالكامل لأن تغيير معلمات نوع عنصر lang (مربع) يربك المترجم (مفاجأة!). الالتزام بالمربع المسبب لـ ICE هو الأخير - كل شيء قبل أن يكون جيدًا. eddyb ثابت rustc في # 47043!

تحرير joshlf لقد تم https://github.com/rust-lang/rust/pull/45272 ، وقمت بإدراجه هنا. شكر!

الذاكرة الثابتة (على سبيل المثال http://pmem.io ) هي الشيء الكبير التالي ، ويجب أن يتم وضع Rust للعمل معها بشكل جيد.

لقد كنت أعمل مؤخرًا على غلاف Rust لمخصص ذاكرة دائم (على وجه التحديد ، libpmemcto). مهما كانت القرارات التي يتم اتخاذها بشأن استقرار واجهة برمجة التطبيقات هذه ، فإنها تحتاج إلى: -

  • تكون قادرًا على دعم غلاف فائق الأداء حول مخصص ذاكرة ثابتة مثل libpmemcto ؛
  • أن تكون قادرًا على تحديد (تحديد معلمات) أنواع المجموعات عن طريق المخصص (في الوقت الحالي ، يحتاج المرء إلى تكرار Box و Rc و Arc وما إلى ذلك)
  • كن قادرًا على استنساخ البيانات عبر المُخصصين
  • كن قادرًا على دعم وجود هياكل مخزنة في الذاكرة الدائمة مع الحقول التي تمت إعادة تهيئتها عند إنشاء مثيل لمجموعة ذاكرة ثابتة ، أي أن بعض هياكل الذاكرة الدائمة تحتاج إلى حقول يتم تخزينها مؤقتًا فقط على الكومة. حالات الاستخدام الحالية الخاصة بي هي إشارة إلى مجموعة الذاكرة الدائمة المستخدمة للتخصيص والبيانات المؤقتة المستخدمة للأقفال.

بالإضافة إلى ذلك ، فإن تطوير pmem.io (PMDK الخاص بشركة Intel) يستخدم بشكل مكثف مخصص jemalloc المعدل تحت الأغطية - لذلك يبدو من الحكمة أن استخدام jemalloc كمثال لمستهلك API سيكون من الحكمة.

هل سيكون من الممكن تقليل نطاق هذا ليشمل فقط GlobalAllocator s أولاً حتى نكتسب المزيد من الخبرة في استخدام Alloc ators في المجموعات؟

IIUC يخدم هذا بالفعل احتياجات servo ويسمح لنا بتجربة حاويات البارامترات بالتوازي. في المستقبل يمكننا إما نقل المجموعات لاستخدام GlobalAllocator بدلاً من ذلك أو إضافة ضمانة شاملة Alloc لـ GlobalAllocator بحيث يمكن استخدامها لجميع المجموعات.

أفكار؟

gnzlbg لكي تكون السمة #[global_allocator] مفيدة (بخلاف اختيار heap::System ) ، يجب أن تكون السمة Alloc مستقرة أيضًا ، بحيث يمكن تنفيذها بواسطة صناديق مثل https: / /crates.io/crates/jemallocator. لا يوجد نوع أو سمة تسمى GlobalAllocator في الوقت الحالي ، هل تقترح بعض واجهة برمجة التطبيقات الجديدة؟

لا يوجد نوع أو سمة تسمى GlobalAllocator في الوقت الحالي ، هل تقترح بعض واجهة برمجة التطبيقات الجديدة؟

ما اقترحته هو إعادة تسمية "الحد الأدنى" لواجهة برمجة التطبيقات التي اقترح alexcrichton الاستقرار هنا من Alloc إلى GlobalAllocator لتمثيل المخصّصين العالميين فقط ، وترك الباب مفتوحًا للمجموعات ليتم تحديدها بواسطة معلمات مختلفة سمة التخصيص في المستقبل (وهذا لا يعني أنه لا يمكننا تحديد معالمها بالسمة GlobalAllocator ).

يحتاج IIUC servo حاليًا فقط إلى أن يكون قادرًا على تبديل المخصّص العالمي (على عكس القدرة أيضًا على تحديد بعض المجموعات بواسطة مُخصص). لذا ربما بدلاً من محاولة تثبيت حل يجب إثباته في المستقبل لكل من حالات الاستخدام ، يمكننا معالجة مشكلة المخصص العالمي فقط الآن ، ومعرفة كيفية تحديد المجموعات بواسطة المُخصصين لاحقًا.

لا أعرف ما إذا كان ذلك منطقيًا.

تحتاج مؤازرات IIUC حاليًا فقط إلى أن تكون قادرة على تبديل المخصص العالمي (على عكس القدرة أيضًا على تحديد بعض المجموعات بواسطة مُخصص).

هذا صحيح ، لكن:

  • إذا كانت السمة وطريقتها مستقرة بحيث يمكن تنفيذها ، فيمكن أيضًا استدعائها مباشرة دون المرور عبر std::heap::Heap . لذلك فهي ليست فقط مخصصًا عالميًا للسمات ، إنها سمة للمخصصين (حتى لو انتهى بنا الأمر إلى صنع سمة مختلفة للمجموعات العامة على المُخصصات) و GlobalAllocator ليس اسمًا جيدًا بشكل خاص.
  • يستخدم صندوق jemallocator حاليًا alloc_excess و realloc و realloc_excess و usable_size و grow_in_place و shrink_in_place وهي ليست كذلك جزء من الحد الأدنى المقترح API. يمكن أن تكون هذه أكثر كفاءة من الضمنية الافتراضية ، لذا فإن إزالتها ستكون بمثابة تراجع في الأداء.

كلتا النقطتين منطقيتان. لقد اعتقدت للتو أن الطريقة الوحيدة لتسريع استقرار هذه الميزة بشكل كبير هي قطع الاعتماد عليها أيضًا كونها سمة جيدة لتجميع المجموعات عليها.

[سيكون من الرائع أن تكون Servo مثل (مستقر | صندوق موزيلا الرسمي) ، ويمكن للشحن أن تفرض هذا ، لإزالة القليل من الضغط هنا.]

@ Ericson2314 servo ليس المشروع الوحيد الذي يريد استخدام واجهات برمجة التطبيقات هذه.

@ Ericson2314 لا أفهم ماذا يعني هذا ، هل يمكنك إعادة صياغته؟

بالنسبة للسياق: يستخدم Servo حاليًا عددًا من الميزات غير المستقرة (بما في ذلك #[global_allocator] ) ، لكننا نحاول الابتعاد ببطء عن ذلك (إما عن طريق التحديث إلى مترجم استقر في بعض الميزات ، أو عن طريق إيجاد بدائل مستقرة. ) يتم تتبع ذلك على https://github.com/servo/servo/issues/5286. لذا فإن تثبيت #[global_allocator] سيكون أمرًا رائعًا ، لكنه لا يمنع أي عمل مؤازر.

يعتمد Firefox على حقيقة أن Rust std افتراضيًا لمخصص النظام عند تجميع cdylib ، وأن mozjemalloc الذي ينتهي به الأمر إلى الارتباط بنفس النظام الثنائي يحدد رموزًا مثل malloc و free that "shadow" (لا أعرف المصطلحات الصحيحة للرابط) تلك الموجودة في libc. لذا فإن التخصيصات من كود Rust في Firefox تنتهي باستخدام mozjemalloc. (هذا موجود على Unix ، لا أعرف كيف يعمل على Windows.) هذا يعمل ، لكنه يبدو هشًا بالنسبة لي. يستخدم Firefox Rust المستقر ، وأود أن يستخدم #[global_allocator] لتحديد mozjemalloc بشكل صريح لجعل الإعداد بأكمله أكثر قوة.

SimonSapin كلما المخصصين والمجموعات ، كلما أميل إلى الاعتقاد بأننا لا نريد تحديد المجموعات بواسطة Alloc حتى الآن ، لأنه اعتمادًا على المخصص ، قد ترغب المجموعة في تقديم واجهة برمجة تطبيقات مختلفة ، يتغير تعقيد بعض العمليات ، تعتمد بعض تفاصيل المجموعة فعليًا على المخصص ، إلخ.

لذلك أود أن أقترح طريقة يمكننا من خلالها إحراز تقدم هنا.

الخطوة 1: مخصص الكومة

يمكننا تقييد أنفسنا في البداية لمحاولة السماح للمستخدمين بتحديد مخصص الكومة (أو مخصص النظام / النظام الأساسي / العالمي / المتجر المجاني ، أو مع ذلك تفضل تسميته) في Rust المستقر.

الشيء الوحيد الذي حددناه في البداية هو Box ، والذي يحتاج فقط إلى تخصيص ( new ) وإلغاء تخصيص ( drop ) الذاكرة.

يمكن أن تحتوي سمة المخصص هذه مبدئيًا على واجهة برمجة التطبيقات التي اقترحها alexcrichton (أو ممتدة إلى حد ما) ، ويمكن أن تحتوي سمة المخصص هذه ، ليلاً ، على واجهة برمجة تطبيقات ممتدة قليلاً لدعم مجموعات std:: .

بمجرد أن نكون هناك ، سيتمكن المستخدمون الذين يرغبون في الانتقال إلى مستقر من القيام بذلك ، ولكن قد يحصلون على أداء ناجح بسبب واجهة برمجة التطبيقات غير المستقرة.

الخطوة 2: مخصص الكومة بدون إصابة في الأداء

في هذه المرحلة ، يمكننا إعادة تقييم المستخدمين الذين لا يستطيعون الانتقال إلى حالة مستقرة بسبب تأثر الأداء ، وتحديد كيفية توسيع واجهة برمجة التطبيقات هذه وتثبيتها.

الخطوات من 3 إلى N: دعم المخصصات المخصصة في مجموعات std .

أولاً ، هذا صعب ، لذا قد لا يحدث أبدًا ، وأعتقد أنه لم يحدث أبدًا ليس بالأمر السيئ.

عندما أرغب في تحديد مجموعة بمخصص مخصص ، فإما أن يكون لدي مشكلة في الأداء أو مشكلة في قابلية الاستخدام.

إذا كانت لدي مشكلة في قابلية الاستخدام ، فأنا عادةً ما أرغب في واجهة برمجة تطبيقات مجموعة مختلفة تستغل ميزات مخصصي المخصص ، على سبيل المثال صندوقي SliceDeque يفعل ذلك. إن تحديد مجموعة بواسطة مُخصص مخصص لن يساعدني هنا.

إذا كانت لدي مشكلة في الأداء ، فسيظل من الصعب جدًا على المخصص المخصص مساعدتي. سأفكر في Vec في الأقسام التالية فقط ، لأنها المجموعة التي أعدت تنفيذها كثيرًا.

تقليل عدد استدعاءات مخصص النظام (Small Vector Optimization)

إذا كنت أرغب في تخصيص بعض العناصر داخل الكائن Vec لتقليل عدد المكالمات لمخصص النظام ، فأنا اليوم فقط استخدم SmallVec<[T; M]> . ومع ذلك ، فإن SmallVec ليس Vec :

  • نقل Vec هو O (1) في عدد العناصر ، لكن نقل SmallVec<[T; M]> هو O (N) لـ N <M و O (1) بعد ذلك ،

  • المؤشرات إلى العناصر Vec يتم إبطالها أثناء النقل إذا كان len() <= M ولكن ليس بخلاف ذلك ، إذا كانت عمليات len() <= M مثل into_iter تحتاج إلى نقل العناصر إلى كائن المكرر نفسه ، بدلاً من مجرد أخذ المؤشرات.

هل يمكننا أن نجعل Vec عامًا عبر مخصص لدعم هذا؟ كل شيء ممكن ، لكني أعتقد أن أهم التكاليف هي:

  • يؤدي القيام بذلك إلى جعل تطبيق Vec أكثر تعقيدًا ، مما قد يؤثر على المستخدمين الذين لا يستخدمون هذه الميزة
  • ستصبح وثائق Vec أكثر تعقيدًا ، لأن سلوك بعض العمليات سيعتمد على المخصص.

أعتقد أن هذه التكاليف لا يمكن إهمالها.

استفد من أنماط التخصيص

تم تصميم عامل النمو لـ Vec لمخصص معين. في std يمكننا تكييفه مع العناصر الشائعة jemalloc / malloc / ... ولكن إذا كنت تستخدم مخصصًا مخصصًا ، فمن المحتمل أن يكون عامل النمو الذي نختاره بشكل افتراضي لن يكون الأفضل لحالة الاستخدام الخاصة بك. هل يجب أن يكون كل مخصص قادرًا على تحديد عامل نمو لأنماط تخصيص تشبه VEC؟ لا أعرف ، لكن شعوري الغريزي يخبرني: ربما لا.

استغلال الميزات الإضافية لمخصص النظام الخاص بك

على سبيل المثال ، يتوفر المُخصص المُفرط في الالتزام في معظم أهداف المستوى 1 والمستوى 2. في أنظمة Linux-like و Macos ، يقوم مُخصص الكومة بإفراط في الالتزام بشكل افتراضي ، بينما تعرض واجهة برمجة تطبيقات Windows VirtualAlloc والتي يمكن استخدامها لحجز الذاكرة (على سبيل المثال على Vec::reserve/with_capacity ) وتثبيت الذاكرة على push .

في الوقت الحالي ، لا تكشف السمة Alloc طريقة لتنفيذ مثل هذا المخصّص على Windows ، لأنها لا تفصل بين مفاهيم الالتزام بالذاكرة وحجزها (في Linux ، يمكن اختراق مخصص غير ملتزم بمجرد لمس كل صفحة مرة واحدة). كما أنه لا يعرض طريقة للمخصص ليحدد ما إذا كان قد تجاوز الالتزام أم لا افتراضيًا على alloc .

أي أننا سنحتاج إلى تمديد واجهة برمجة تطبيقات Alloc لدعم هذا مقابل Vec ، وسيكون ذلك بمثابة IMO لتحقيق ربح بسيط. لأنه عندما يكون لديك مثل هذا المخصص ، تتغير الدلالات Vec مرة أخرى:

  • لا يحتاج Vec إلى النمو مرة أخرى ، لذا فإن العمليات مثل reserve لا معنى لها
  • push هو O(1) بدلاً من الاستهلاك O(1) .
  • لا يتم إبطال المكرر للكائنات الحية طالما أن الكائن حي ، مما يسمح ببعض التحسينات

استغلال المزيد من الميزات الإضافية لمخصص النظام الخاص بك

بعض مخصصات النظام مثل cudaMalloc / cudaMemcpy / ... تفرق بين الذاكرة المثبتة وغير المثبتة ، وتسمح لك بتخصيص الذاكرة على مساحات العنوان المنفصلة (لذلك سنحتاج إلى نوع المؤشر المرتبط في سمة التخصيص) ، ...

لكن استخدام هذه في مجموعات مثل Vec يغير مرة أخرى دلالات بعض العمليات بطرق خفية ، مثل ما إذا كانت فهرسة ناقل فجأة تستدعي سلوكًا غير محدد أم لا ، اعتمادًا على ما إذا كنت تفعل ذلك من نواة GPU أو من المضيف.

تغليف

أعتقد أن محاولة ابتكار واجهة برمجة تطبيقات Alloc يمكن استخدامها لتجميع كل المجموعات (أو حتى Vec ) أمر صعب ، وربما صعب للغاية.

ربما بعد أن نحصل على مخصصات global / system / platform / heap / free-store بشكل صحيح ، و Box ، يمكننا إعادة التفكير في المجموعات. ربما يمكننا إعادة استخدام Alloc ، ربما نحتاج إلى VecAlloc, VecDequeAlloc , HashMapAlloc` ، ... أو ربما نقول فقط ، "أنت تعرف ماذا ، إذا كنت تحتاج هذا حقًا ، ما عليك سوى نسخ المجموعة القياسية ولصقها في صندوق ، ثم قولبها حسب المخصص الخاص بك ". ربما يكون الحل الأفضل هو جعل هذا الأمر أسهل ، من خلال وجود مجموعات الأمراض المنقولة جنسياً في صندوقها الخاص (أو الصناديق) في الحضانة واستخدام ميزات مستقرة فقط ، ربما يتم تنفيذها كمجموعة من اللبنات الأساسية.

على أي حال ، أعتقد أن محاولة معالجة كل هذه المشكلات هنا مرة واحدة ومحاولة التوصل إلى سمة Alloc التي تكون جيدة لكل شيء أمر صعب للغاية. نحن في الخطوة 0. أعتقد أن أفضل طريقة للوصول إلى الخطوة 1 والخطوة 2 سريعًا هي ترك المجموعات خارج الصورة حتى نكون هناك.

بمجرد أن نكون هناك ، سيتمكن المستخدمون الذين يرغبون في الانتقال إلى مستقر من القيام بذلك ، ولكن قد يحصلون على أداء ناجح بسبب واجهة برمجة التطبيقات غير المستقرة.

عادةً ما يتعلق اختيار مخصص مخصص بتحسين الأداء ، لذلك لا أعرف من الذي سيخدم هذا التثبيت الأولي.

عادةً ما يتعلق اختيار مخصص مخصص بتحسين الأداء ، لذلك لا أعرف من الذي سيخدم هذا التثبيت الأولي.

الجميع؟ على الأقل الآن. معظم الطرق التي تشتكي منها مفقودة في اقتراح التثبيت الأولي ( alloc_excess ، على سبيل المثال) ، لم يتم استخدام AFAIK بواسطة أي شيء في المكتبة القياسية حتى الآن. أو هل تغير هذا مؤخرًا؟

Vec (ومستخدمون آخرون لـ RawVec ) يستخدمون realloc في push

تضمين التغريدة

يقوم صندوق jemallocator حاليًا بتنفيذ تخصيص_الزيادة ، والتخصيص ، والتخصيص الحقيقي ، والحجم القابل للاستخدام ، والنمو_في_المكان ، والتقلص

من هذه الطرق ، يتم استخدام AFAIK realloc و grow_in_place و shrink_in_place لكن grow_in_place عبارة عن غلاف ساذج يزيد عن shrink_in_place لـ jemalloc في لا يقل عن ذلك إذا كنا تنفيذ impl غير مستقر الافتراضي من grow_in_place من حيث shrink_in_place في Alloc سمة، أن التخفيضات ذلك وصولا الى طريقتين: realloc و shrink_in_place .

عادةً ما يتعلق اختيار مخصص مخصص بتحسين الأداء ،

في حين أن هذا صحيح ، قد تحصل على أداء أكثر من استخدام مُخصص أكثر ملاءمة بدون هذه الأساليب ، من مُخصص سيء يمتلكها.

IIUC كانت حالة الاستخدام الرئيسية لأجهزة المؤازرة هي استخدام Firefox jemalloc بدلاً من وجود jemalloc آخر ، هل كان هذا صحيحًا؟

حتى إذا أضفنا realloc و shrink_in_place إلى السمة Alloc في التثبيت الأولي ، فإن ذلك سيؤدي فقط إلى تأخير شكاوى الأداء.

على سبيل المثال ، في اللحظة التي نضيف فيها أي واجهة برمجة تطبيقات غير مستقرة إلى السمة Alloc التي ينتهي بها الأمر باستخدام مجموعات std ، لن تتمكن من الحصول على نفس الأداء على نحو مستقر مما كنت تفعل تكون قادرة على الذهاب ليلا. بمعنى ، إذا أضفنا realloc_excess و shrink_in_place_excess إلى سمة التخصيص وجعلنا Vec / String / ... استخدمها ، فإننا استقرنا realloc و shrink_in_place لم تكن لتساعدك ولو قليلاً.

IIUC كانت حالة الاستخدام الرئيسية لأجهزة المؤازرة هي استخدام Firefox jemalloc بدلاً من وجود jemalloc آخر ، هل كان هذا صحيحًا؟

على الرغم من أنهما يشتركان في بعض التعليمات البرمجية ، فإن Firefox و Servo هما مشروعان / تطبيقان منفصلان.

يستخدم Firefox mozjemalloc ، وهو تفرع من نسخة قديمة من jemalloc مع مجموعة من الميزات المضافة. أعتقد أن بعض كود FFI unsafe يعتمد على صحته وسلامته على mozjemalloc الذي يستخدمه Rust std.

يستخدم Servo jemalloc والذي يحدث ليكون Rust الافتراضي للملفات التنفيذية في الوقت الحالي ، ولكن هناك خطط لتغيير هذا الإعداد الافتراضي إلى مخصص النظام. يحتوي Servo أيضًا على رمز الإبلاغ عن استخدام الذاكرة unsafe والذي يعتمد على السلامة على jemalloc المستخدم بالفعل. (تمرير Vec::as_ptr() إلى je_malloc_usable_size .)

يستخدم Servo jemalloc والذي يحدث ليكون Rust الافتراضي للملفات التنفيذية في الوقت الحالي ، ولكن هناك خطط لتغيير هذا الإعداد الافتراضي إلى مخصص النظام.

سيكون من الجيد معرفة ما إذا كان مخصصو النظام في الأنظمة التي تستهدف المؤازرة يوفرون realloc و shrink_to_fit API مثل jemalloc؟ realloccalloc ) شائعة جدًا ، لكن shrink_to_fit ( xallocx ) هو AFAIK خاص بـ jemalloc . ربما يكون الحل الأفضل هو تثبيت realloc و alloc_zeroed ( calloc ) في التنفيذ الأولي ، وترك shrink_to_fit لوقت لاحق. يجب أن يسمح ذلك المؤازرة بالعمل مع مخصصات النظام في معظم الأنظمة الأساسية دون مشاكل في الأداء.

يحتوي Servo أيضًا على بعض كود الإبلاغ عن استخدام الذاكرة غير الآمن الذي يعتمد على السلامة على jemalloc المستخدم بالفعل. (تمرير Vec :: as_ptr () إلى je_malloc_usable_size.)

كما تعلم ، يحتوي الصندوق jemallocator على واجهات برمجة تطبيقات لهذا الغرض. أتوقع ظهور الصناديق المشابهة لصندوق jemallocator للمخصصين الآخرين الذين يقدمون واجهات برمجة تطبيقات مماثلة حيث تبدأ قصة المخصص العالمي في الاستقرار. لم أفكر في ما إذا كانت واجهات برمجة التطبيقات هذه تنتمي إلى سمة Alloc على الإطلاق.

لا أعتقد أن malloc_usable_size يجب أن يكون في السمة Alloc . استخدام #[global_allocator] للتأكّد من المخصّص الذي يستخدمه Vec<T> وبشكل منفصل استخدام دالة من الصندوق jemallocator .

SimonSapin بمجرد أن تصبح سمة Alloc مستقرة ، من المحتمل أن يكون لدينا صندوق مثل jemallocator لنظامي Linux malloc و Windows. يمكن أن تحتوي هذه الصناديق على ميزة إضافية لتنفيذ الأجزاء التي يمكنها من واجهة برمجة تطبيقات Alloc غير المستقرة (مثل ، usable_size أعلى malloc_usable_size ) وبعض الأشياء الأخرى التي ليست جزءًا من واجهة برمجة تطبيقات Alloc ، مثل إعداد تقارير الذاكرة أعلى mallinfo . بمجرد وجود صناديق قابلة للاستخدام للأنظمة التي تستهدف المؤازرة ، سيكون من الأسهل معرفة أجزاء السمة Alloc لتحديد أولويات الاستقرار ، وربما سنكتشف واجهات برمجة تطبيقات أحدث يجب تجربتها على الأقل مع البعض المخصصين.

gnzlbg أنا متشكك قليلاً في الأشياء الموجودة في https://github.com/rust-lang/rust/issues/32838#issuecomment -358267292. ترك كل تلك الأشياء الخاصة بالنظام ، ليس من الصعب تعميم المجموعات للتخصيص - لقد فعلت ذلك. محاولة دمج ذلك يبدو وكأنه تحد منفصل.

SimonSapin هل لدى

sfackler انظر إلى أن ^. كنت أحاول التمييز بين المشاريع التي تحتاج مقابل هذا الاستقرار ، لكن Servo على الجانب الآخر من هذا الانقسام.

لدي مشروع يريد هذا ويتطلب استقراره. لا يوجد شيء سحري بشكل خاص حول Servo أو Firefox كمستهلكين لـ Rust.

@ Ericson2314 صحيح ، يستخدم Firefox مستقرًا: https://wiki.mozilla.org/Rust_Update_Policy_for_Firefox. كما أوضحت ، على الرغم من وجود حل عملي اليوم ، لذلك هذا ليس مانعًا حقيقيًا لأي شيء. سيكون أجمل / أقوى استخدام #[global_allocator] ، هذا كل شيء.

يستخدم Servo بعض الميزات غير المستقرة ، ولكن كما ذكرنا ، نحاول تغيير ذلك.

FWIW ، المخصصات البارامترية مفيدة جدًا لتنفيذ المخصصات. يصبح مسك الدفاتر الأقل حساسية للأداء أسهل بكثير إذا كان بإمكانك استخدام هياكل بيانات مختلفة داخليًا وتحديد معالمها بواسطة مُخصص أبسط (مثل

هههههههههههه

ترك كل تلك الأشياء الخاصة بالنظام ، ليس من الصعب تعميم المجموعات للتخصيص - لقد فعلت ذلك. محاولة دمج ذلك يبدو وكأنه تحد منفصل.

هل لديك تنفيذ لـ ArrayVec أو SmallVec أعلى Vec + المخصصات المخصصة التي يمكنني الاطلاع عليها؟ كانت هذه هي النقطة الأولى التي ذكرتها ، وهي ليست محددة بنظام على الإطلاق. يمكن القول إن هذا سيكون أبسط مخصّصين يمكن تخيلهما ، أحدهما عبارة عن مصفوفة خام كمخزن ، والآخر يمكن بناؤه فوق الأول عن طريق إضافة احتياطي إلى الكومة بمجرد نفاد سعة المصفوفة. الاختلاف الرئيسي هو أن هؤلاء المخصصين ليسوا "عالميين" ، لكن كل من Vec s له مخصّص خاص به مستقل عن الآخرين ، وهؤلاء المخصصون ذوو الحالة.

أيضا ، أنا لا أجادل في عدم القيام بذلك. أنا أقول فقط أن هذا صعب للغاية: C ++ تحاول منذ 30 عامًا بنجاح جزئي فقط: يعمل مخصصات GPU ومخصصات GC بسبب أنواع المؤشرات العامة ، ولكن يتم تنفيذ ArrayVec و SmallVec فوق Vec لا ينتج عنه تجريد بدون تكلفة في أرض C ++ (يناقش P0843r1 بعض المشكلات لـ ArrayVec بالتفصيل).

لذلك أفضل ما إذا كنا سنتابع هذا الأمر بعد أن نثبّت القطع التي تقدم شيئًا مفيدًا طالما أنها لا تجعل متابعة مخصصات التحصيل المخصصة في المستقبل.


لقد تحدثت قليلاً مع SimonSapin على IRC وإذا أردنا تمديد اقتراح التثبيت الأولي بـ realloc و alloc_zeroed ، فإن Rust في Firefox (الذي يستخدم Rust المستقر فقط) سيكون قادرًا على استخدامه mozjemalloc كمخصص عالمي في حالة الصدأ المستقر دون الحاجة إلى أي اختراقات إضافية. كما ذكر SimonSapin ، لدى Firefox حاليًا حلاً عمليًا لهذا اليوم ، لذلك في حين أن هذا سيكون لطيفًا ، لا يبدو أنه يمثل أولوية عالية جدًا.

ومع ذلك ، يمكننا البدء من هناك ، وبمجرد وصولنا إلى هناك ، قم بتحويل servo إلى الاستقرار #[global_allocator] دون خسارة الأداء.


تضمين التغريدة

FWIW ، المخصصات البارامترية مفيدة جدًا لتنفيذ المخصصات.

هل يمكنك توضيح ما تعنيه بالتفصيل؟ هل هناك سبب يمنعك من تحديد معلمات مخصصاتك المخصصة بالسمة Alloc ؟ أو سمة التخصيص المخصصة الخاصة بك وقم فقط بتنفيذ سمة Alloc على المخصصات النهائية (لا تحتاج هاتان السمتان بالضرورة إلى المساواة)؟

أنا لا أفهم من أين تأتي حالة استخدام "SmallVec = Vec + المخصص الخاص". إنه ليس شيئًا أشرت إليه كثيرًا من قبل (لا في Rust ولا في سياقات أخرى) ، على وجه التحديد لأنه يحتوي على العديد من المشكلات الخطيرة. عندما أفكر في "تحسين الأداء باستخدام مُخصص متخصص" ، فهذا ليس ما أفكر به على الإطلاق.

بالنظر إلى Layout API ، كنت أتساءل عن الاختلافات في معالجة الأخطاء بين from_size_align و align_to ، حيث يُرجع الأول None في حالة حدوث خطأ ، في حين أن الأخير ذعر (!).

ألن يكون من المفيد والمتسق إضافة تعداد LayoutErr معرّف ومفيد بشكل مناسب وإرجاع Result<Layout, LayoutErr> في كلتا الحالتين (وربما استخدمه للوظائف الأخرى التي تُرجع حاليًا Option أيضًا)؟

تضمين التغريدة

أنا لا أفهم من أين تأتي حالة استخدام "SmallVec = Vec + المخصص الخاص". إنه ليس شيئًا أشرت إليه كثيرًا من قبل (لا في Rust ولا في سياقات أخرى) ، على وجه التحديد لأنه يحتوي على العديد من المشكلات الخطيرة. عندما أفكر في "تحسين الأداء باستخدام مُخصص متخصص" ، فهذا ليس ما أفكر به على الإطلاق.

هناك طريقتان مستقلتان لاستخدام المخصصات في Rust و C ++: مخصص النظام ، الذي تستخدمه جميع عمليات التخصيص افتراضيًا ، وكوسيطة نوع لمجموعة معلمات بواسطة بعض سمات المخصص ، كطريقة لإنشاء كائن من تلك المجموعة المعينة التي يستخدم مخصصًا معينًا (يمكن أن يكون مخصصًا للنظام أم لا).

ما يلي يركز فقط على حالة الاستخدام الثانية هذه: استخدام مجموعة ونوع مخصص لإنشاء كائن من تلك المجموعة يستخدم مخصصًا معينًا.

من واقع تجربتي مع C ++ ، فإن تحديد معلمات مجموعة باستخدام مُخصص يخدم حالتين من حالات الاستخدام:

  • تحسين أداء كائن المجموعة بجعل المجموعة تستخدم مخصصًا مخصصًا يستهدف نمط تخصيص محدد ، و / أو
  • إضافة ميزة جديدة إلى مجموعة تسمح لها بفعل شيء لم يكن بإمكانها فعله من قبل.

إضافة ميزات جديدة إلى المجموعات

هذه هي حالة استخدام المُخصصات التي أراها في قواعد رموز C ++ 99٪ من الوقت. حقيقة أن إضافة ميزة جديدة إلى مجموعة تؤدي إلى تحسين الأداء هي ، في رأيي ، مصادفة. على وجه الخصوص ، لا يقوم أي من المُخصصين التاليين بتحسين الأداء من خلال استهداف نمط التخصيص. يفعلون ذلك عن طريق إضافة ميزات ، والتي في بعض الحالات ، كما يذكر @ Ericson2314 ، يمكن اعتبارها "خاصة بالنظام". هذه بعض الأمثلة:

  • كومة المخصصات لإجراء تحسينات صغيرة للمخزن المؤقت (انظر ورق stack_alloc الخاص بـ Howard Hinnant). إنها تتيح لك استخدام std::vector أو flat_{map,set,multimap,...} وبتمريرها إلى مخصص مخصص تضيفه في تحسين المخزن المؤقت الصغير باستخدام ( SmallVec ) أو بدون ( ArrayVec ) كومة تتراجع. يسمح هذا ، على سبيل المثال ، بوضع مجموعة مع عناصرها على المكدس أو الذاكرة الثابتة (حيث كانت ستستخدم الكومة بطريقة أخرى).

  • بنيات الذاكرة المجزأة (مثل أهداف x86 ذات المؤشر العريض 16 بت ووحدات معالجة الرسومات GPGPU). على سبيل المثال ، كانت C ++ 17 Parallel STL ، خلال C ++ 14 ، هي المواصفة الفنية الموازية. مكتبة الطليعة الخاصة بالمؤلف نفسه هي مكتبة اقتحام NVIDIA ، والتي تتضمن مخصصات للسماح لمجموعات الحاويات باستخدام ذاكرة GPGPU (على سبيل المثال ، التوجه :: device_malloc_allocator ) أو الذاكرة المثبتة (على سبيل المثال ، الدفع :: pinned_allocator ؛ تسمح الذاكرة المثبتة بنقل أسرع بين الجهاز المضيف بعض الحالات).

  • المخصصات لحل المشكلات المتعلقة بالتوازي ، مثل المشاركة الخاطئة (على سبيل المثال ، كتل إنشاء خيوط Intel cache_aligned_allocator ) أو متطلبات المحاذاة الزائدة لأنواع SIMD (مثل Eigen3's aligned_allocator ).

  • الذاكرة المشتركة بين العمليات: Boost.Interprocess لها

  • جمع البيانات المهملة: تستخدم مكتبة تخصيص الذاكرة المؤجلة في Herb Sutter نوع مؤشر محدد من قبل المستخدم لتنفيذ

  • المُخصصات المُجهزة: يتيح لك Vec يخصص بعد reserve ؟ قم بتوصيل مثل هذا المخصص ، وسوف يخبرك إذا حدث ذلك. تسمح لك بعض هذه المخصصات بتسميتها ، بحيث يمكنك استخدامها على كائنات متعددة والحصول على سجلات توضح أي كائن يفعل ماذا.

هذه هي أنواع المخصصات التي أراها غالبًا في البرية في C ++. كما ذكرت من قبل ، فإن حقيقة أنها تحسن الأداء في بعض الحالات ، في رأيي ، هي مصادفة. الجزء المهم هو أن لا أحد منهم يحاول استهداف نمط تخصيص معين.

استهداف أنماط التخصيص لتحسين الأداء.

AFAIK لا يوجد أي مخصصات C ++ مستخدمة على نطاق واسع تفعل ذلك وسأشرح لماذا أعتقد أن هذا في ثانية. المكتبات التالية تستهدف حالة الاستخدام هذه:

ومع ذلك ، فإن هذه المكتبات لا توفر حقًا مخصصًا واحدًا لحالة استخدام معينة. بدلاً من ذلك ، فإنها توفر الكتل البرمجية الإنشائية للمخصص التي يمكنك استخدامها لإنشاء مخصصات مخصصة تستهدف نمط التخصيص المحدد في جزء معين من التطبيق.

كانت النصيحة العامة التي أتذكرها من أيام C ++ الخاصة بي هي "عدم استخدامها" (فهي الملاذ الأخير) للأسباب التالية:

  • مطابقة أداء مخصّص النظام صعب جدًا ، والضرب عليه صعب جدًا ،
  • فرص مطابقة نمط تخصيص ذاكرة تطبيق شخص آخر مع نمطك ضئيلة ، لذا فأنت بحاجة حقًا إلى معرفة نمط التخصيص الخاص بك ومعرفة اللبنات الأساسية المخصصة التي تحتاجها لمطابقتها
  • ليست محمولة لأن البائعين المختلفين لديهم تطبيقات مكتبة قياسية مختلفة لـ C ++ والتي تستخدم أنماط تخصيص مختلفة ؛ عادة ما يستهدف البائعون تنفيذها عند مخصصي النظام. أي أن الحل المصمم لمورد ما قد يؤدي بشكل فظيع (أسوأ من مخصص النظام) في آخر.
  • هناك العديد من البدائل التي يمكن للمرء استنفادها قبل محاولة استخدام هذه: استخدام مجموعة مختلفة ، وحفظ الذاكرة ، ... معظم البدائل أقل مجهودًا ويمكن أن تحقق مكاسب أكبر.

هذا لا يعني أن المكتبات لحالة الاستخدام هذه ليست مفيدة. هم ، وهذا هو سبب ازدهار مكتبات مثل

تغليف

أعتقد IMO أنه من الرائع أن يدعم نموذج مخصص C ++ ، على الرغم من كونه بعيدًا عن الكمال ، كلا من حالات الاستخدام ، وأعتقد أنه إذا تم تحديد مجموعات Rust's std بواسطة المُخصصين ، فيجب عليهم دعم كلتا حالات الاستخدام أيضًا ، لأنه على الأقل في تبين أن مخصصات C ++ لكلتا الحالتين مفيدة.

أعتقد فقط أن هذه المشكلة متعامدة بعض الشيء في القدرة على تخصيص المخصص العام / النظام / النظام الأساسي / الافتراضي / الكومة / المتجر المجاني لتطبيق معين ، وأن محاولة حل كلتا المشكلتين في نفس الوقت قد يؤخر الحل لأحدهما منهم دون داع.

قد يكون ما يريد بعض المستخدمين القيام به باستخدام معلمات مجموعة بواسطة المُخصص مختلفًا عما يريد بعض المستخدمين الآخرين القيام به. إذا بدأت rkruppe من "مطابقة أنماط التخصيص" وبدأت من "منع المشاركة الخاطئة" أو "استخدام تحسين المخزن المؤقت الصغير مع احتياطي الكومة" فسيكون من الصعب أولاً فهم احتياجات بعضنا البعض ، وثانيًا ، الوصول في حل يناسب كليهما.

gnzlbg شكرا على الكتابة

سؤالي كان بالتحديد عن هذا التطبيق:

مكدس المخصصات لإجراء تحسينات صغيرة للمخزن المؤقت (انظر ورق stack_alloc الخاص بهوارد هينانت). إنها تتيح لك استخدام std :: vector أو flat_ {map، set، multimap، ...} ومن خلال تمريره مخصصًا مخصصًا تضيفه في تحسين المخزن المؤقت الصغير باستخدام (SmallVec) أو بدون تراجع (ArrayVec). يسمح هذا ، على سبيل المثال ، بوضع مجموعة مع عناصرها على المكدس أو الذاكرة الثابتة (حيث كانت ستستخدم الكومة بطريقة أخرى).

عند القراءة عن stack_alloc ، أدرك الآن كيف يمكن أن يعمل ذلك. هذا ليس ما يقصده الناس عادةً بـ SmallVec (حيث يتم تخزين المخزن المؤقت في المجموعة) ، وهذا هو السبب وراء ضياع هذا الخيار ، ولكنه يخطو جانبًا مشكلة الاضطرار إلى تحديث المؤشرات عندما تتحرك المجموعة (وأيضًا يجعل تلك التحركات أرخص ). لاحظ أيضًا أن short_alloc تسمح لمجموعات متعددة بمشاركة واحدة arena ، مما يجعلها أكثر اختلافًا عن أنواع SmallVec النموذجية. إنه أشبه بمخصص مؤشر خطي / عثرة مع احتياطي رشيق لتخصيص الكومة عند نفاد المساحة المخصصة.

لا أوافق على أن هذا النوع من المخصص و cache_aligned_allocator يضيفان ميزات جديدة بشكل أساسي. يتم استخدامها بشكل مختلف ، وبناءً على تعريفك "لنمط التخصيص" قد لا يتم تحسينها لنمط تخصيص محدد. ومع ذلك ، فهم بالتأكيد يقومون بالتحسين لحالات استخدام محددة وليس لديهم أي اختلافات سلوكية كبيرة من مخصصات كومة الأغراض العامة.

ومع ذلك ، أوافق على أن حالات الاستخدام مثل تخصيص الذاكرة المؤجلة من Sutter ، والتي تغير بشكل كبير ما يعنيه "المؤشر" ، هي تطبيق منفصل قد يحتاج إلى تصميم منفصل إذا أردنا دعمه على الإطلاق.

عند القراءة عن stack_alloc ، أدرك الآن كيف يمكن أن يعمل ذلك. هذا ليس ما يقصده الناس عادةً بـ SmallVec (حيث يتم تخزين المخزن المؤقت في المجموعة) ، وهذا هو السبب وراء ضياع هذا الخيار ، ولكنه يخطو جانبًا مشكلة الاضطرار إلى تحديث المؤشرات عندما تتحرك المجموعة (وأيضًا يجعل تلك التحركات أرخص ).

لقد ذكرت stack_alloc لأنه هو المُخصص الوحيد الذي يحتوي على "ورقة" ، ولكن تم إصداره في عام 2009 وسبق C ++ 11 (لم يدعم C ++ 03 المُخصصات ذات الحالة الخاصة في المجموعات).

الطريقة التي يعمل بها هذا في C ++ 11 (التي تدعم المخصصات ذات الحالة) ، باختصار ، هي:

  • الأمراض المنقولة جنسيا :: ناقلات مخازن و Allocator الكائن داخله تماما مثل الصدأ RawVec يفعل .
  • تحتوي واجهة Allocator على خاصية غامضة تسمى Allocator :: propagate_on_container_move_assignment (POCMA من الآن فصاعدًا) يمكن للمخصصات المعرفة من قبل المستخدم تخصيصها ؛ هذه الخاصية هي true بشكل افتراضي. إذا كانت هذه الخاصية هي false ، عند تعيين النقل ، لا يمكن نشر المخصص ، لذلك يلزم المجموعة بواسطة المعيار لنقل كل عنصر من عناصرها إلى التخزين الجديد يدويًا.

لذلك ، عندما يتم نقل متجه مع مخصص النظام ، يتم أولاً تخصيص تخزين المتجه الجديد على المكدس ، ثم يتم نقل المخصص (وهو حجم صفري) ، ثم يتم نقل المؤشرات الثلاثة ، والتي لا تزال صالحة. هذه الحركات هي O(1) .

OTOHO ، عندما يتم نقل متجه بمخصص POCMA == true ، يتم أولاً تخصيص تخزين المتجه الجديد على المكدس وتهيئته باستخدام متجه فارغ ، ثم تكون المجموعة القديمة drain ed في الجديد ، بحيث يكون القديم فارغًا والجديد ممتلئ. يؤدي هذا إلى تحريك كل عنصر من عناصر المجموعة على حدة ، باستخدام عوامل تعيين النقل الخاصة بهم. هذه الخطوة هي O(N) وتصلح المؤشرات الداخلية للعناصر. أخيرًا ، تم إسقاط المجموعة الأصلية الفارغة الآن. لاحظ أن هذا يبدو وكأنه نسخة ، ولكن ليس لأن العناصر نفسها غير مستنسخة ، ولكن تم نقلها في C ++.

هل هذا منطقي؟

المشكلة الرئيسية في هذا النهج في C ++ هي:

  • يتم تعريف سياسة النمو المتجه للتنفيذ
  • لا تحتوي واجهة برمجة تطبيقات المخصص على طرق _excess
  • يعني الجمع بين المسألتين أعلاه أنه إذا كنت تعرف أن المتجه الخاص بك يمكنه الاحتفاظ بـ 9 عناصر على الأكثر ، فلا يمكنك الحصول على مخصص مكدس يمكنه الاحتفاظ بـ 9 عناصر ، لأن المتجه الخاص بك قد يحاول النمو عندما يكون لديه 8 مع عامل نمو من 1.5 لذلك تحتاج إلى التشاؤم وتخصيص مساحة لـ 18 عنصرًا.
  • يتغير تعقيد عملية المتجه اعتمادًا على خصائص المخصص (POCMA هي واحدة فقط من العديد من الخصائص التي تمتلكها C ++ Allocator API ؛ كتابة مخصصات C ++ غير تافهة). هذا يجعل تحديد واجهة برمجة التطبيقات للمتجه أمرًا صعبًا لأنه في بعض الأحيان يكون لنسخ العناصر أو نقلها بين مختلف المُخصصات من نفس النوع تكاليف إضافية ، مما يغير من تعقيد العمليات. كما أنه يجعل قراءة المواصفات معاناة كبيرة. تضع العديد من مصادر التوثيق عبر الإنترنت مثل cppreference الحالة العامة في المقدمة ، والتفاصيل الغامضة لما يتغير إذا كانت خاصية تخصيص واحدة صحيحة أو خاطئة بأحرف صغيرة جدًا لتجنب إزعاج 99٪ من المستخدمين بها.

هناك العديد من الأشخاص الذين يعملون على تحسين واجهة برمجة تطبيقات تخصيص C ++ لإصلاح هذه المشكلات ، على سبيل المثال ، عن طريق إضافة طرق _excess والتأكد من أن المجموعات المطابقة القياسية تستخدمها.

لا أوافق على أن هذا النوع من المخصصات و cache_aligned_allocator يضيفان ميزات جديدة بشكل أساسي.

ربما ما قصدته هو أنها تسمح لك باستخدام مجموعات الأمراض المنقولة جنسياً في المواقف أو للأنواع التي لم تتمكن من استخدامها من قبل. على سبيل المثال ، في لغة ++ C ، لا يمكنك وضع عناصر المتجه في مقطع الذاكرة الثابتة للثنائي الخاص بك بدون شيء مثل مُخصص المكدس (ومع ذلك يمكنك كتابة مجموعتك الخاصة التي تقوم بذلك). OTOH ، لا يدعم معيار C ++ أنواع المحاذاة الزائدة مثل أنواع SIMD ، وإذا حاولت تخصيص كومة واحدة بـ new فسوف تستدعي سلوك غير محدد (تحتاج إلى استخدام posix_memalign أو ما شابه) . يُظهر استخدام الكائن عادةً السلوك غير المحدد عبر segfault (*). أشياء مثل aligned_allocator تسمح لك بتخصيص هذه الأنواع ، وحتى وضعها في مجموعات قياسية ، دون استدعاء سلوك غير محدد ، باستخدام مُخصص مختلف. بالتأكيد سيكون للمخصص الجديد أنماط تخصيص مختلفة (يقوم هؤلاء المخصصون بشكل أساسي بمحاذاة كل الذاكرة بالمناسبة ...) ، ولكن ما يستخدمهم الناس من أجله هو أن يكونوا قادرين على القيام بشيء لم يتمكنوا من فعله من قبل.

من الواضح أن Rust ليس C ++. و C ++ به مشاكل لا يعاني منها Rust (والعكس صحيح). قد يكون المُخصص الذي يضيف ميزة جديدة في C ++ غير ضروري في Rust ، والذي ، على سبيل المثال ، لا يواجه أي مشاكل مع أنواع SIMD.

(*) يعاني مستخدمو Eigen3 من هذا الأمر بشدة ، لأنه لتجنب السلوك غير المحدد عند استخدام حاويات C ++ و STL ، تحتاج إلى حماية الحاويات ضد أنواع SIMD ، أو الأنواع التي تحتوي على أنواع SIMD ( مستندات Eigen3 ) وأيضًا تحتاج إلى حماية نفسك من في أي وقت باستخدام new على الأنواع الخاصة بك عن طريق زيادة تحميل عامل التشغيل new لهم ( المزيد من مستندات Eigen3 )

gnzlbg شكرًا ، لقد كنت مرتبكًا أيضًا من مثال smallvec. سيتطلب ذلك أنواعًا غير قابلة للحركة ونوعًا من التخصيص في Rust - اثنان من RFC قيد المراجعة ثم المزيد من أعمال المتابعة - لذلك ليس لدي أي قلق بشأن ذلك في الوقت الحالي. تبدو إستراتيجية smallvec الحالية المتمثلة في استخدام كل مساحة المكدس التي تحتاجها دائمًا جيدة في الوقت الحالي.

أتفق أيضًا مع rkruppe على أنه في قائمتك المنقحة ، لا يلزم معرفة الإمكانات الجديدة للمخصص للمجموعة التي تستخدم المخصص. في بعض الأحيان ، يحتوي Collection<Allocator> الكامل على خصائص جديدة (موجودة بالكامل في الذاكرة المثبتة ، دعنا نقول) ولكن هذا مجرد نتيجة طبيعية لاستخدام المخصص.

الاستثناء الوحيد الذي أراه هنا هو المخصصات التي تخصص فقط حجمًا / نوعًا واحدًا (تقوم NVidia بذلك ، كما يفعل مخصصات الألواح). يمكن أن يكون لدينا سمة ObjAlloc<T> منفصلة يتم تنفيذها بشكل شامل للمخصصين العاديين: impl<A: Alloc, T> ObjAlloc<T> for A . بعد ذلك ، ستستخدم المجموعات حدود ObjAlloc إذا احتاجت فقط إلى تخصيص بعض العناصر. لكني أشعر بالسخافة إلى حد ما حتى عند طرح هذا الأمر لأنه يجب أن يكون قابلاً للتنفيذ بشكل عكسي لاحقًا.

هل هذا منطقي؟

بالتأكيد لكنها ليست ذات صلة حقًا بـ Rust نظرًا لعدم وجود مُنشئين متحركين. لذا فإن المخصص (المتحرك) الذي يحتوي مباشرة على الذاكرة التي يوزع عليها المؤشرات فقط غير ممكن ، هذه الفترة.

على سبيل المثال ، في لغة ++ C ، لا يمكنك وضع عناصر المتجه في مقطع الذاكرة الثابتة للثنائي الخاص بك بدون شيء مثل مُخصص المكدس (ومع ذلك يمكنك كتابة مجموعتك الخاصة التي تقوم بذلك).

هذا ليس تغييرًا سلوكيًا. هناك العديد من الأسباب الصحيحة للتحكم في المكان الذي تحصل منه المجموعات على ذاكرتها ، ولكن جميعها مرتبطة "بالعوامل الخارجية" مثل الأداء ، ونصوص الروابط ، والتحكم في تخطيط ذاكرة البرنامج بالكامل ، وما إلى ذلك.

تسمح لك أشياء مثل align_allocator بتخصيص هذه الأنواع ، وحتى وضعها في مجموعات قياسية ، دون استدعاء سلوك غير محدد ، باستخدام مُخصص مختلف.

هذا هو السبب في أنني ذكرت على وجه التحديد cache_aligned_allocator الخاص بـ TBB وليس align_allocator الخاص بـ Eigen. لا يبدو أن cache_aligned_allocator يضمن أي محاذاة محددة في وثائقه (يقول فقط أنه "نموذجي" 128 بايت) ، وحتى إذا تم ذلك ، فلن يتم استخدامه عادةً لهذا الغرض (لأن محاذاته ربما تكون كبيرة جدًا بحيث لا يمكن استخدامها بشكل عام أنواع SIMD وصغيرة جدًا بالنسبة لأشياء مثل DMA المحاذاة للصفحة). والغرض منه ، كما ذكرت ، هو تجنب المشاركة الزائفة.

تضمين التغريدة

FWIW ، المخصصات البارامترية مفيدة جدًا لتنفيذ المخصصات.

هل يمكنك توضيح ما تعنيه بالتفصيل؟ هل هناك سبب يمنعك من تحديد مخصصاتك المخصصة بسمة Alloc؟ أو سمة المخصص المخصص الخاصة بك وقم فقط بتنفيذ سمة التخصيص على المخصصات النهائية (لا يلزم بالضرورة أن تكون هاتان السمتان متساويتين)؟

أعتقد أنني لم أكن واضحًا. اسمحوا لي أن أحاول أن أشرح بشكل أفضل. لنفترض أنني أقوم بتطبيق مخصص أتوقع استخدامه إما:

  • كمخصص عالمي
  • في بيئة خالية من الأمراض المنقولة بالاتصال الجنسي

ولنفترض أنني أرغب في استخدام Vec تحت الغطاء لتنفيذ هذا المخصص. لا يمكنني استخدام Vec مباشرةً كما هو موجود اليوم لأنه

  • إذا كنت أنا المخصص العالمي ، فإن استخدامه سيقدم فقط تبعية متكررة على نفسي
  • إذا كنت في بيئة خالية من الأمراض المنقولة بالاتصال الجنسي ، فلا يوجد Vec كما هو موجود اليوم

وبالتالي ، فإن ما أحتاجه هو أن أكون قادرًا على استخدام Vec الذي يتم تحديده على مُخصص آخر أستخدمه داخليًا لمسك الدفاتر الداخلية البسيطة. هذا هو هدف bsalloc (ومصدر الاسم - يتم استخدامه في

في elfmalloc ، ما زلنا قادرين على أن نكون مخصصًا عالميًا من خلال:

  • عند تجميع أنفسنا ، قم بترجمة jemalloc بشكل ثابت كمخصص عالمي
  • قم بإنشاء ملف كائن مشترك يمكن تحميله ديناميكيًا بواسطة برامج أخرى

لاحظ أن في هذه الحالة، فإنه من المهم أن لا تجميع أنفسنا باستخدام مخصص النظام كما مخصص العالمي لأنه بعد ذلك، مرة واحدة تحميل، كنا إعادة تعريف التبعية متكررة، لأنه في تلك المرحلة، ونحن مخصص النظام.

لكنها لا تعمل عندما:

  • شخص ما يريد أن يستخدمنا كمخصص عالمي في Rust بالطريقة "الرسمية" (بدلاً من إنشاء ملف كائن مشترك أولاً)
  • نحن في بيئة خالية من الأمراض المنقولة بالاتصال الجنسي

OTOH ، لا يدعم معيار C ++ أنواع المحاذاة الزائدة مثل أنواع SIMD ، وإذا حاولت كومة تخصيص واحدة جديدة ، فسوف تستدعي سلوكًا غير محدد (تحتاج إلى استخدام posix_memalign أو ما شابه).

نظرًا لأن سمة Alloc الحالية لدينا تأخذ المحاذاة كمعامل ، أفترض أن هذه الفئة من المشكلات (مشكلة "لا أستطيع العمل بدون محاذاة مختلفة") تختفي بالنسبة لنا؟

gnzlbg - كتابة شاملة (شكرًا لك) ولكن أيا من حالات الاستخدام لا تغطي الذاكرة الدائمة *.

يجب النظر في حالة الاستخدام هذه. على وجه الخصوص ، فإنه يؤثر بشدة على الشيء الصحيح الذي يجب القيام به: -

  • أن أكثر من مخصص واحد قيد الاستخدام ، وعلى وجه الخصوص ، عند استخدام هذا المخصص للذاكرة الدائمة ، فلن يكون مطلقًا هو مخصص النظام ؛ (في الواقع ، يمكن أن يكون هناك العديد من مخصصات الذاكرة الدائمة)
  • تكلفة "إعادة تنفيذ" المجموعات القياسية عالية ، وتؤدي إلى رمز غير متوافق مع مكتبات الطرف الثالث.
  • عمر المخصص ليس بالضرورة 'static .
  • تحتاج الكائنات المخزنة في الذاكرة الدائمة إلى حالة إضافية يجب ملؤها من الكومة ، أي أنها تحتاج إلى إعادة تهيئة الحالة. هذا ينطبق بشكل خاص على كائنات المزامنة وما شابه. ما كان يمكن التخلص منه مرة واحدة لم يعد يتم التخلص منه.

لدى Rust فرصة رائعة لاغتنام المبادرة هنا ، وجعلها منصة من الدرجة الأولى لما سيحل محل محركات الأقراص الصلبة ومحركات أقراص الحالة الثابتة وحتى وحدات التخزين المتصلة بـ PCI.

* ليست مفاجأة ، حقًا ، لأنه حتى وقت قريب جدًا كان الأمر مميزًا بعض الشيء. إنه مدعوم الآن على نطاق واسع في Linux و FreeBSD و Windows.

تضمين التغريدة

هذا في الحقيقة ليس المكان المناسب لتكوين ذاكرة ثابتة. إن مدرستك ليست المدرسة الفكرية الوحيدة فيما يتعلق بواجهة الذاكرة الدائمة - على سبيل المثال ، قد يتضح أن النهج السائد هو ببساطة التعامل معها كقرص أسرع ، لأسباب تتعلق بسلامة البيانات.

إذا كانت لديك حالة استخدام لاستخدام الذاكرة الثابتة بهذه الطريقة ، فمن الأفضل أن تقوم بهذه الحالة في مكان آخر بطريقة ما أولاً. ضع نموذجًا أوليًا لها ، وتوصل إلى بعض التغييرات الملموسة على واجهة المخصص ، وتأكد بشكل مثالي من أن هذه التغييرات تستحق التأثير الذي سيكون لها على الحالة المتوسطة.

تضمين التغريدة

أنا أعترض. هذا هو بالضبط نوع المكان الذي تنتمي إليه. أريد أن أتجنب اتخاذ قرار من شأنه أن يخلق تصميمًا ناتجًا عن التركيز الضيق للغاية والبحث عن الأدلة.

إن Intel PMDK الحالي - حيث يتم التركيز على الكثير من الجهد لدعم مساحة المستخدم منخفضة المستوى - يقترب منه أكثر بكثير مثل الذاكرة العادية المخصصة مع المؤشرات - الذاكرة التي تشبه تلك عبر mmap ، على سبيل المثال. في الواقع ، إذا أراد أحد العمل مع ذاكرة ثابتة على Linux ، فأعتقد أنه إلى حد كبير منفذ الاتصال الوحيد في الوقت الحالي. في جوهرها ، تعامل واحدة من أكثر مجموعات الأدوات تقدمًا لاستخدامها - السائدة إذا أردت - على أنها ذاكرة مخصصة.

بالنسبة للنماذج الأولية - حسنًا ، هذا بالضبط ما قلته لقد فعلت: -

لقد كنت أعمل مؤخرًا على غلاف Rust لمخصص ذاكرة دائم (على وجه التحديد ، libpmemcto).

(يمكنك استخدام إصدار الأيام الأولى من الصندوق الخاص بي على https://crates.io/crates/nvml . هناك الكثير من التجارب في التحكم بالمصادر في الوحدة النمطية cto_pool ).

تم بناء النموذج الأولي الخاص بي مع ما هو مطلوب لاستبدال محرك تخزين البيانات في نظام واسع النطاق في العالم الحقيقي. هناك عقلية مماثلة وراء العديد من مشاريعي مفتوحة المصدر. لقد وجدت على مدار سنوات عديدة أفضل المكتبات ، وكذلك أفضل المعايير ، تلك التي تشتق من الاستخدام الحقيقي.

لا شيء يضاهي محاولة ملاءمة مُخصِّص في العالم الحقيقي للواجهة الحالية. بصراحة ، كانت تجربة استخدام واجهة Alloc ، ثم نسخ Vec بالكامل ، ثم تعديلها ، مؤلمة. تفترض العديد من الأماكن أنه لم يتم تمرير المخصصات ، على سبيل المثال Vec::new() .

أثناء القيام بذلك ، قدمت بعض الملاحظات في تعليقي الأصلي حول ما سيكون مطلوبًا من المخصص ، وما هو مطلوب من مستخدم مثل هذا المخصص. أعتقد أن هذه صالحة جدًا في سلسلة محادثات حول واجهة مخصص.

الخبر السار هو أن أول 3 نقاط من https://github.com/rust-lang/rust/issues/32838#issuecomment -358940992 يتم مشاركتها بواسطة حالات استخدام أخرى.

أردت فقط أن أضيف أنني لم أقم بإضافة ذاكرة غير متطايرة إلى القائمة
لأن القائمة أدرجت حالات استخدام المخصصات في حاويات
عالم C ++ الذي يتم استخدامه "على نطاق واسع" ، على الأقل بناءً على تجربتي (هؤلاء
المؤلفات التي ذكرتها هي في الغالب من مكتبات شائعة جدًا يستخدمها الكثير).
بينما أعرف جهود Intel SDK (بعض مكتباتهم
target C ++) أنا شخصياً لا أعرف أي مشاريع تستخدمها (هل لديهم
مخصص يمكن استخدامه مع الأمراض المنقولة جنسيا :: المتجه؟ لا أدري، لا أعرف). هذا لا
يعني أنها لا تستخدم ولا مهمة. سأكون مهتمًا بمعرفة
حول هذه ، ولكن النقطة الرئيسية في رسالتي كانت تلك المعلمات
المخصصات حسب الحاويات معقدة للغاية ، ويجب أن نحاول صنعها
التقدم مع نظام تخصيصات النظام دون إغلاق أي أبواب للحاويات
(ولكن يجب علينا معالجة ذلك لاحقًا).

يوم الأحد 21 يناير 2018 الساعة 17:36 ، كتب John Ericson [email protected] :

الخبر السار هو أول 3 نقاط لك من # 32838 (تعليق)
https://github.com/rust-lang/rust/issues/32838#issuecomment-358940992
يتم مشاركتها بواسطة حالات استخدام أخرى.

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/rust-lang/rust/issues/32838#issuecomment-359261305 ،
أو كتم الخيط
https://github.com/notifications/unsubscribe-auth/AA3Npk95PZBZcm7tknNp_Cqrs_3T1UkEks5tM2ekgaJpZM4IDYUN
.

حاولت قراءة معظم ما كتب بالفعل ، لذا فقد يكون هذا موجودًا بالفعل وفي هذه الحالة أنا آسف إذا فاتني ذلك ولكن هنا يذهب:

شيء شائع إلى حد ما للألعاب (في C / C ++) هو استخدام "تخصيص خدش لكل إطار" ما يعنيه هذا هو وجود مخصص خطي / نتوء يتم استخدامه للتخصيصات التي تظل حية لفترة زمنية معينة (في إطار لعبة) ثم "دمر".

تم تدميره في هذه الحالة مما يعني أنك تقوم بإعادة تعيين المُخصص إلى موضع البداية. لا يوجد "تدمير" للأشياء على الإطلاق لأن هذه الكائنات يجب أن تكون من نوع POD (وبالتالي لا يتم تنفيذ أي مدمرات)

أتساءل عما إذا كان شيء مثل هذا يتناسب مع تصميم المخصص الحالي في Rust؟

(تحرير: يجب أن يكون هناك أي تدمير للأشياء)

emoon

شيء شائع إلى حد ما للألعاب (في C / C ++) هو استخدام "تخصيص خدش لكل إطار" ما يعنيه هذا هو وجود مخصص خطي / نتوء يتم استخدامه للتخصيصات التي تظل حية لفترة زمنية معينة (في إطار لعبة) ثم "دمر".

تم تدميره في هذه الحالة مما يعني أنك تقوم بإعادة تعيين المُخصص إلى موضع البداية. هناك "تدمير" للأشياء على الإطلاق لأن هذه الأشياء يجب أن تكون من نوع POD (وبالتالي لا يتم تنفيذ أي مدمرات)

يجب أن يكون ممكنًا. خارج الجزء العلوي من رأسي ، ستحتاج إلى كائن واحد للساحة نفسها وكائن آخر يمثل مقبض لكل إطار على الساحة. بعد ذلك ، يمكنك تنفيذ Alloc لهذا المقبض ، وبافتراض أنك تستخدم أغلفة آمنة عالية المستوى للتخصيص (على سبيل المثال ، تخيل أن Box يصبح حدوديًا على Alloc ) ، تضمن الأعمار إسقاط جميع الكائنات المخصصة قبل إسقاط المقبض لكل إطار. لاحظ أنه لا يزال من الممكن استدعاء dealloc لكل عنصر ، ولكن إذا كان dealloc غير متاح ، فقد يتم تحسين منطق الإسقاط وإلغاء التخصيص بالكامل أو في الغالب بعيدًا.

يمكنك أيضًا استخدام نوع مؤشر ذكي مخصص لا يطبق Drop ، مما يجعل الكثير من الأشياء أسهل في أي مكان آخر.

شكر! لقد ارتكبت خطأ مطبعي في رسالتي الأصلية. إنه يعني أنه لا يوجد تدمير للأشياء.

بالنسبة للأشخاص الذين ليسوا خبراء في المخصصين ، ولا يمكنهم متابعة هذا الموضوع ، ما هو الإجماع الحالي: هل نخطط لدعم المخصصين المخصصين لأنواع مجموعة stdlib؟

alexreg لست متأكدًا من الخطة النهائية ، ولكن هناك 0 صعوبات فنية مؤكدة في القيام بذلك. OTOH ليس لدينا طريقة جيدة لفضح ذلك في std لأن متغيرات النوع الافتراضي مشبوهة ، لكن ليس لدي مشكلة في جعلها مجرد alloc -شيء في الوقت الحالي لذلك نحن يمكن أن يحرز تقدمًا على الجانب الليبرالي دون عوائق.

@ Ericson2314 حسنًا ، من الجيد الاستماع. هل تم تنفيذ متغيرات النوع الافتراضي حتى الآن؟ أو ربما في مرحلة RFC؟ كما تقول ، إذا كانت مقيدة فقط بالأشياء المتعلقة بالتخصيص / std::heap ، فيجب أن يكون كل شيء على ما يرام.

أعتقد حقًا أن AllocErr يجب أن يكون خطأ. سيكون أكثر اتساقًا مع وحدات أخرى (مثل io).

impl Error for AllocError المحتمل أن يكون Error عديمة الفائدة.

كنت أبحث في وظيفة Layout :: from_size_align اليوم ، ويجب ألا يتجاوز " align 2 ^ 31 (أي 1 << 31 ) ،" لم يكن القيد منطقيًا بالنسبة لي. وأشار git blame إلى # 30170.

يجب أن أقول أن هذه كانت رسالة التزام خادعة تمامًا هناك ، حيث تحدثنا عن align ملائمًا في u32 ، وهو أمر عرضي فقط ، عندما يكون الشيء الفعلي الذي يتم "إصلاحه" (تم العمل عليه أكثر) يسيء التصرف.

وهو ما يقودني إلى هذه الملاحظة: لا يجب فحص عنصر "OSX / Specialty_system هو عربات التي تجرها الدواب في محاذاة ضخمة" هنا. بينما تم التعامل مع المشكلة المباشرة ، لا أعتقد أن الإصلاح صحيح على المدى الطويل: نظرًا لأن مخصِّص النظام يسيء التصرف لا ينبغي أن يمنع تطبيق مخصص يتصرف. والقيود التعسفية على Layout :: from_size_align تفعل ذلك.

glandium هل من المفيد طلب المحاذاة لمضاعفات 4 جيجا بايت أو أكثر؟

أستطيع أن أتخيل الحالات التي قد يرغب فيها المرء في الحصول على تخصيص لـ 4GiB محاذاة في 4GiB ، وهو أمر غير ممكن حاليًا ، ولكن ليس أكثر من ذلك. لكنني لا أعتقد أنه يجب إضافة قيود عشوائية لمجرد أننا لا نفكر في مثل هذه الأسباب الآن.

يمكنني أن أتخيل الحالات التي قد يرغب فيها المرء في الحصول على تخصيص لـ 4GiB محاذي في 4GiB

ما هي تلك الحالات؟

يمكنني أن أتخيل الحالات التي قد يرغب فيها المرء في الحصول على تخصيص لـ 4GiB محاذي في 4GiB

ما هي تلك الحالات؟

بشكل ملموس ، لقد أضفت للتو دعمًا للمحاذاة الكبيرة بشكل تعسفي في mmap-alloc أجل دعم تخصيص ألواح كبيرة ومتوافقة من الذاكرة للاستخدام في elfmalloc . الفكرة هي أن تكون شريحة الذاكرة محاذاة لحجمها بحيث ، عند إعطاء مؤشر لكائن مخصص من تلك اللوح ، تحتاج فقط إلى إخفاء البتات المنخفضة للعثور على اللوحة المحتوية. لا نستخدم حاليًا ألواحًا بحجم 4 غيغابايت (بالنسبة للأشياء الكبيرة ، نذهب مباشرة إلى mmap) ، ولكن لا يوجد سبب يمنعنا من ذلك ، ويمكنني تمامًا تخيل تطبيق به متطلبات ذاكرة وصول عشوائي كبيرة يريد القيام هذا (أي إذا خصصت كائنات متعددة الجيجابايت بشكل متكرر بما يكفي بحيث لا ترغب في قبول الحمل الزائد لـ mmap).

فيما يلي حالة استخدام محتملة لمحاذاة> 4GiB: المحاذاة إلى حدود صفحة كبيرة. توجد بالفعل منصات تدعم> 4 صفحات جيبي بايت. يقول مستند IBM هذا "يدعم المعالج POWER5 + أربعة أحجام لصفحات الذاكرة الظاهرية: 4 كيلوبايت ، و 64 كيلوبايت ، و 16 ميجابايت ، و 16 جيجابايت." حتى x86-64 ليس بعيدًا: عادةً ما تكون "الصفحات الضخمة" 2 ميجابايت ، ولكنها تدعم أيضًا 1 جيجابايت.

تتعامل جميع الوظائف غير المكتوبة في سمة التخصيص مع *mut u8 . مما يعني أنه يمكنهم أخذ مؤشرات فارغة أو إرجاعها ، وسوف ينفصل كل الجحيم. هل يجب عليهم استخدام NonNull بدلاً من ذلك؟

هناك العديد من المؤشرات التي يمكن أن يعودوا منها كل الجحيم
تحرر بالقوة.
يوم الأحد ، 4 مارس 2018 الساعة 3:56 صباحًا ، كتب مايك Hommey [email protected] :

تتعامل جميع الوظائف غير المكتوبة في سمة Alloc مع * mut u8.
مما يعني أنه يمكنهم أخذ أو إرجاع مؤشرات فارغة ، وكل شيء سيفعل
تحرر بالقوة. هل يجب عليهم استخدام NonNull بدلاً من ذلك؟

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/rust-lang/rust/issues/32838#issuecomment-370223269 ،
أو كتم الخيط
https://github.com/notifications/unsubscribe-auth/ABY2UR2dRxDtdACeRUh_djM-DExRuLxiks5ta9aFgaJpZM4IDYUN
.

السبب الأكثر إقناعًا لاستخدام NonNull هو أنه سيسمح بـ Result s المُعاد حاليًا من طرق Alloc (أو Options ، إذا قمنا بالتبديل إلى ذلك في المستقبل) لتكون أصغر.

السبب الأكثر إقناعًا لاستخدام NonNull هو أنه سيسمح للنتائج التي يتم إرجاعها حاليًا من طرق التخصيص (أو الخيارات ، إذا انتقلنا إلى ذلك في المستقبل) لتكون أصغر.

لا أعتقد أن ذلك سيكون لأن AllocErr له متغيران.

هناك العديد من المؤشرات التي يمكن أن يعودوا منها والتي ينهار منها كل الجحيم.

لكن من الواضح أن المؤشر الفارغ خاطئ أكثر من أي مؤشر آخر.

أحب أن أعتقد أن نظام نوع الصدأ يساعد في استخدام مسدسات القدم ، ويستخدم لتشفير الثوابت. توضح وثائق alloc "إذا كانت هذه الطريقة ترجع Ok(addr) ، فإن العنوان الذي تم إرجاعه سيكون عنوانًا غير خالي" ، ولكن نوع الإرجاع لا يكون كذلك. كما هي الأمور ، سيكون Ok(malloc(layout.size())) تطبيقًا صالحًا ، بينما من الواضح أنه ليس كذلك.

ملاحظة ، هناك أيضًا ملاحظات حول حجم Layout يحتاج إلى أن يكون غير صفري ، لذلك سأجادل أيضًا أنه يجب ترميز ذلك على أنه NonZero.

ليس لأن كل هذه الوظائف غير آمنة بطبيعتها فلا ينبغي أن يكون لدينا بعض الوقاية من استخدام مسدس القدم.

من بين جميع الأخطاء المحتملة في استخدام (تحرير: وتنفيذ) المخصصات ، يعد تمرير مؤشر فارغ واحدًا من أسهل طرق التعقب (تحصل دائمًا على خطأ بسيط عند إلغاء المرجع ، على الأقل إذا كان لديك MMU ولم تفعل أشياء غريبة جدًا معها) ، وعادة ما تكون واحدة من أكثر الأشياء تافهة لإصلاحها أيضًا. صحيح أن الواجهات غير الآمنة يمكن أن تحاول منع المسدسات ، لكن يبدو أن مسدس القدم هذا صغير بشكل غير متناسب (مقارنة بالأخطاء المحتملة الأخرى ، وإسهاب ترميز هذا الثابت في نظام النوع).

علاوة على ذلك ، يبدو من المحتمل أن تطبيقات المخصص ستستخدم فقط المُنشئ غير المحدد NonNull "للأداء": نظرًا لأنه في المُخصص الصحيح لن يُرجع قيمة فارغة على أي حال ، فقد يرغب في تخطي NonNell::new(...).unwrap() . في هذه الحالة لن تحصل في الواقع على أي وقاية ملموسة من مسدس القدم ، فقط المزيد من الحماية. (قد لا تزال فوائد الحجم Result ، إذا كانت حقيقية ، سببًا مقنعًا لذلك.)

قد تستخدم تطبيقات المخصص فقط المُنشئ غير المحدد لـ NonNull

الهدف أقل من مساعدة المخصص في التنفيذ من مساعدة المستخدمين. إذا كان MyVec يحتوي على NonNull<T> و Heap.alloc() بالفعل بإرجاع NonNull ، تلك المكالمة الأقل فحصًا أو غير الآمنة التي أحتاج إلى إجرائها.

لاحظ أن المؤشرات ليست فقط أنواع إرجاع ، بل هي أيضًا أنواع إدخال مثل dealloc و realloc . هل من المفترض أن تكون هذه الوظائف قاسية ضد أن تكون مدخلاتها لاغية أم لا؟ تميل الوثائق إلى قول لا ، لكن نظام الكتابة يميل إلى قول نعم.

تمامًا بالمثل مع layout.size (). هل من المفترض أن تتعامل وظائف التخصيص مع الحجم المطلوب وهو 0 بطريقة ما أم لا؟

(قد تكون فوائد حجم النتيجة ، إذا كانت حقيقية ، سببًا مقنعًا لذلك.)

أشك في أن هناك فوائد للحجم ، ولكن بشيء مثل # 48741 ، ستكون هناك فوائد codegen.

إذا واصلنا مبدأ كوننا أكثر مرونة لمستخدمي API ، فيجب أن تكون المؤشرات NonNull في أنواع الإرجاع ولكن ليس في الوسائط. (هذا لا يعني أنه يجب التحقق من هذه الوسيطات فارغة في وقت التشغيل.)

أعتقد أن نهج قانون Postel هو النهج الخطأ الذي يجب اتباعه هنا. هل هنالك أي
الحالة التي يكون فيها تمرير مؤشر فارغ إلى طريقة Alloc صحيحًا؟ ان لم،
إذن هذه المرونة هي في الأساس مجرد إعطاء مسدس القدم أكثر قليلاً
الزناد الحساس.

في 5 مارس 2018 الساعة 8:00 صباحًا ، كتب "Simon Sapin" [email protected] :

إذا واصلنا مبدأ كوننا أكثر مرونة لمستخدمي واجهة برمجة التطبيقات ،
يجب أن تكون المؤشرات NonNull في أنواع الإرجاع ولكن ليس في الوسائط. (هذه
لا يعني أنه يجب التحقق من صحة هذه الوسائط في وقت التشغيل.)

-
أنت تتلقى هذا لأنك مشترك في هذا الموضوع.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/rust-lang/rust/issues/32838#issuecomment-370327018 ،
أو كتم الخيط
https://github.com/notifications/unsubscribe-auth/AA_2L8zrOLyUv5mUc_kiiXOAn1f60k9Uks5tbOJ0gaJpZM4IDYUN
.

الهدف أقل من مساعدة المخصص في التنفيذ من مساعدة المستخدمين. إذا احتوى MyVec على NonNullو Heap.alloc () يقوم بالفعل بإرجاع NonNull ، تلك المكالمة الأقل فحصًا أو غير الآمنة التي يجب إجراؤها.

آه هذا منطقي. لا يصلح مسدس القدم ، لكنه يركز المسؤولية عنه.

لاحظ أن المؤشرات ليست فقط أنواع إرجاع ، بل هي أيضًا أنواع مدخلات مثل إلغاء التخصيص وإعادة التخصيص. هل من المفترض أن تكون هذه الوظائف قاسية ضد أن تكون مدخلاتها لاغية أم لا؟ تميل الوثائق إلى قول لا ، لكن نظام الكتابة يميل إلى قول نعم.

هل هناك أي حالة يكون فيها تمرير مؤشر فارغ إلى طريقة Alloc صحيحًا؟ إذا لم يكن الأمر كذلك ، فإن هذه المرونة هي في الأساس تعطي المسدس زنادًا أكثر حساسية قليلاً.

المستخدم لديه على الاطلاق لقراءة الوثائق والحفاظ على الثوابت في العقل. لا يمكن فرض العديد من الثوابت عبر نظام الكتابة على الإطلاق - إذا أمكن ذلك ، فلن تكون الوظيفة غير آمنة في البداية. لذا فإن هذا مجرد سؤال عما إذا كان وضع NonNull في أي واجهة سيساعد المستخدمين بالفعل

  • تذكيرهم بقراءة المستندات والتفكير في الثوابت
  • تقديم الراحة ( قيمة إرجاع تخصيص نقطة wrt الخاصة بـ SimonSapin)
  • إعطاء بعض المزايا المادية (على سبيل المثال ، تحسينات التخطيط)

لا أرى أي ميزة قوية في صنع ، على سبيل المثال ، وسيطة dealloc إلى NonNull . أرى فئتين تقريبًا من استخدامات واجهة برمجة التطبيقات هذه:

  1. استخدام تافه نسبيًا ، حيث تقوم باستدعاء alloc ، قم بتخزين المؤشر المرتجع في مكان ما ، وبعد فترة قم بتمرير المؤشر المخزن إلى dealloc .
  2. سيناريوهات معقدة تتضمن FFI ، والكثير من العمليات الحسابية للمؤشر ، وما إلى ذلك ، حيث يوجد منطق مهم لضمان تمرير الشيء الصحيح إلى dealloc في النهاية.

إن أخذ NonNull هنا بشكل أساسي يساعد فقط النوع الأول من حالة الاستخدام ، لأن هؤلاء سيخزنون NonNull في مكان ما لطيف ويمررونه إلى NonNull تغيير. من الناحية النظرية ، يمكن أن يمنع بعض الأخطاء المطبعية (تجاوز foo عندما كنت تعني bar ) إذا كنت تتلاعب بمؤشرات متعددة وكان واحد منهم فقط هو NonNull ، لكن هذا لا يبدو شائع جدًا أو مهم. عيب استخدام dealloc لمؤشر خام (بافتراض أن alloc يعيد NonNull الذي أقنعني SimonSapin أنه يجب أن يحدث) هو أنه يتطلب as_ptr في مكالمة dealloc ، والتي من المحتمل أن تكون مزعجة ولكنها لا تؤثر على السلامة في كلتا الحالتين.

النوع الثاني من حالة الاستخدام لم يتم مساعدته لأنه من المحتمل أنه لا يمكنه الاستمرار في استخدام NonNull طوال العملية بأكملها ، لذلك سيتعين عليه إعادة إنشاء NonNull يدويًا من المؤشر الأولي الذي حصل عليه بأي وسيلة. كما ذكرت سابقًا ، من المحتمل أن يصبح هذا تأكيدًا غير محدد / unsafe غير محدد بدلاً من فحص وقت التشغيل الفعلي ، لذلك لا يتم منع أي مسدسات.

هذا لا يعني أنني أؤيد dealloc أخذ مؤشر الخام. أنا فقط لا أرى أي المزايا المزعومة لبنادق القدم. ربما يفوز اتساق الأنواع افتراضيًا.

أنا آسف ولكني قرأت هذا على أنه "لا يمكن فرض العديد من الثوابت عبر نظام الكتابة على الإطلاق ... لذلك دعونا لا نحاول حتى" لا تدع الكمال يكون عدو الخير!

أعتقد أن الأمر يتعلق أكثر بالمفاضلات بين الضمانات المقدمة من خلال NonNull وبيئة العمل المفقودة من الاضطرار إلى الانتقال ذهابًا وإيابًا بين NonNull والمؤشرات الأولية. ليس لدي رأي قوي بشكل خاص في كلتا الحالتين - لا يبدو أي جانب غير معقول.

cramertj نعم ، لكنني لا أشتري حقًا فرضية هذا النوع من الجدل. يقول الناس إن Alloc مخصص لحالات استخدام غامضة وخفية وغير آمنة إلى حد كبير. حسنًا ، في التعليمات البرمجية الغامضة التي يصعب قراءتها ، أود الحصول على أكبر قدر ممكن من الأمان - على وجه التحديد لأنهم نادرًا ما يتم لمسهم ، ومن المحتمل ألا يكون المؤلف الأصلي موجودًا. على العكس من ذلك ، إذا تمت قراءة الكود بعد سنوات ، فقم بربط علم النفس. إذا كان أي شيء ، فإنه يأتي بنتائج عكسية. يجب أن تسعى الشفرة إلى أن تكون واضحة للغاية حتى يتمكن القارئ غير المألوف من معرفة ما يجري على الأرض بشكل أفضل. ضوضاء أقل <ثوابت أوضح.

النوع الثاني من حالة الاستخدام لم يتم مساعدته لأنه من المحتمل أنه لا يمكنه الاستمرار في استخدام NonNull طوال العملية بأكملها ، لذلك سيتعين عليه إعادة إنشاء NonNull يدويًا من المؤشر الأولي الذي حصل عليه بأي وسيلة.

هذا ببساطة فشل في التنسيق ، وليس حتمية فنية. بالتأكيد ، في الوقت الحالي ، قد تستخدم العديد من واجهات برمجة التطبيقات غير الآمنة مؤشرات أولية. لذلك يجب أن يقود شيء ما طريق التحول إلى واجهة فائقة باستخدام NonNull أو أغلفة أخرى. ثم يمكن للكود الآخر أن يحذو حذوه بسهولة أكبر. لا أرى سببًا للرجوع باستمرار إلى المؤشرات الأولية التي يصعب قراءتها وغير المليئة بالمعلومات في التعليمات البرمجية غير الآمنة وغير الآمنة.

مرحبا!

أريد فقط أن أقول أنه ، بصفتي مؤلف / مشرف مخصص لمخصص Rust المخصص ، فأنا أؤيد NonNull . إلى حد كبير لجميع الأسباب التي تم وضعها بالفعل في هذا الموضوع.

أيضًا ، أود أن أشير إلى أن glandium هو

يمكنني أن أكتب المزيد من الردود على @ Ericson2314 وآخرين ، لكنه سرعان ما أصبح نقاشًا فلسفيًا منفصلاً للغاية ، لذلك أقوم السلامة NonNull في هذا النوع من API (هناك فوائد أخرى ، بالطبع). هذا لا يعني أنه لا توجد فوائد للسلامة ، ولكن كما قال cramertj ، هناك مقايضات وأعتقد أن الجانب "المؤيد" مبالغ فيه. بغض النظر ، لقد قلت بالفعل إنني أميل إلى استخدام NonNull في أماكن مختلفة لأسباب أخرى - لهذا السبب أعطت SimonSapin alloc ، في dealloc للاتساق. لذلك دعونا نفعل ذلك ولا نذهب إلى المزيد من الظل.

إذا كان هناك عدد قليل من حالات الاستخدام NonNull يشاركها الجميع ، فهذه بداية رائعة.

ربما نرغب في تحديث Unique والأصدقاء لاستخدام NonNull بدلاً من NonZero للإبقاء على الاحتكاك على الأقل ضمن liballoc منخفضًا. لكن هذا يبدو كشيء نقوم به بالفعل على أي حال على مستوى تجريد واحد فوق المخصص ، ولا يمكنني التفكير في أي أسباب لعدم القيام بذلك على مستوى المخصص أيضًا. يبدو أنه تغيير معقول بالنسبة لي.

(يوجد بالفعل تطبيقان للسمة From تحويلهما بأمان بين Unique<T> و NonNull<T> .)

بالنظر إلى أنني بحاجة إلى شيء يشبه إلى حد كبير واجهة برمجة تطبيقات المخصص في صدأ مستقر ، فقد استخرجت الكود من ريبو الصدأ ووضعته في صندوق منفصل:

هذا / يمكن / يمكن استخدامه للتكرار على التغييرات التجريبية لواجهة برمجة التطبيقات ، ولكن اعتبارًا من الآن ، إنها نسخة بسيطة لما هو موجود في ريبو الصدأ.

[خارج الموضوع] حسنًا ، سيكون أمرًا رائعًا أن يستخدم std من صناديق الكود المستقرة مثل هذه ، حتى نتمكن من تجربة واجهات غير مستقرة في كود ثابت. هذا أحد الأسباب التي تجعلني أحب الحصول على واجهة std .

يمكن أن يعتمد std على نسخة صندوق من الصناديق. لا أرى كيف سيساعد ذلك. على أي حال ، بغض النظر عن الواجهة ، فإن جعل الميزات غير المستقرة غير متوفرة على القناة الثابتة هو اختيار متعمد للغاية وليس مصادفة.

يبدو أن لدينا بعض الاتفاق مكتوبًا باستخدام NonNull. ما السبيل إلى الأمام ليحدث هذا بالفعل؟ مجرد علاقات عامة تفعل ذلك؟ RFC؟

بشكل غير مرتبط ، كنت أبحث في بعض التجميعات التي تم إنشاؤها من أشياء Boxing ، ومسارات الخطأ كبيرة نوعًا ما. هل يجب عمل شيء حيال ذلك؟

أمثلة:

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

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

يجمع إلى:

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

هذه كمية كبيرة من التعليمات البرمجية لإضافتها إلى أي مكان لإنشاء مربعات. قارن مع 1.19 ، والذي لم يكن به واجهة برمجة تطبيقات للمخصص:

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>

إذا كان هذا مهمًا بالفعل ، فهو أمر مزعج حقًا. ومع ذلك ، ربما تقوم LLVM بتحسين هذا الأمر للبرامج الأكبر؟

هناك 1439 مكالمة إلى __rust_oom في أحدث متصفح Firefox كل ليلة. لا يستخدم Firefox أداة تخصيص rust ، لذلك نحصل على مكالمات مباشرة إلى malloc / calloc ، متبوعًا بفحص فارغ يقفز إلى رمز إعداد oom ، والذي يكون عادةً اثنين من movq و lea ، وملء AllocErr والحصول على عنوانه لتمريره إلى __rust__oom . هذا هو أفضل سيناريو ، بشكل أساسي ، ولكن لا يزال هذا هو 20 بايت من كود الآلة لكل من movq و lea.

أنا أنظر إلى ripgrep ، هناك 85 ، وكلهم في وظائف _ZN61_$LT$alloc..heap..Heap$u20$as$u20$alloc..allocator..Alloc$GT$3oom17h53c76bda5 0c6b65aE.llvm.nnnnnnnnnnnnnnn متطابقة. كل منهم 16 بايت. هناك 685 استدعاءًا لوظائف الغلاف هذه ، يسبق معظمها رمز مشابه لما قمت بلصقه في https://github.com/rust-lang/rust/issues/32838#issuecomment -377097485.

كانت nox تبحث اليوم في تمكين mergefunc ، وأتساءل عما إذا كان هذا يحدث أي فرق هنا.

mergefunc لا يتخلص من وظائف _ZN61_$LT$alloc..heap..Heap$u20$as$u20$alloc..allocator..Alloc$GT$3oom17h53c76bda5 0c6b65aE.llvm.nnnnnnnnnnnnnnn المتطابقة المتعددة (تمت تجربتها مع -C passes=mergefunc في RUSTFLAGS ).

ولكن ما يحدث فرقًا كبيرًا هو LTO ، وهو ما يجعل Firefox يتصل مباشرة بـ malloc ، تاركًا إنشاء AllocErr إلى اليمين قبل استدعاء __rust_oom . هذا أيضًا يجعل إنشاء Layout غير ضروري قبل استدعاء المخصّص ، وتركه عند ملء AllocErr .

هذا يجعلني أعتقد أن وظائف التخصيص ، باستثناء __rust_oom يجب أن تكون مضمنة على الأرجح.

راجع للشغل ، بعد الاطلاع على الكود الذي تم إنشاؤه لـ Firefox ، أعتقد أنه سيكون من المرغوب فيه استخدام moz_xmalloc بدلاً من malloc . هذا غير ممكن بدون مزيج من سمات التخصيص والقدرة على استبدال مخصص الكومة العمومية ، ولكنه يجلب الحاجة المحتملة لنوع خطأ مخصص لسمة التخصيص: moz_xmalloc معصوم عن الخطأ ولا يعود أبدًا في حالة بالفشل. IOW ، يتعامل مع OOM نفسها ، ولن يحتاج رمز الصدأ إلى استدعاء __rust_oom في هذه الحالة. مما يجعل من المرغوب فيه أن تقوم وظائف المخصص بإرجاع ! اختياري بدلاً من AllocErr .

لقد ناقشنا جعل AllocErr بنية ذات حجم صفري ، مما قد يساعد هنا أيضًا. مع جعل المؤشر أيضًا NonNull ، يمكن أن تكون قيمة الإرجاع بأكملها بحجم المؤشر.

https://github.com/rust-lang/rust/pull/49669 يُجري عددًا من التغييرات على واجهات برمجة التطبيقات هذه ، بهدف تثبيت مجموعة فرعية تغطي الموزعين العالميين. مشكلة التعقب لهذه المجموعة الفرعية: https://github.com/rust-lang/rust/issues/49668. على وجه الخصوص ، تم تقديم سمة GlobalAlloc جديدة.

هل سيسمح لنا PR هذا بالقيام بأشياء مثل Vec::new_with_alloc(alloc) حيث alloc: Alloc قريبًا؟

alexreg لا

sfackler هم ، لماذا لا؟ ماذا نحتاج قبل أن نتمكن من فعل ذلك؟ لا أفهم حقًا وجهة نظر هذا العلاقات العامة بخلاف ذلك ، إلا إذا كان ذلك لمجرد تغيير المخصص العالمي.

تضمين التغريدة

لا أفهم حقًا وجهة نظر هذا العلاقات العامة بخلاف ذلك ، إلا إذا كان ذلك لمجرد تغيير المخصص العالمي.

أعتقد أنه لمجرد تغيير المخصص العالمي.

alexreg إذا كنت تقصد ثابتًا ، فهناك عدد من أسئلة التصميم التي لم يتم حلها والتي لسنا مستعدين لتحقيق الاستقرار. في Nightly ، يتم دعم هذا #[unstable] مقابل Vec لأي شخص يشعر بأنه يرغب في العمل على ذلك.

ونعم ، كما هو مذكور في العلاقات العامة ، فإن هدفه هو السماح بتغيير المخصص العام ، أو التخصيص (على سبيل المثال في نوع مجموعة مخصصة) دون فقدان Vec::with_capacity .

FWIW ، الصندوق allocator_api المذكور في https://github.com/rust-lang/rust/issues/32838#issuecomment -376793369 يحتوي على RawVec<T, A> و Box<T, A> على الربان فرع (لم يصدر بعد). أنا أفكر في الأمر كحاضنة لما يمكن أن تبدو عليه المجموعات العامة على نوع التخصيص (بالإضافة إلى حقيقة أنني بحاجة إلى نوع Box<T, A> لصدأ مستقر). لم أبدأ في نقل vec.rs لإضافة Vec<T, A> حتى الآن ، لكن العلاقات العامة مرحب بها. vec.rs كبير.

سألاحظ أن "مشكلات" أدوات التشفير المذكورة في https://github.com/rust-lang/rust/issues/32838#issuecomment -377097485 يجب أن تختفي مع التغييرات في # 49669.

الآن ، مع مزيد من التفكير في استخدام السمة Alloc للمساعدة في تنفيذ مخصص في الطبقات ، هناك شيئان أعتقد أنهما سيكونان مفيدان (على الأقل بالنسبة لي):

  • كما ذكرنا سابقًا ، القدرة اختياريًا على تحديد نوع AllocErr . قد يكون هذا مفيدًا لجعله ! ، أو الآن بعد أن أصبح AllocErr فارغًا ، لجعله ينقل بشكل اختياري معلومات أكثر من "فشل".
  • القدرة اختياريًا على تحديد نوع Layout . تخيل أن لديك طبقتان من المُخصصات: واحدة لتخصيصات الصفحات والأخرى للمناطق الأكبر. يمكن أن يعتمد الأخير على الأول ، ولكن إذا أخذ كلاهما نفس النوع Layout ، فحينئذٍ تحتاج كلتا الطبقتين إلى إجراء التحقق من الصحة الخاص بهما: في المستوى الأدنى ، يكون هذا الحجم والمحاذاة مضاعفًا لحجم الصفحة والمستوى الأعلى ، يتوافق هذا الحجم والمحاذاة مع متطلبات المناطق الأكبر. لكن هذه الشيكات زائدة عن الحاجة. باستخدام أنواع Layout المتخصصة ، يمكن تفويض التحقق إلى إنشاء Layout بدلاً من المخصص نفسه ، وستسمح التحويلات بين أنواع Layout بتخطي عمليات التحقق الزائدة.

cramertjSimonSapinglandium حسنا، شكرا لتوضيح. يمكنني فقط تقديم PR لبعض أنواع المجموعات الرئيسية الأخرى. هل من الأفضل القيام بذلك مقابل الريبو / الصندوق المخصص أو glandium أو

alexreg مع الأخذ في الاعتبار مقدار التغييرات التي تم إجراؤها على السمة Alloc في # 49669 ، فمن الأفضل الانتظار حتى يتم دمجها أولاً.

glandium عادل بما فيه الكفاية. لا يبدو ذلك بعيدًا https://github.com/pnkfelix/collections- الريبو الممتاز أيضًا ... ما هذا بالنسبة لك؟

أود إضافة سؤال مفتوح آخر:

  • هل يسمح Alloc::oom بالذعر؟ يقول المستندات حاليًا أن هذه الطريقة يجب أن تحبط العملية. هذا له آثار على الكود الذي يستخدم المخصصات حيث يجب تصميمها بعد ذلك للتعامل مع فك اللف بشكل صحيح دون تسرب الذاكرة.

أعتقد أننا يجب أن نسمح بالذعر لأن الفشل في المخصص المحلي لا يعني بالضرورة أن المخصص العالمي سيفشل كذلك. في أسوأ الحالات ، سيتم استدعاء المخصّص العالمي oom وهو ما سيؤدي إلى إجهاض العملية (وإلا سيؤدي ذلك إلى كسر الكود الحالي).

alexreg ليس كذلك. يبدو أنه نسخة عادية لما هو موجود في مجموعات / تخصيص /. حسنًا ، نسخة عمرها سنتان. الصندوق الخاص بي محدود النطاق بدرجة أكبر (الإصدار المنشور يحتوي فقط على السمة Alloc اعتبارًا من بضعة أسابيع مضت ، الفرع الرئيسي يحتوي فقط على RawVec و Box فوق ذلك) ، وأحد أهدافي هو الحفاظ على بنائه مع صدأ مستقر.

glandium حسنًا ، في هذه الحالة ربما يكون من المنطقي بالنسبة لي الانتظار حتى وصول العلاقات العامة ، ثم إنشاء

alexreg له معنى. يمكنك / يمكن / تبدأ العمل على ذلك الآن ، ولكن من المحتمل أن يؤدي ذلك إلى حدوث بعض التوتر في نهايتك إذا / عندما يغير ارتداء الدراجات الأشياء في هذا العلاقات العامة.

glandium ، لدي أشياء أخرى تجعلني مشغولاً مع Rust في الوقت الحالي ، لكنني

هل يسمح Alloc :: oom بالذعر؟ يقول المستندات حاليًا أن هذه الطريقة يجب أن تحبط العملية. هذا له آثار على الكود الذي يستخدم المخصصات حيث يجب تصميمها بعد ذلك للتعامل مع فك اللف بشكل صحيح دون تسرب الذاكرة.

Amanieu تم دمج RFC هذا: https://github.com/rust-lang/rfcs/pull/2116 ربما لم يتم تحديث المستندات والتنفيذ حتى الآن.

هناك تغيير واحد على واجهة برمجة التطبيقات أفكر في تقديم عرض عام له:

قسّم السمة Alloc جزأين: "التنفيذ" و "المساعدون". الأولى ستكون وظائف مثل alloc ، dealloc ، realloc ، وما إلى ذلك ، والأخيرة ، alloc_one ، dealloc_one ، alloc_array ، إلخ. في حين أن هناك بعض الفوائد الافتراضية من القدرة على تنفيذ مخصص لهذا الأخير ، إلا أنها بعيدة كل البعد عن الحاجة الأكثر شيوعًا ، وعندما تحتاج إلى تنفيذ أغلفة عامة (والتي وجدت أنها شائعة بشكل لا يصدق ، لدرجة أنني بدأت بالفعل في كتابة اشتقاق مخصص لذلك) ، ما زلت بحاجة إلى تنفيذها جميعًا لأن الغلاف ربما يقوم بتخصيصها.

OTOH ، إذا حاول منفذ السمات Alloc القيام بأشياء خيالية على سبيل المثال alloc_one ، فلن يضمنوا أنه سيتم استدعاء dealloc_one لهذا التخصيص. وهناك أسباب متعددة لذلك:

  • لا يتم استخدام المساعدين باستمرار. مثال واحد فقط ، يستخدم raw_vec مزيجًا من alloc_array و alloc / alloc_zeroed ، لكنه يستخدم فقط dealloc .
  • حتى مع الاستخدام المتسق مثل alloc_array / dealloc_array ، لا يزال بإمكان المرء تحويل Vec بأمان إلى Box ، والذي سيستخدم بعد ذلك dealloc .
  • ثم هناك بعض أجزاء واجهة برمجة التطبيقات غير الموجودة (لا توجد نسخة صفرية من alloc_one / alloc_array )

لذلك على الرغم من وجود حالات استخدام فعلية للتخصص على سبيل المثال alloc_one (وفي واقع الأمر ، لدي مثل هذه الحاجة إلى mozjemalloc) ، فمن الأفضل استخدام مُخصص متخصص بدلاً من ذلك.

في الواقع ، إنه أسوأ من ذلك ، في ريبو الصدأ ، هناك استخدام واحد بالضبط alloc_array ، ولا يوجد استخدام alloc_one ، dealloc_one ، realloc_array ، dealloc_array . لا تستخدم بنية الصندوق حتى alloc_one ، بل تستخدم exchange_malloc ، والتي تأخذ size و align . لذا فإن الغرض من هذه الوظائف هو توفير الراحة للعملاء أكثر من المنفذين.

بشيء مثل impl<A: Alloc> AllocHelpers for A (أو AllocExt ، أيا كان الاسم المختار) ، لا يزال لدينا راحة من هذه الوظائف للعملاء ، مع عدم السماح للمنفذين بإطلاق النار على أنفسهم إذا اعتقدوا يقومون بأشياء خيالية من خلال تجاوزها (وتسهيل الأمر على الأشخاص الذين يطبقون مخصصات الوكيل).

هناك تغيير واحد على واجهة برمجة التطبيقات أفكر في تقديم عرض عام له

فعلت ذلك في # 50436

تضمين التغريدة

(وفي واقع الأمر ، لدي مثل هذه الحاجة إلى mozjemalloc) ،

هل يمكنك توضيح حالة الاستخدام هذه؟

mozjemalloc لديه مخصص قاعدة التي تتسرب عن قصد. باستثناء نوع واحد من الكائنات ، حيث يحتفظ بقائمة مجانية. يمكنني القيام بذلك عن طريق وضع طبقات مخصصة بدلاً من القيام بالحيل باستخدام alloc_one .

هل يلزم عدم التخصيص مع المحاذاة الدقيقة التي خصصتها لها؟

فقط للتأكيد على أن الإجابة على هذا السؤال هي نعم ، لدي هذا الاقتباس الرائع من Microsoft أنفسهم :

من المحتمل ألا يتم تنفيذ () align_alloc أبدًا ، كما حددته C11 بطريقة لا تتوافق مع تطبيقنا (أي أن () المجاني يجب أن يكون قادرًا على التعامل مع عمليات التخصيص شديدة التوافق)

سيتطلب استخدام مخصص النظام على Windows دائمًا معرفة المحاذاة عند إلغاء التخصيص من أجل إلغاء تخصيص التخصيصات شديدة التوافق بشكل صحيح ، لذا هل يمكننا فقط وضع علامة على هذا السؤال على أنه تم الحل؟

سيتطلب استخدام مخصص النظام على Windows دائمًا معرفة المحاذاة عند إلغاء التخصيص من أجل إلغاء تخصيص التخصيصات شديدة التوافق بشكل صحيح ، لذا هل يمكننا فقط وضع علامة على هذا السؤال على أنه تم الحل؟

إنه عار ، لكن هذا هو الحال. دعونا نتخلى عن المتجهات المتراكبة إذن. :مشوش:

دعونا نتخلى عن المتجهات المتراكبة إذن

كيف ذلك؟ أنت فقط بحاجة إلى Vec<T, OverAlignedAlloc<U16>> الذي يقوم بالتخصيص وإلغاء التخصيص مع المحاذاة الزائدة.

كيف ذلك؟ أنت فقط بحاجة إلى Vec<T, OverAlignedAlloc<U16>> الذي يقوم بالتخصيص وإلغاء التخصيص مع المحاذاة الزائدة.

كان يجب أن أكون أكثر تحديدا. قصدت نقل المتجهات المتجاورة إلى واجهة برمجة تطبيقات خارج نطاق سيطرتك ، على سبيل المثال ، التي تتطلب Vec<T> وليس Vec<T, OverAlignedAlloc<U16>> . (على سبيل المثال CString::new() .)

يجب عليك بالأحرى استخدام ملفات

#[repr(align(16))]
struct OverAligned16<T>(T);

ثم Vec<OverAligned16<T>> .

يجب عليك بالأحرى استخدام ملفات

هذا يعتمد على. لنفترض أنك تريد استخدام مضمنات AVX (عرض 256 بت ، متطلبات المحاذاة 32 بايت) على متجه f32 s:

  • Vec<T, OverAlignedAlloc<U32>> يحل المشكلة ، يمكن للمرء استخدام مضمن AVX مباشرة على عناصر المتجه (على وجه الخصوص ، أحمال الذاكرة المحاذاة) ، ولا يزال المتجه يتحول إلى شريحة &[f32] مما يجعله مريحًا للاستخدام.
  • Vec<OverAligned32<f32>> لا يحل المشكلة حقًا. يأخذ كل f32 32 بايت من المساحة بسبب متطلبات المحاذاة. تمنع الحشوة المقدمة الاستخدام المباشر لعمليات AVX نظرًا لأن f32 s لم تعد موجودة على الذاكرة المستمرة. وأنا شخصيا أجد deref إلى &[OverAligned32<f32>] مملة بعض الشيء للتعامل معها.

بالنسبة لعنصر واحد في Box ، Box<T, OverAligned<U32>> مقابل Box<OverAligned32<T>> ، كلا النهجين أكثر تكافؤًا ، وقد يكون الأسلوب الثاني هو الأفضل بالفعل. في أي حال من الجيد أن يكون لديك كلا الخيارين.

نشر هذه التغييرات الكتابية على سمة Alloc: https://internals.rust-lang.org/t/pre-rfc-changing-the-alloc-trait/7487

منشور التتبع الموجود في الجزء العلوي من هذا الإصدار قديم بشكل مروع (تم تحريره آخر مرة في عام 2016). نحن بحاجة إلى قائمة محدثة من الاهتمامات النشطة لمواصلة المناقشة بشكل مثمر.

ستستفيد المناقشة أيضًا بشكل كبير من وثيقة التصميم الحديثة ، التي تحتوي على الأسئلة الحالية التي لم يتم حلها ، والأساس المنطقي لقرارات التصميم.

هناك عدة سلاسل من الاختلافات من "ما يتم تنفيذه حاليًا ليلاً" إلى "ما تم اقتراحه في Alloc RFC الأصلي" ينتج عنه آلاف التعليقات على قنوات مختلفة (rfc repo ، مشكلة تتبع rust-lang ، تخصيص عالمي RFC ، منشورات داخلية ، العديد العلاقات العامة الضخمة ، وما إلى ذلك) ، وما يتم استقراره في RFC GlobalAlloc لا يبدو كثيرًا مما تم اقتراحه في RFC الأصلي.

هذا شيء نحتاجه على أي حال لإنهاء تحديث المستندات والمرجع ، وسيكون مفيدًا في المناقشات الحالية أيضًا.

أعتقد أنه قبل أن نفكر حتى في تثبيت سمة Alloc ، يجب أن نحاول أولاً تنفيذ دعم المخصص في جميع مجموعات المكتبة القياسية. يجب أن يمنحنا هذا بعض الخبرة في كيفية استخدام هذه السمة في الممارسة العملية.

أعتقد أنه قبل أن نفكر حتى في تثبيت سمة Alloc ، يجب أن نحاول أولاً تنفيذ دعم المخصص في جميع مجموعات المكتبة القياسية. يجب أن يمنحنا هذا بعض الخبرة في كيفية استخدام هذه السمة في الممارسة العملية.

نعم بالتاكيد. خاصةً Box ، لأننا لا نعرف حتى الآن كيفية تجنب استخدام Box<T, A> لكلمتين.

نعم بالتاكيد. خاصة Box ، لأننا لا نعرف حتى الآن كيفية تجنب استخدام Boxتناول كلمتين.

لا أعتقد أننا يجب أن نقلق بحجم Box<T, A> للتنفيذ الأولي ، ولكن هذا شيء يمكن إضافته لاحقًا بطريقة متوافقة مع الإصدارات السابقة عن طريق إضافة سمة DeAlloc التي تدعم فقط إلغاء التخصيص.

مثال:

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);
    }
}

أعتقد أنه قبل أن نفكر حتى في تثبيت سمة Alloc ، يجب أن نحاول أولاً تنفيذ دعم المخصص في جميع مجموعات المكتبة القياسية. يجب أن يمنحنا هذا بعض الخبرة في كيفية استخدام هذه السمة في الممارسة العملية.

أعتقد أن @ Ericson2314 يعمل على هذا ، عبر https://github.com/rust-lang/rust/issues/42774. سيكون من الجيد الحصول على تحديث منه.

لا أعتقد أننا يجب أن نقلق بحجم Box<T, A> للتنفيذ الأولي ، ولكن هذا شيء يمكن إضافته لاحقًا بطريقة متوافقة مع الإصدارات السابقة عن طريق إضافة سمة DeAlloc التي تدعم فقط إلغاء التخصيص.

هذا نهج واحد ، لكن ليس من الواضح على الإطلاق أنه الأفضل بالتأكيد. لها عيوب مميزة ، على سبيل المثال ، أ) تعمل فقط عندما يكون المؤشر -> البحث عن المُخصص ممكنًا (هذا ليس صحيحًا ، على سبيل المثال ، معظم مخصصات الساحة) و ، ب) يضيف مصروفات عامة كبيرة إلى dealloc (أي لإجراء البحث العكسي). قد ينتهي الأمر بأن أفضل حل لهذه المشكلة هو تأثير أكثر عمومية أو نظام سياق مثل هذا الاقتراح أو هذا الاقتراح . أو ربما شيء مختلف تمامًا. لذلك لا أعتقد أننا يجب أن نفترض أنه سيكون من السهل حل هذا بطريقة تتوافق مع الإصدارات السابقة مع التجسد الحالي للسمة Alloc .

joshlf بالنظر إلى حقيقة أن Box<T, A> لديه حق الوصول إلى نفسه فقط عندما يتم إسقاطه ، فهذا هو أفضل شيء يمكننا القيام به باستخدام الكود الآمن فقط. قد يكون مثل هذا النمط مفيدًا للمخصصات التي تشبه الحلبة والتي لديها no-op dealloc وذاكرة خالية فقط عند إسقاط المُخصص.

بالنسبة للأنظمة الأكثر تعقيدًا حيث يكون المُخصص مملوكًا بواسطة حاوية (على سبيل المثال ، LinkedList ) وإدارة تخصيصات متعددة ، أتوقع أن Box لن يتم استخدامه داخليًا. بدلا من ذلك، LinkedList سوف الداخلية استخدام المؤشرات الأولية التي يتم تخصيصها وأطلق سراحه مع Alloc المثال موجود في LinkedList الكائن. سيؤدي ذلك إلى تجنب مضاعفة حجم كل مؤشر.

بالنظر إلى حقيقة أن Box<T, A> لديه حق الوصول إلى نفسه فقط عندما يتم إسقاطه ، فهذا أفضل شيء يمكننا القيام به باستخدام الكود الآمن فقط. قد يكون مثل هذا النمط مفيدًا للمخصصات التي تشبه الحلبة والتي لديها no-op dealloc وذاكرة خالية فقط عند إسقاط المُخصص.

صحيح ، لكن Box لا يعرف أن dealloc غير متوفر.

بالنسبة للأنظمة الأكثر تعقيدًا حيث يكون المُخصص مملوكًا بواسطة حاوية (على سبيل المثال LinkedList ) وإدارة تخصيصات متعددة ، أتوقع أن Box لن يتم استخدامه داخليًا. بدلا من ذلك، LinkedList سوف الداخلية استخدام المؤشرات الأولية التي يتم تخصيصها وأطلق سراحه مع Alloc المثال موجود في LinkedList الكائن. سيؤدي ذلك إلى تجنب مضاعفة حجم كل مؤشر.

أعتقد أنه سيكون من العار حقًا مطالبة الأشخاص باستخدام رمز غير آمن لكتابة أي مجموعات على الإطلاق. إذا كان الهدف هو إنشاء جميع المجموعات (من المفترض أن تتضمن تلك الموجودة خارج المكتبة القياسية) اختياريًا حدوديًا على المُخصص ، و Box ليس معلمات تخصيص ، فيجب على مؤلف المجموعات إما ألا يستخدم Box على الإطلاق أو استخدام رمز غير آمن (ضع في اعتبارك أن تذكر تحرير الأشياء دائمًا هو أحد أكثر أنواع عدم أمان الذاكرة شيوعًا في C و C ++ ، لذلك من الصعب الحصول على رمز غير آمن بشكل صحيح) هذا يبدو وكأنه صفقة مؤسفة.

صحيح ، لكن Box لا يعرف أن dealloc ليست عملية No-op.

لماذا لا تتكيف مع ما يفعله C ++ unique_ptr ؟
وهذا يعني: تخزين المؤشر للمخصص إذا كان "ذو حالة" ، وعدم تخزينه إذا كان المخصص "بدون الحالة"
(على سبيل المثال ، غلاف عام حول malloc أو mmap ).
سيتطلب ذلك تقسيم المسار الحالي Alloc إلى سمتين: StatefulAlloc و StatelessAlloc .
أدرك أنه وقح للغاية وغير أنيق (وربما اقترحه شخص ما بالفعل في المناقشات السابقة).
على الرغم من عدم كون هذا الحل بسيطًا ومتوافقًا مع الإصدارات السابقة (بدون عقوبات الأداء).

أعتقد أنه سيكون من العار حقًا مطالبة الأشخاص باستخدام رمز غير آمن لكتابة أي مجموعات على الإطلاق. إذا كان الهدف هو جعل جميع المجموعات (بما في ذلك على الأرجح تلك الموجودة خارج المكتبة القياسية) اختيارية حدوديًا على المُخصص ، ولم يكن Box محددًا ، فيجب على مؤلف المجموعات إما عدم استخدام Box على الإطلاق أو استخدام رمز غير آمن (و ضع في اعتبارك أن تذكر تحرير الأشياء دائمًا هو أحد أكثر أنواع عدم أمان الذاكرة شيوعًا في C و C ++ ، لذلك من الصعب الحصول على رمز غير آمن بشكل صحيح في ذلك). هذا يبدو وكأنه صفقة مؤسفة.

أخشى أن تنفيذ نظام التأثير أو السياق الذي قد يسمح لكتابة حاويات قائمة على العقد مثل القوائم والأشجار وما إلى ذلك بطريقة آمنة قد يستغرق وقتًا طويلاً (إذا كان ذلك ممكنًا من حيث المبدأ)
لم أر أي أوراق أو لغات أكاديمية تعالج هذه المشكلة (من فضلك ، صححني إذا كانت هذه الأعمال موجودة بالفعل).

لذا فإن اللجوء إلى unsafe في تنفيذ الحاويات القائمة على العقد قد يكون شرًا ضروريًا ، على الأقل في المنظور قصير المدى.

eucpp لاحظ أن unique_ptr لا يخزن مخصصًا - إنه يخزن Deleter :

يجب أن يكون المحذوف FunctionObject أو مرجع lvalue إلى مرجع FunctionObject أو lvalue للدالة ، ويمكن استدعاؤه باستخدام وسيطة من النوع unique_ptr:: المؤشر`

أرى أن هذا يكافئ تقريبًا تقديم سمات مقسمة Alloc و Dealloc .

cramertj نعم ، أنت على حق. ومع ذلك ، هناك سمتان مطلوبتان - Dealloc .

ألن يكون Dealloc ZST كافيًا؟

يوم الثلاثاء 12 يونيو 2018 الساعة 3:08 مساءً Evgeniy Moiseenko [email protected]
كتب:

cramertj https://github.com/cramertj نعم ، أنت محق. لا يزال اثنان
السمات مطلوبة - Dealloc ذات الحالة وعديمة الجنسية.

-
أنت تتلقى هذا لأنه تم ذكرك.
قم بالرد على هذا البريد الإلكتروني مباشرة ، وقم بعرضه على GitHub
https://github.com/rust-lang/rust/issues/32838#issuecomment-396716689 ،
أو كتم الخيط
https://github.com/notifications/unsubscribe-auth/AEAJtWkpF0ofVc18NwbfV45G4QY6SCFBks5t8B_AgaJpZM4IDYUN
.

ألن يكون Dealloc ZST كافيًا؟

Remexre أعتقد أنه سيكون :)

لم أكن أعرف أن مترجم الصدأ يدعم ZST خارج الصندوق.
في C ++ ، سيتطلب بعض الحيل على الأقل حول تحسين القاعدة الفارغة.
أنا جديد جدًا في Rust ، آسف جدًا لبعض الأخطاء الواضحة.

لا أعتقد أننا بحاجة إلى سمات منفصلة لكل من صاحب الحالة مقابل عديم الجنسية.

مع Box زيادتها مع A نوع معلمة، فإنه يحتوي على قيمة A مباشرة، وليس مرجع أو المؤشر إلى A . يمكن أن يكون هذا النوع بحجم صفري لمخصص عديم الحالة (de). أو يمكن أن يكون A نفسه شيئًا مثل مرجع أو معالج لمخصص ذي حالة يمكن مشاركته بين كائنات مخصصة متعددة. لذا بدلاً من impl Alloc for MyAllocator ، قد ترغب في القيام بشيء مثل impl<'r> Alloc for &'r MyAllocator

بالمناسبة ، فإن Box الذي يعرف فقط كيفية إلغاء التخصيص وليس كيفية التخصيص لن يطبق Clone .

SimonSapin أتوقع أن Clone ing سيتطلب تحديد مُخصص مرة أخرى ، بنفس الطريقة التي يتم بها إنشاء Box (أي ، لن يتم ذلك باستخدام Clone سمة).

cramertj ألن يكون غير متسق مقارنة بـ Vec والحاويات الأخرى التي تستخدم Clone ؟
ما هي سلبيات تخزين مثيل Alloc داخل Box بدلاً من Dealloc ؟
ثم Box قد ينفذ Clone بالإضافة إلى clone_with_alloc .

لا أفعل هذا ، فإن السمات المنقسمة تؤثر حقًا على Clone بطريقة ضخمة - سيبدو الضمني فقط impl<T, A> Clone for Box<T, A> where A: Alloc + Dealloc + Clone { ... } .

sfackler لن أعارض ذلك الضمني ، لكنني أتوقع أيضًا أن يكون لدي clone_into أو شيء يستخدم مخصصًا مقدمًا .

هل يعقل استخدام طريقة alloc_copy إلى Alloc ؟ يمكن استخدام هذا لتوفير تطبيقات memcpy أسرع ( Copy/Clone ) لعمليات التخصيص الكبيرة ، على سبيل المثال عن طريق نسخ الصفحات عند الكتابة.

سيكون ذلك رائعًا وتافهًا لتوفير تطبيق افتراضي لـ.

ما الذي قد يستخدمه مثل هذه الوظيفة alloc_copy ؟ impl Clone for Box<T, A> ؟

نعم ، كما سبق مقابل Vec .

بعد النظر في الأمر أكثر ، يبدو أنه طرق لإنشاء صفحات نسخ عند الكتابة ضمن نطاق العملية نفسه بين الاختراق والمستحيل ، على الأقل إذا كنت تريد القيام بذلك بأكثر من مستوى واحد. لذلك لن يكون alloc_copy فائدة كبيرة.

وبدلاً من ذلك ، قد تكون فتحة الهروب الأكثر عمومية والتي تتيح خدع الذاكرة الافتراضية المستقبلية مفيدة بعض الشيء. أي إذا كان التخصيص كبيرًا ، مدعومًا بـ mmap على أي حال وعديم الحالة ، فيمكن للمخصص أن يعد غافلاً عن التغييرات المستقبلية للتخصيص. يمكن للمستخدم بعد ذلك نقل تلك الذاكرة إلى أنبوب أو إلغاء تعيينها أو أشياء مماثلة.
بدلاً من ذلك ، يمكن أن يكون هناك مخصص غبي لكل الأشياء ووظيفة نقل المحاولة.

بدلاً من ذلك ، فتحة هروب أكثر عمومية تسمح للذاكرة الافتراضية المستقبلية

لا تسمح لك أدوات تخصيص الذاكرة (malloc ، jemalloc ، ...) عمومًا بسرقة أي نوع من الذاكرة منها ، ولا تسمح لك عمومًا بالاستعلام عن خصائص الذاكرة التي تمتلكها أو تغييرها. إذن ما علاقة فتحة الهروب العامة هذه بمخصصات الذاكرة؟

أيضًا ، يختلف دعم الذاكرة الظاهرية اختلافًا كبيرًا بين الأنظمة الأساسية ، لدرجة أن استخدام الذاكرة الافتراضية بشكل فعال غالبًا ما يتطلب خوارزميات مختلفة لكل نظام أساسي في كثير من الأحيان مع ضمانات مختلفة تمامًا. لقد رأيت بعض الأفكار التجريدية المحمولة على الذاكرة الافتراضية ، لكنني لم أر حتى الآن واحدة لم يتم شلها إلى درجة كونها عديمة الفائدة في بعض المواقف بسبب "قابليتها للنقل".

أنت على حق. من المحتمل أن يتم تقديم أي حالة استخدام (كنت أفكر في الغالب في تحسينات خاصة بالمنصة) باستخدام مخصص مخصص في المقام الأول.

أي أفكار حول Composable Allocator API التي وصفها Andrei Alexandrescu في عرض CppCon؟ الفيديو متاح على YouTube هنا: https://www.youtube.com/watch؟v=LIb3L4vKZ7U (يبدأ في وصف تصميمه المقترح حوالي الساعة 26:00 ، لكن الحديث ممتع بما يكفي وقد تفضل مشاهدته من خلاله) .

يبدو أن الاستنتاج الحتمي لكل هذا هو أن مكتبات المجموعات يجب أن تكون تخصيصًا عامًا لـ WRT ، ويجب أن يكون مبرمج التطبيق نفسه قادرًا على تكوين المُخصصات والمجموعات بحرية في موقع البناء.

أي أفكار حول Composable Allocator API التي وصفها Andrei Alexandrescu في عرض CppCon؟

تتيح واجهة برمجة التطبيقات الحالية Alloc كتابة أدوات تخصيص قابلة للتركيب (على سبيل المثال MyAlloc<Other: Alloc> ) ويمكنك استخدام السمات والتخصص لتحقيق كل ما تم تحقيقه في حديث Andreis. ومع ذلك ، بعيدًا عن "الفكرة" القائلة بأنه يجب أن يكون المرء قادرًا على القيام بذلك ، لا يمكن تطبيق أي شيء تقريبًا من حديث Andrei على Rust نظرًا لأن الطريقة التي يبني بها Andrei واجهة برمجة التطبيقات تعتمد على الأدوية الجنيسة غير المقيدة + SFINAE / static إذا كان من البداية ونظام Rust's genics يختلف تمامًا عن ذلك.

أود أن أقترح تثبيت باقي طرق Layout . هذه مفيدة بالفعل مع API المخصص الحالي.

هل هذه كل الطرق التي تقصدها؟

  • 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>

gnzlbg نعم.

Amanieu يبدو

من المخصصات والعمر :

  1. (لتطبيقات التخصيص): يجب ألا يؤدي نقل قيمة المخصص إلى إبطال كتل الذاكرة المعلقة.

    يمكن لجميع العملاء افتراض هذا في التعليمات البرمجية الخاصة بهم.

    لذلك إذا قام العميل بتخصيص كتلة من المُخصص (أطلق عليه a1) ثم انتقل a1 إلى مكان جديد (على سبيل المثال vialet a2 = a1 ؛) ، فسيظل من الجيد للعميل إلغاء تخصيص تلك الكتلة عبر a2.

هل هذا يعني أن المخصص يجب أن يكون Unpin ؟

مسكة جيدة!

نظرًا لأن السمة Alloc لا تزال غير مستقرة ، أعتقد أنه لا يزال يتعين علينا تغيير القواعد إذا أردنا تعديل هذا الجزء من RFC. لكنه في الواقع شيء يجب أخذه في الاعتبار.

gnzlbg نعم ، أنا على دراية بالاختلافات الهائلة في أنظمة الأدوية ، وأنه ليس كل ما

هل هذا يعني أن المخصص _ يجب أن يكون Unpin ؟

لا. Unpin يتعلق بسلوك النوع عندما يتم تغليفه في Pin ، لا يوجد اتصال محدد بواجهة برمجة التطبيقات هذه.

لكن ألا يمكن استخدام Unpin لفرض القيد المذكور؟

سؤال آخر بخصوص dealloc_array : لماذا ترجع الدالة Result ؟ في التطبيق الحالي ، قد يفشل هذا في حالتين:

  • n هو صفر
  • تجاوز السعة لـ n * size_of::<T>()

في الحالة الأولى لدينا حالتان (كما في الوثائق ، يمكن للمنفذ الاختيار بين هاتين الحالتين):

  • إرجاع التخصيص Ok على n => dealloc_array يجب أيضًا إرجاع Ok .
  • يُرجع التخصيص Err على n => لا يوجد مؤشر يمكن تمريره إلى dealloc_array .

والثاني مكفول بقيد الأمان التالي:

يجب أن يتلاءم تخطيط [T; n] كتلة الذاكرة هذه.

هذا يعني أنه يجب علينا استدعاء dealloc_array بنفس n كما في التخصيص. إذا كان من الممكن تخصيص مصفوفة تحتوي على عناصر n ، فإن n صالح لـ T . وإلا ، فإن التخصيص قد فشل.

تحرير: فيما يتعلق بالنقطة الأخيرة: حتى إذا أعاد usable_size قيمة أعلى من n * size_of::<T>() ، فلا يزال هذا صالحًا. وإلا فإن التنفيذ ينتهك قيد السمات هذا:

يجب أن يقع حجم الكتلة في النطاق [use_min, use_max] ، حيث:

  • [...]
  • use_max هي السعة التي تم إرجاعها (أو كان يمكن إرجاعها) عندما (إذا) تم تخصيص الكتلة عبر مكالمة إلى alloc_excess أو realloc_excess .

هذا صحيح فقط ، حيث تتطلب السمة unsafe impl .

في الحالة الأولى لدينا حالتان (كما في الوثائق ، يمكن للمنفذ الاختيار بين هاتين الحالتين):

  • يُرجع التخصيص Ok على n الصفر

من أين لك هذه المعلومات من؟

تحدد جميع طرق Alloc::alloc_ في المستندات أن سلوك التخصيصات ذات الحجم الصفري غير محدد بموجب بند "الأمان".

مستندات core::alloc::Alloc (الأجزاء ذات الصلة المميزة):

ملاحظة بخصوص الأنواع ذات الحجم الصفري والتخطيطات ذات الحجم الصفري: تشير العديد من الطرق في سمة Alloc أن طلبات التخصيص يجب أن تكون بحجم غير صفري ، وإلا فقد ينتج عن ذلك سلوك غير محدد.

  • ومع ذلك ، فإن بعض طرق التخصيص ذات المستوى الأعلى ( alloc_one ، alloc_array ) محددة جيدًا على الأنواع ذات الحجم الصفري ويمكن أن تدعمها اختياريًا : يترك الأمر للمنفذ إذا كان سيعيد Err ، أو إرجاع Ok ببعض المؤشر.
  • إذا اختار تطبيق Alloc إرجاع Ok في هذه الحالة (أي يشير المؤشر إلى كتلة لا يمكن الوصول إليها بحجم صفري) ، فيجب اعتبار ذلك المؤشر الذي تم إرجاعه "مخصصًا حاليًا". في مثل هذا المخصص ، يجب أن تقبل جميع الطرق التي تأخذ المؤشرات المخصصة حاليًا كمدخلات هذه المؤشرات ذات الحجم الصفري ، دون التسبب في سلوك غير محدد.

  • بعبارة أخرى ، إذا كان مؤشر الحجم الصفري يمكن أن يتدفق من المخصِّص ، فيجب أن يقبل ذلك المُخصِّص بالمثل ذلك المؤشر الذي يتدفق عائدًا إلى أساليب إلغاء التخصيص وإعادة التخصيص .

لذا فإن أحد شروط الخطأ dealloc_array مريب بالتأكيد:

/// # 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.

إذا كان [T; N] لا يفي بحجم المخصص أو قيود المحاذاة ، فإن AFAICT لا يتناسب مع كتلة ذاكرة التخصيص ، ويكون السلوك غير محدد (وفقًا لشرط الأمان).

حالة الخطأ الأخرى هي "إرجاع Err دومًا عند تجاوز السعة الحسابية." وهو عام جدًا. من الصعب معرفة ما إذا كانت حالة خطأ مفيدة. لكل Alloc تنفيذ سمة يمكن للمرء أن يبتكر تطبيقًا مختلفًا يمكنه إجراء بعض العمليات الحسابية التي يمكن أن تلتف نظريًا ، لذا 🤷‍♂️


مستندات core::alloc::Alloc (الأجزاء ذات الصلة المميزة):

في الواقع. أجد أنه من الغريب أن العديد من الطرق (على سبيل المثال Alloc::alloc ) تنص على أن التخصيصات ذات الحجم الصفري هي سلوك غير محدد ، ولكن بعد ذلك نقدم فقط Alloc::alloc_array(0) بسلوك محدد من قبل التنفيذ. بمعنى ما ، يعد Alloc::alloc_array(0) اختبارًا أساسيًا للتحقق مما إذا كان المُخصص يدعم التخصيصات ذات الحجم الصفري أم لا.

إذا كان [T; N] لا يفي بحجم المخصص أو قيود المحاذاة ، فإن AFAICT لا يتناسب مع كتلة ذاكرة التخصيص ، ويكون السلوك غير محدد (وفقًا لشرط الأمان).

نعم ، أعتقد أنه يمكن إسقاط حالة الخطأ هذه لأنها زائدة عن الحاجة. إما أننا نحتاج إلى شرط الأمان أو شرط الخطأ ، ولكن ليس كلاهما.

حالة الخطأ الأخرى هي "إرجاع Err دومًا عند التجاوز الحسابي." وهو عام جدًا. من الصعب معرفة ما إذا كانت حالة خطأ مفيدة.

IMO ، يحرسها نفس شرط الأمان على النحو الوارد أعلاه ؛ إذا تجاوزت سعة [T; N] ، فلن يصلح تخصيص كتلة الذاكرة هذه. ربما يستطيع pnkfelix توضيح هذا الأمر؟

بمعنى ما ، يعد Alloc::alloc_array(1) اختبارًا أساسيًا للتحقق مما إذا كان المُخصص يدعم التخصيصات ذات الحجم الصفري أم لا.

هل تقصد Alloc::alloc_array(0) ؟

IMO ، يحرسها نفس شرط الأمان على النحو الوارد أعلاه ؛ إذا تجاوزت سعة [T; N] ، فلن يتم إلغاء تخصيص كتلة الذاكرة هذه.

لاحظ أنه يمكن للمستخدمين تنفيذ هذه السمة لمخصصاتهم المخصصة ، وأن هؤلاء المستخدمين يمكنهم تجاوز عمليات التنفيذ الافتراضية لهذه الطرق. لذلك عند التفكير في ما إذا كان هذا يجب أن يعيد Err للتجاوز الحسابي أم لا ، لا ينبغي للمرء التركيز فقط على ما يفعله التنفيذ الافتراضي الحالي للطريقة الافتراضية ، ولكن أيضًا التفكير في ما قد يكون منطقيًا بالنسبة للمستخدمين الذين ينفذون هذه الطريقة للآخرين المخصصين.

هل تقصد Alloc::alloc_array(0) ؟

نعم آسف.

لاحظ أنه يمكن للمستخدمين تنفيذ هذه السمة لمخصصاتهم المخصصة ، وأن هؤلاء المستخدمين يمكنهم تجاوز عمليات التنفيذ الافتراضية لهذه الطرق. لذلك عند التفكير فيما إذا كان هذا يجب أن يعيد Err للتجاوز الحسابي أم لا ، لا ينبغي للمرء أن يركز فقط على ما يفعله التنفيذ الافتراضي الحالي للطريقة الافتراضية ، ولكن أيضًا التفكير في ما قد يكون منطقيًا بالنسبة للمستخدمين الذين يطبقون هذه الطريقة للآخرين المخصصين.

أرى ، لكن تنفيذ Alloc يتطلب unsafe impl ويجب على المنفذين اتباع قواعد الأمان المذكورة في https://github.com/rust-lang/rust/issues/32838#issuecomment -467093527 .

كل واجهة برمجة تطبيقات تُشير إلى مشكلة تتبع هنا هي سمة Alloc أو مرتبطة بالسمة Alloc . @ rust-lang / libs ، هل تشعر أنه من المفيد إبقاء هذا مفتوحًا بالإضافة إلى https://github.com/rust-lang/rust/issues/42774؟

سؤال بسيط في الخلفية: ما هو الدافع وراء المرونة مع ZST؟ يبدو لي أنه نظرًا لأننا نعلم في وقت الترجمة أن النوع هو ZST ، يمكننا تحسين كل من التخصيص (لإرجاع قيمة ثابتة) وإلغاء التخصيص تمامًا. بالنظر إلى ذلك ، يبدو لي أنه يجب أن نقول واحدًا مما يلي:

  • يعود الأمر دائمًا إلى المنفذ لدعم ZSTs ، ولا يمكنهم إرجاع Err مقابل ZST
  • دائمًا ما يكون UB هو تخصيص ZSTs ، وتقع على المتصل مسؤولية قصر الدائرة في هذه الحالة
  • هناك نوع من الطريقة alloc_inner التي يطبقها المتصلون ، وطريقة alloc مع تطبيق افتراضي يقوم بعمل الدائرة القصيرة ؛ يجب أن يدعم alloc ZSTs ، لكن alloc_inner قد لا يتم استدعاؤه لـ ZST (هذا فقط حتى نتمكن من إضافة منطق الدائرة القصيرة في مكان واحد - في تعريف السمة - بالترتيب لتوفير بعض المنفذين المعياري)

هل هناك سبب للحاجة إلى المرونة التي نتمتع بها مع واجهة برمجة التطبيقات الحالية؟

هل هناك سبب للحاجة إلى المرونة التي نتمتع بها مع واجهة برمجة التطبيقات الحالية؟

إنها مقايضة. يمكن القول ، أن سمة Alloc تُستخدم أكثر مما يتم تنفيذها ، لذلك قد يكون من المنطقي جعل استخدام Alloc سهلاً قدر الإمكان من خلال توفير دعم مدمج لـ ZST.

هذا يعني أن منفذي سمة Alloc سيحتاجون إلى الاهتمام بهذا ، ولكن الأهم بالنسبة لي أن أولئك الذين يحاولون تطوير سمة Alloc سيحتاجون إلى وضع ZST في الاعتبار عند كل تغيير في واجهة برمجة التطبيقات. كما أنه يعقد مستندات واجهة برمجة التطبيقات من خلال شرح كيفية معالجة ZST (أو يمكن أن تكون كذلك إذا تم "تعريف التنفيذ").

يتبع مخصصو C ++ هذا النهج ، حيث يحاول المخصص حل العديد من المشكلات المختلفة. لم يجعل هذا الأمر أكثر صعوبة في التنفيذ وصعوبة تطويرها فحسب ، بل زاد من صعوبة استخدامها أيضًا بسبب كيفية تفاعل كل هذه المشكلات في واجهة برمجة التطبيقات.

أعتقد أن التعامل مع ZSTs ، وتخصيص / إلغاء تخصيص الذاكرة الأولية هما مشكلتان متعامدتان ومختلفتان ، وبالتالي يجب أن نحافظ على بساطة Alloc trait API من خلال عدم التعامل معها.

سيحتاج مستخدمو Alloc مثل libstd إلى التعامل مع ZST ، على سبيل المثال ، في كل مجموعة. هذه بالتأكيد مشكلة تستحق الحل ، لكنني لا أعتقد أن سمة Alloc هي المكان المناسب لذلك. أتوقع أن تظهر أداة لحل هذه المشكلة داخل libstd بدافع الضرورة ، وعندما يحدث ذلك ، يمكننا ربما محاولة RFC مثل هذه الأداة وكشفها في std :: heap.

كل هذا يبدو معقولاً.

أعتقد أن التعامل مع ZSTs ، وتخصيص / إلغاء تخصيص الذاكرة الأولية هما مشكلتان متعامدتان ومختلفتان ، وبالتالي يجب أن نحافظ على بساطة Alloc trait API من خلال عدم التعامل معها.

ألا يعني ذلك أننا يجب أن يكون لدينا API صراحة لا تتعامل مع ZSTs بدلاً من أن تكون محددة التنفيذ؟ IMO ، خطأ "غير مدعوم" ليس مفيدًا جدًا في وقت التشغيل لأن الغالبية العظمى من المتصلين لن يكونوا قادرين على تحديد مسار احتياطي ، وبالتالي سيتعين عليهم افتراض أن ZSTs غير مدعومة على أي حال. يبدو أنظف لمجرد تبسيط واجهة برمجة التطبيقات وإعلان أنها _لا تدعمها أبدًا.

هل سيتم استخدام التخصص من قبل مستخدمي alloc للتعامل مع ZST؟ أو مجرد if size_of::<T>() == 0 شيكات؟

هل سيتم استخدام التخصص من قبل مستخدمي alloc للتعامل مع ZST؟ أو مجرد if size_of::<T>() == 0 شيكات؟

يجب أن تكون الأخيرة كافية ؛ ستتم إزالة مسارات الكود المناسبة بشكل تافه في وقت الترجمة.

ألا يعني ذلك أننا يجب أن يكون لدينا API صراحة لا تتعامل مع ZSTs بدلاً من أن تكون محددة التنفيذ؟

بالنسبة لي ، يتمثل أحد القيود المهمة في أنه إذا حظرنا التخصيصات ذات الحجم الصفري ، فيجب أن تكون طرق Alloc قادرة على افتراض أن Layout تم تمريره إليهم ليس بحجم صفري.

هناك طرق متعددة لتحقيق ذلك. قد يكون أحدها إضافة عبارة Safety إلى جميع طرق Alloc تفيد بأنه إذا كان حجم Layout صفري الحجم ، يكون السلوك غير محدد.

بدلاً من ذلك ، يمكننا حظر Layout s ذات الحجم الصفري ، ثم لا يحتاج Alloc إلى قول أي شيء عن التخصيصات ذات الحجم الصفري نظرًا لأن هذه لا يمكن أن تحدث بأمان ، ولكن القيام بذلك سيكون له بعض الجوانب السلبية.

على سبيل المثال ، بعض الأنواع مثل HashMap تبني Layout من عدة Layout s ، وبينما Layout الأخير قد لا يكون حجمه صفريًا ، قد تكون الوسيطة (على سبيل المثال في HashSet ). لذلك ستحتاج هذه الأنواع إلى استخدام "شيء آخر" (على سبيل المثال ، نوع LayoutBuilder ) لبناء آخر Layout s ، والدفع مقابل شيك "ليس بحجم صفري" (أو استخدم طريقة _unchecked ) عند التحويل إلى Layout .

هل سيتم استخدام التخصص من خلال تخصيص المستخدمين للتعامل مع ZST؟ أو فقط إذا كان size_of ::() == 0 شيكات؟

لا يمكننا التخصص في ZSTs حتى الآن. في الوقت الحالي ، تستخدم جميع الرموز size_of::<T>() == 0 .

هناك طرق متعددة لتحقيق ذلك. قد يكون أحدها إضافة عبارة Safety إلى جميع طرق Alloc تفيد بأنه إذا كان حجم Layout صفري الحجم ، يكون السلوك غير محدد.

سيكون من المثير للاهتمام التفكير فيما إذا كانت هناك طرق لجعل هذا ضمانًا لوقت الترجمة ، ولكن حتى debug_assert أن التصميم ليس بحجم الصفر يجب أن يكون كافياً للقبض على 99٪ من الأخطاء.

لم أهتم بالمناقشات المتعلقة بالمخصصين ، لذا آسف لذلك. لكنني كنت أتمنى منذ فترة طويلة أن يكون للمخصص إمكانية الوصول إلى نوع القيمة التي يخصصها. قد تكون هناك تصميمات مخصصة يمكن استخدامها.

ثم من المحتمل أن يكون لدينا نفس المشكلات مثل C ++ وواجهة برمجة تطبيقات مخصصة.

لكنني كنت أتمنى منذ فترة طويلة أن يكون للمخصص إمكانية الوصول إلى نوع القيمة التي يخصصها. تي

ماذا تريد هذا؟

كانgnzblgbrson اليوم أنا حالة استخدام محتملة ل_something_ معرفة عن هذا النوع من قيمة تم تخصيص.

أنا أعمل على مُخصص عالمي يمكن التبديل بين ثلاثة مُخصصات أساسية - مؤشر ترابط محلي ، وواحد عالمي مع أقفال ، وواحد لاستخدام coroutines - الفكرة هي أنه يمكنني تقييد coroutine الذي يمثل اتصال الشبكة إلى الحد الأقصى مقدار استخدام الذاكرة الديناميكي (في حالة عدم القدرة على التحكم في المخصصات في المجموعات ، ولا سيما في كود الطرف الثالث) *.

قد يكون من المفيد معرفة ما إذا كنت أقوم بتخصيص قيمة قد تنتقل عبر سلاسل الرسائل (مثل Arc) أم لا. أو ربما لا. لكنه سيناريو محتمل. في الوقت الحالي ، يكون للمخصص العام مفتاحًا يستخدمه المستخدم ليخبره عن أي مخصص يقوم بإجراء التخصيصات (غير مطلوب لإعادة التخصيص أو مجاني ؛ يمكننا فقط إلقاء نظرة على عنوان الذاكرة لذلك).

* [يتيح لي أيضًا استخدام ذاكرة NUMA المحلية حيثما كان ذلك ممكنًا دون أي قفل ، ومع نموذج مؤشر ترابط 1 core 1 ، للحد من إجمالي استخدام الذاكرة].

تضمين التغريدة

أنا أعمل على مخصص عالمي

لا أعتقد أن أيًا من هذا يمكن (أو يمكن) تطبيقه على السمة GlobalAlloc ، والسمة Alloc لها بالفعل طرق عامة يمكنها الاستفادة من معلومات النوع (على سبيل المثال alloc_array<T>(1) يخصص T واحدًا ، حيث T هو النوع الفعلي ، لذلك يمكن للمخصص أن يأخذ النوع في الاعتبار أثناء إجراء التخصيص). أعتقد أنه سيكون من المفيد أكثر لأغراض هذه المناقشة أن ترى في الواقع مُخصصات تنفيذ التعليمات البرمجية التي تستخدم معلومات النوع. لم أسمع أي حجة جيدة حول سبب احتياج هذه الأساليب إلى أن تكون جزءًا من بعض سمات المخصص العام ، بدلاً من أن تكون مجرد جزء من واجهة برمجة تطبيقات المخصص ، أو بعض سمات المخصص الأخرى.

أعتقد أنه سيكون من المثير للاهتمام أيضًا معرفة الأنواع التي تم تحديدها بواسطة Alloc التي تنوي دمجها مع المُخصصات التي تستخدم معلومات النوع ، وماذا تتوقع أن تكون النتيجة.

AFAICT ، النوع الوحيد المثير للاهتمام لذلك سيكون Box لأنه يخصص T مباشرة. إلى حد كبير جميع الأنواع الأخرى في std لا تخصص أبدًا T ، لكن بعض الأنواع الداخلية الخاصة التي لا يستطيع المخصص الخاص بك معرفة أي شيء عنها. على سبيل المثال ، Rc و Arc يمكن أن يخصص (InternalRefCounts, T) ، List / BTreeSet / إلخ. تخصيص أنواع العقد الداخلية ، Vec / Deque / ... قم بتخصيص مصفوفات من T s ، لكن ليس T s نفسها ، إلخ.

بالنسبة إلى Box و Vec يمكننا إضافة BoxAlloc وسمة ArrayAlloc مع الضمانات الشاملة مقابل Alloc التي يمكن للمخصصين تخصص في اختطاف كيف يتصرف هؤلاء ، إذا كانت هناك حاجة في أي وقت لمهاجمة هذه المشاكل بطريقة عامة. ولكن هل هناك سبب يجعل تقديم أنواع MyAllocBox و MyAllocVec التي تتآمر مع مخصصك لاستغلال معلومات النوع ليس حلاً قابلاً للتطبيق؟

نظرًا لأن لدينا الآن مستودعًا مخصصًا لموزعي العمل ، والقائمة في OP قديمة ، فقد يتم إغلاق هذه المشكلة للحفاظ على المناقشات وتتبع هذه الميزة في مكان واحد؟

نقطة جيدة TimDiekmann! سوف أمضي قدمًا وأغلق هذا لصالح سلاسل المناقشة في ذلك المستودع.

لا تزال هذه هي مشكلة التتبع التي تشير إليها بعض سمات #[unstable] . أعتقد أنه لا ينبغي إغلاقه حتى يتم تثبيت هذه الميزات أو إهمالها. (أو يمكننا تغيير السمات للإشارة إلى مشكلة مختلفة.)

نعم ، يجب أن تحتوي الميزات غير المستقرة المشار إليها في git master بالتأكيد على مشكلة تتبع مفتوحة.

متفق عليه. أضاف أيضًا إشعارًا ورابطًا إلى OP.

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات

القضايا ذات الصلة

SharplEr picture SharplEr  ·  3تعليقات

mcarton picture mcarton  ·  3تعليقات

Robbepop picture Robbepop  ·  3تعليقات

behnam picture behnam  ·  3تعليقات

dtolnay picture dtolnay  ·  3تعليقات