การจัดการหน่วยความจำสำหรับการส่งข้อความอย่างรวดเร็วระหว่างเธรดใน C ++


9

สมมติว่ามีสองเธรดซึ่งสื่อสารโดยการส่งข้อความข้อมูลแบบอะซิงโครนัสซึ่งกันและกัน แต่ละเธรดมีคิวข้อความบางประเภท

คำถามของฉันอยู่ในระดับต่ำมาก: สิ่งที่คาดว่าจะเป็นวิธีที่มีประสิทธิภาพที่สุดในการจัดการหน่วยความจำ? ฉันสามารถคิดถึงวิธีแก้ปัญหาต่าง ๆ :

  1. newผู้ส่งสร้างวัตถุผ่าน deleteโทรผู้รับ
  2. Memory pooling (เพื่อโอนหน่วยความจำกลับไปยังผู้ส่ง)
  3. การรวบรวมขยะ (เช่น Boehm GC)
  4. (ถ้าวัตถุมีขนาดเล็กพอ) คัดลอกตามค่าเพื่อหลีกเลี่ยงการจัดสรรฮีปอย่างสมบูรณ์

1) เป็นคำตอบที่ชัดเจนที่สุดดังนั้นฉันจะใช้มันสำหรับต้นแบบ มีโอกาสที่มันจะดีอยู่แล้ว แต่เป็นอิสระจากปัญหาเฉพาะของฉันฉันสงสัยว่าเทคนิคใดมีแนวโน้มมากที่สุดหากคุณกำลังปรับประสิทธิภาพการทำงานให้ดีที่สุด

ฉันคาดหวังว่าการรวมกันจะดีที่สุดในทางทฤษฎีโดยเฉพาะอย่างยิ่งเพราะคุณสามารถใช้ความรู้เพิ่มเติมเกี่ยวกับการไหลของข้อมูลระหว่างเธรด อย่างไรก็ตามฉันกลัวว่ามันเป็นการยากที่จะทำถูกที่สุด การปรับแต่งมากมาย ... :-(

การรวบรวมขยะควรเพิ่มได้อย่างง่ายดายหลังจากนั้น (หลังจากโซลูชันที่ 1) และฉันคาดว่ามันจะทำงานได้ดีมาก ดังนั้นฉันคิดว่ามันเป็นวิธีแก้ปัญหาที่ใช้งานได้จริงหาก 1) กลายเป็นว่าไม่มีประสิทธิภาพเกินไป

หากวัตถุมีขนาดเล็กและเรียบง่ายการคัดลอกตามค่าอาจเร็วที่สุด อย่างไรก็ตามฉันกลัวว่ามันจะบังคับให้มีข้อ จำกัด ที่ไม่จำเป็นในการใช้งานข้อความที่รองรับดังนั้นฉันจึงต้องการหลีกเลี่ยง

คำตอบ:


9

หากวัตถุมีขนาดเล็กและเรียบง่ายการคัดลอกตามค่าอาจเร็วที่สุด อย่างไรก็ตามฉันกลัวว่ามันจะบังคับให้มีข้อ จำกัด ที่ไม่จำเป็นในการใช้งานข้อความที่รองรับดังนั้นฉันจึงต้องการหลีกเลี่ยง

หากคุณสามารถคาดการณ์ขอบเขตบนchar buf[256]เช่นทางเลือกที่ใช้งานได้จริงหากคุณไม่สามารถเรียกใช้การจัดสรรฮีปเฉพาะในกรณีที่ไม่ค่อยเกิดขึ้น:

struct Message
{
    // Stores the message data.
    char buf[256];

    // Points to 'buf' if it fits, heap otherwise.
    char* data;
};

3

มันจะขึ้นอยู่กับว่าคุณจะใช้คิวอย่างไร

ถ้าคุณไปกับอาเรย์ (สไตล์โรบินกลม) คุณต้องตั้งค่าขอบเขตบนของขนาดสำหรับโซลูชัน 4 ถ้าคุณไปกับคิวที่เชื่อมโยงคุณจำเป็นต้องจัดสรรวัตถุ

จากนั้นทรัพยากรร่วมกันสามารถทำได้อย่างง่ายดายเมื่อคุณเพียงแค่เปลี่ยนใหม่และลบด้วยและAllocMessage<T> freeMessage<T>ข้อเสนอแนะของฉันจะไป จำกัด จำนวนของขนาดที่มีศักยภาพสามารถมีและรอบขึ้นเมื่อจัดสรรคอนกรีตTmessages

การรวบรวมขยะโดยตรงอาจทำงานได้ แต่อาจทำให้หยุดไปนานเมื่อต้องรวบรวมชิ้นส่วนขนาดใหญ่และ (ฉันคิดว่า) ทำงานได้แย่กว่าใหม่ / ลบเล็กน้อย


3

หากอยู่ใน C ++ เพียงใช้พอยน์เตอร์อัจฉริยะหนึ่งเดียว - unique_ptrจะทำงานได้ดีสำหรับคุณเนื่องจากจะไม่ลบวัตถุต้นแบบจนกว่าจะไม่มีใครจัดการกับมันได้ คุณส่งผ่านวัตถุ ptr ไปยังผู้รับตามค่าและไม่ต้องกังวลว่าเธรดใดควรลบ (ในกรณีที่ผู้รับไม่ได้รับวัตถุ)

คุณยังคงต้องจัดการกับการล็อกระหว่างเธรด แต่ประสิทธิภาพจะดีเนื่องจากไม่มีหน่วยความจำถูกคัดลอก (เฉพาะวัตถุ ptr เองซึ่งมีขนาดเล็ก)

การจัดสรรหน่วยความจำบนฮีปไม่ใช่สิ่งที่เร็วที่สุดดังนั้นการรวมกำไรจึงใช้เพื่อทำให้เร็วขึ้น คุณเพิ่งคว้าบล็อกถัดไปจากฮีปขนาดก่อนหน้าในพูลดังนั้นเพียงใช้ไลบรารีที่มีอยู่สำหรับสิ่งนี้


2
การล็อคมักเป็นปัญหาใหญ่กว่าการคัดลอกหน่วยความจำ แค่พูด.
tdammers

เมื่อคุณเขียนผมคิดว่าคุณหมายถึงunique_ptr shared_ptrแต่ในขณะที่มีข้อสงสัยว่าการใช้ตัวชี้สมาร์ทนั้นดีสำหรับการจัดการทรัพยากรมันไม่ได้เปลี่ยนความจริงที่ว่าคุณกำลังใช้การจัดสรรหน่วยความจำและการจัดสรรคืนรูปแบบบางอย่าง ฉันคิดว่าคำถามนี้อยู่ในระดับต่ำมากขึ้น
5gon12eder

3

ประสิทธิภาพที่ยอดเยี่ยมที่สุดเมื่อทำการสื่อสารวัตถุจากเธรดหนึ่งไปยังอีกเธรดหนึ่งคือโอเวอร์เฮดของการล็อก นี่เป็นคำสั่งของหลายไมโครวินาทีซึ่งมีความหมายมากกว่าเวลาเฉลี่ยที่คู่new/ deleteใช้เวลา (ตามลำดับของหนึ่งร้อยนาโนวินาที) newการใช้งานที่มีสติพยายามหลีกเลี่ยงการล็อคเกือบทุกค่าใช้จ่ายเพื่อหลีกเลี่ยงการกระทบกับประสิทธิภาพ

ที่กล่าวว่าคุณต้องการให้แน่ใจว่าคุณไม่จำเป็นต้องคว้าล็อคเมื่อสื่อสารวัตถุจากเธรดหนึ่งไปยังอีก ฉันรู้วิธีการทั่วไปสองวิธีเพื่อให้บรรลุสิ่งนี้ ทั้งสองทำงานเพียงทางเดียวระหว่างผู้ส่งและผู้รับหนึ่งคน:

  1. ใช้บัฟเฟอร์แหวน กระบวนการทั้งสองควบคุมตัวชี้หนึ่งเข้าไปในบัฟเฟอร์นี้อันหนึ่งคือตัวชี้อ่านอีกอันหนึ่งคือตัวชี้การเขียน

    • ผู้ส่งตรวจสอบก่อนว่ามีที่ว่างสำหรับเพิ่มองค์ประกอบโดยการเปรียบเทียบตัวชี้แล้วเพิ่มองค์ประกอบจากนั้นเพิ่มตัวชี้การเขียน

    • ผู้รับตรวจสอบว่ามีองค์ประกอบที่จะอ่านโดยการเปรียบเทียบพอยน์เตอร์แล้วอ่านองค์ประกอบจากนั้นเพิ่มตัวชี้อ่าน

    พอยน์เตอร์จะต้องเป็นอะตอมในขณะที่ใช้ร่วมกันระหว่างเธรด อย่างไรก็ตามแต่ละตัวชี้ถูกปรับเปลี่ยนโดยเธรดเดียวเท่านั้นส่วนอีกตัวต้องการเข้าถึงการอ่านของตัวชี้เท่านั้น องค์ประกอบในบัฟเฟอร์อาจเป็นตัวชี้ซึ่งช่วยให้คุณปรับขนาดบัฟเฟอร์แหวนเป็นขนาดที่ไม่ทำให้บล็อกผู้ส่งได้อย่างง่ายดาย

  2. ใช้รายการเชื่อมโยงที่มีองค์ประกอบอย่างน้อยหนึ่งรายการเสมอ ผู้รับมีตัวชี้ไปยังองค์ประกอบแรกผู้ส่งมีตัวชี้ไปยังองค์ประกอบสุดท้าย ตัวชี้เหล่านี้ไม่ถูกแชร์

    • ผู้ส่งสร้างโหนดใหม่สำหรับรายการที่เชื่อมโยงการตั้งค่าของตัวชี้ไปยังnext nullptrจากนั้นจะอัปเดตnextตัวชี้ขององค์ประกอบสุดท้ายให้ชี้ไปที่องค์ประกอบใหม่ ในที่สุดก็เก็บองค์ประกอบใหม่ในตัวชี้ของตัวเอง

    • ตัวรับสัญญาณจะเฝ้าดูnextตัวชี้ขององค์ประกอบแรกเพื่อดูว่ามีข้อมูลใหม่หรือไม่ ถ้าเป็นเช่นนั้นก็จะลบองค์ประกอบแรกเก่าเลื่อนตัวชี้ของตัวเองเพื่อชี้ไปที่องค์ประกอบปัจจุบันและเริ่มการประมวลผล

    ในการตั้งค่านี้พnextอยน์เตอร์จะต้องเป็นแบบอะตอมมิกและผู้ส่งต้องแน่ใจว่าจะไม่ตรวจสอบองค์ประกอบสุดท้ายตัวที่สองหลังจากตั้งค่าnextตัวชี้ แน่นอนว่าข้อดีคือผู้ส่งไม่ต้องบล็อค

ทั้งสองวิธีนั้นเร็วกว่าวิธีการล็อคแบบใด ๆ แต่พวกเขาต้องการการใช้อย่างระมัดระวังเพื่อให้ถูกต้อง และแน่นอนว่าพวกเขาต้องการ atomicity ฮาร์ดแวร์ดั้งเดิมของตัวชี้การเขียน / โหลด หากatomic<>การใช้งานของคุณใช้การล็อคภายในคุณจะถึงวาระ

ในทำนองเดียวกันหากคุณมีผู้อ่านและ / หรือนักเขียนหลายคนคุณคงเสียใจที่ไม่ได้: คุณอาจลองใช้รูปแบบที่ไม่ล็อค แต่จะเป็นการยากที่จะใช้งานให้ดีที่สุด สถานการณ์เหล่านี้ง่ายต่อการจัดการด้วยการล็อค อย่างไรก็ตามเมื่อคุณคว้าล็อคคุณสามารถหยุดกังวลเกี่ยวกับnew/ deleteประสิทธิภาพ


+1 ฉันต้องตรวจสอบวิธีแก้ปัญหาบัฟเฟอร์แหวนนี้เป็นทางเลือกแทนคิวพร้อมกันโดยใช้ CAS ลูป มันฟังดูมีแนวโน้มมาก
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.