ซึ่งเร็วกว่า: การจัดสรรสแต็กหรือการจัดสรรฮีป


503

คำถามนี้อาจฟังดูค่อนข้างธรรมดา แต่เป็นการอภิปรายที่ฉันมีกับผู้พัฒนารายอื่นที่ฉันทำงานด้วย

ฉันกำลังดูแลการจัดสรรสิ่งต่าง ๆ ที่ฉันสามารถทำได้แทนที่จะกองพวกเขา เขากำลังพูดกับฉันและเฝ้าไหล่ของฉันและแสดงความคิดเห็นว่ามันไม่จำเป็นเพราะมันเป็นการแสดงที่ฉลาดเหมือนกัน

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

สิ่งนี้ทำให้ฉันเป็นสิ่งที่อาจต้องพึ่งพาผู้แปล สำหรับโครงการนี้โดยเฉพาะฉันใช้คอมไพเลอร์MetrowerksสำหรับสถาปัตยกรรมPPC ข้อมูลเชิงลึกเกี่ยวกับชุดค่าผสมนี้จะมีประโยชน์มากที่สุด แต่โดยทั่วไปสำหรับ GCC และ MSVC ++ กรณีนี้คืออะไร การจัดสรรฮีปไม่สูงเท่ากับประสิทธิภาพการจัดสรรสแต็กหรือไม่ ไม่มีความแตกต่าง? หรือความแตกต่างดังนั้นนาทีมันกลายเป็นไมโครเพิ่มประสิทธิภาพไม่มีจุดหมาย


11
ฉันรู้ว่ามันค่อนข้างเก่า แต่ก็ดีที่เห็นตัวอย่างโค้ด C / C ++ แสดงการจัดสรรประเภทต่างๆ
Joseph Weissman

42
นายหน้าวัวของคุณไม่รู้เรื่อง แต่ที่สำคัญกว่านั้นคือเขาอันตรายเพราะเขาอ้างสิทธิ์อย่างเป็นทางการเกี่ยวกับสิ่งที่เขาไม่รู้อย่างมาก สร้างความตื่นเต้นให้กับคนจากทีมของคุณโดยเร็วที่สุด
Jim Balter

5
โปรดทราบว่ากองมักจะเป็นมากมีขนาดใหญ่กว่าสแต็ค หากคุณได้รับการจัดสรรข้อมูลจำนวนมากคุณจะต้องวางมันลงบนฮีปมิฉะนั้นจะเปลี่ยนขนาดสแต็คจากระบบปฏิบัติการ
Paul Draper

1
การปรับให้เหมาะสมทั้งหมดยกเว้นว่าคุณมีเกณฑ์มาตรฐานหรือข้อโต้แย้งที่ซับซ้อนพิสูจน์เป็นอย่างอื่น
Björn Lindqvist

2
ฉันสงสัยว่าเพื่อนร่วมงานของคุณมีประสบการณ์ Java หรือ C # เป็นส่วนใหญ่หรือไม่ ในภาษาเหล่านั้นเกือบทุกอย่างได้รับการจัดสรรกองไว้ใต้ฝากระโปรงซึ่งอาจนำไปสู่ข้อสันนิษฐานดังกล่าว
Cort Ammon

คำตอบ:


493

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

นอกจากนี้ stack vs. heap ไม่เพียง แต่คำนึงถึงประสิทธิภาพเท่านั้น มันยังบอกคุณมากเกี่ยวกับอายุการใช้งานของวัตถุที่คาดหวัง


211
และที่สำคัญมากกองอยู่เสมอร้อนของหน่วยความจำที่คุณได้รับมากมีแนวโน้มที่จะอยู่ในแคชกว่าหน่วยความจำใด ๆ ไกลจัดสรรกอง
Benoît

47
ในสถาปัตยกรรมบางส่วน (ส่วนใหญ่ฝังที่ฉันรู้) สแต็กอาจถูกเก็บไว้ในหน่วยความจำ on-die ที่รวดเร็ว (เช่น SRAM) สิ่งนี้สามารถสร้างความแตกต่างอย่างมาก!
Leander

38
เนื่องจากสแต็คเป็นจริงสแต็ก คุณไม่สามารถเพิ่มหน่วยความจำที่ใช้โดยสแต็กได้จนกว่าจะอยู่ด้านบน ไม่มีการจัดการคุณผลักดันหรือวางสิ่งต่างๆลงไป ในทางกลับกันหน่วยความจำฮีปมีการจัดการ: มันจะถามเคอร์เนลสำหรับหน่วยความจำอาจแบ่งพวกเขารวมพวกเขานำมาใช้ใหม่และปลดปล่อยพวกเขา สแต็คมีความหมายจริงๆสำหรับการจัดสรรที่รวดเร็วและสั้น
Benoît

24
@Pierier เนื่องจากสแต็กมีขนาดเล็กกว่าฮีปมาก หากคุณต้องการจัดสรรอาร์เรย์ขนาดใหญ่คุณควรจัดสรรให้กับฮีป หากคุณพยายามที่จะจัดสรรอาเรย์ขนาดใหญ่บนสแต็กมันจะให้สแต็คล้นคุณ ลองตัวอย่างใน C ++ อันนี้: int t [100000000]; ลองตัวอย่างเช่น t [10000000] = 10; จากนั้นศาล << t [10,000000]; ควรให้สแต็คล้นหรือไม่ทำงานและจะไม่แสดงอะไรให้คุณเห็น แต่ถ้าคุณจัดสรรอาเรย์บนฮีป: int * t = new int [100000000]; และดำเนินการเดียวกันหลังจากนั้นมันจะทำงานเพราะกองมีขนาดที่จำเป็นสำหรับอาร์เรย์ขนาดใหญ่เช่นนั้น
Lilian A. Moraru

7
@Pacerier เหตุผลที่ชัดเจนที่สุดคือการที่วัตถุบนสแต็คออกไปจากขอบเขตเมื่อออกจากบล็อกที่พวกเขาได้รับการจัดสรรใน.
จิม Balter

166

กองซ้อนเร็วกว่ามาก แท้จริงแล้วมันใช้คำสั่งเดียวในสถาปัตยกรรมส่วนใหญ่ในกรณีส่วนใหญ่เช่นใน x86:

sub esp, 0x10

(ซึ่งจะย้ายตัวชี้สแต็กลง 0x10 ไบต์และ "จัดสรร" ไบต์เหล่านั้นสำหรับการใช้งานโดยตัวแปร)

แน่นอนว่าขนาดของสแต็กนั้นมีขนาดที่ จำกัด มาก ๆ เพราะคุณจะรู้ได้อย่างรวดเร็วว่าคุณใช้การจัดสรรสแต็กมากเกินไปหรือพยายามเรียกซ้ำ :-)

นอกจากนี้ยังมีเหตุผลเล็กน้อยที่จะเพิ่มประสิทธิภาพการทำงานของโค้ดที่ไม่ต้องการด้วยเหตุผลเช่นแสดงให้เห็นโดยการทำโปรไฟล์ "การเพิ่มประสิทธิภาพก่อนกำหนด" มักทำให้เกิดปัญหามากกว่าที่ควรค่า

กฎง่ายๆของฉัน: ถ้าฉันรู้ว่าฉันต้องการข้อมูลบางส่วนในเวลาคอมไพล์และมีขนาดไม่เกินร้อยไบต์ฉันก็จะจัดสรรมัน มิฉะนั้นฉันจะจัดสรรกองไว้


20
คำสั่งเดียวและมักจะใช้ร่วมกันโดยวัตถุทั้งหมดบนสแต็ก
MSalters

9
ทำให้จุดดีโดยเฉพาะอย่างยิ่งจุดที่ต้องการได้ด้วยการตรวจสอบ ฉันประหลาดใจอย่างต่อเนื่องที่ความกังวลของผู้คนเกี่ยวกับการแสดงถูกวางผิดที่
Mike Dunlavey

6
"การจัดสรรคืน" นั้นง่ายมากและทำได้ด้วยการleaveเรียนการสอนเพียงครั้งเดียว
doc

15
โปรดจำไว้ว่าค่าใช้จ่าย "ซ่อน" ที่นี่โดยเฉพาะอย่างยิ่งเป็นครั้งแรกที่คุณขยายสแต็ก การทำเช่นนั้นอาจส่งผลให้เกิดความผิดพลาดของเพจบริบทเปลี่ยนเป็นเคอร์เนลซึ่งจำเป็นต้องทำงานเพื่อจัดสรรหน่วยความจำ (หรือโหลดจากการสลับในกรณีที่แย่ที่สุด)
nos

2
ในบางกรณีคุณสามารถจัดสรรได้ด้วย 0 คำแนะนำ หากทราบข้อมูลบางอย่างเกี่ยวกับจำนวนไบต์ที่ต้องจัดสรรคอมไพเลอร์สามารถจัดสรรล่วงหน้าในเวลาเดียวกันกับที่มันจัดสรรตัวแปรสแต็กอื่น ๆ ในกรณีเหล่านั้นคุณไม่ต้องจ่ายอะไรเลย!
Cort Ammon

119

สุจริตมันเป็นเรื่องไม่สำคัญที่จะเขียนโปรแกรมเพื่อเปรียบเทียบประสิทธิภาพ:

#include <ctime>
#include <iostream>

namespace {
    class empty { }; // even empty classes take up 1 byte of space, minimum
}

int main()
{
    std::clock_t start = std::clock();
    for (int i = 0; i < 100000; ++i)
        empty e;
    std::clock_t duration = std::clock() - start;
    std::cout << "stack allocation took " << duration << " clock ticks\n";
    start = std::clock();
    for (int i = 0; i < 100000; ++i) {
        empty* e = new empty;
        delete e;
    };
    duration = std::clock() - start;
    std::cout << "heap allocation took " << duration << " clock ticks\n";
}

มันบอกว่าสอดคล้องโง่หลอกเด็กของจิตใจของเล็ก ๆ น้อย ๆ เห็นได้ชัดว่าการเพิ่มประสิทธิภาพคอมไพเลอร์เป็น hobgoblins ในใจของโปรแกรมเมอร์หลายคน การสนทนานี้มักจะอยู่ที่ด้านล่างของคำตอบ แต่คนดูเหมือนจะไม่ใส่ใจที่จะอ่านจนถึงตอนนี้ดังนั้นฉันจึงย้ายมาที่นี่เพื่อหลีกเลี่ยงคำถามที่ฉันตอบไปแล้ว

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

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

ใครก็ตามที่เปิดใช้งานเครื่องมือเพิ่มประสิทธิภาพแล้วบ่นเกี่ยวกับการต่อสู้มันควรจะถูกเยาะเย้ยสาธารณะ

ถ้าฉันสนใจความแม่นยำระดับนาโนวินาทีฉันก็จะไม่ใช้ std::clock()ฉันจะไม่ใช้ ถ้าฉันต้องการเผยแพร่ผลลัพธ์เป็นวิทยานิพนธ์ระดับปริญญาเอกฉันจะทำเรื่องใหญ่กว่านี้และฉันอาจเปรียบเทียบ GCC, Tendra / Ten15, LLVM, Watcom, Borland, Visual C ++, Digital Mars, ICC และคอมไพเลอร์อื่น ๆ เนื่องจากการจัดสรรฮีปใช้เวลานานกว่าการจัดสรรสแต็กนับร้อยครั้งและฉันไม่เห็นว่ามีประโยชน์ใด ๆ เกี่ยวกับการตรวจสอบคำถามอีกต่อไป

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

  1. เพิ่มสมาชิกข้อมูลไปยังemptyและเข้าถึงสมาชิกข้อมูลนั้นในลูป แต่ถ้าฉันเคยอ่านจากสมาชิกข้อมูลเครื่องมือเพิ่มประสิทธิภาพสามารถทำการพับแบบคงที่และเอาลูปออก ถ้าฉันเพิ่งเขียนไปยังสมาชิกของข้อมูลเครื่องมือเพิ่มประสิทธิภาพอาจข้ามทั้งหมดยกเว้นการวนซ้ำครั้งสุดท้ายของลูป นอกจากนี้คำถามไม่ใช่ "การจัดสรรสแต็กและการเข้าถึงข้อมูลกับการจัดสรรฮีปและการเข้าถึงข้อมูล"

  2. ประกาศe volatile, แต่volatileมักจะถูกรวบรวมอย่างไม่ถูกต้อง (PDF)

  3. นำที่อยู่eภายในวง (และอาจกำหนดให้กับตัวแปรที่มีการประกาศexternและกำหนดไว้ในไฟล์อื่น) แต่ถึงแม้ในกรณีนี้คอมไพเลอร์อาจสังเกตเห็นว่า - บนสแต็กอย่างน้อย - eจะถูกจัดสรรที่ที่อยู่หน่วยความจำเดียวกันเสมอ ฉันได้รับการวนซ้ำทั้งหมด แต่ไม่มีการจัดสรรวัตถุจริงๆ

นอกเหนือจากที่เห็นได้ชัดการทดสอบนี้มีข้อบกพร่องที่จะวัดการจัดสรรและการจัดสรรคืนและคำถามเดิมไม่ได้ถามเกี่ยวกับการจัดสรรคืน แน่นอนว่าตัวแปรที่จัดสรรไว้ในสแต็กจะถูกจัดสรรคืนอัตโนมัติเมื่อสิ้นสุดขอบเขตดังนั้นจึงไม่เรียกdelete(1) เอียงตัวเลข (การจัดสรรคืนสแต็กจะรวมอยู่ในจำนวนที่เกี่ยวกับการจัดสรรสแต็ก 2) ทำให้หน่วยความจำรั่วไหลไม่ดีเว้นแต่เราจะทำการอ้างอิงไปยังตัวชี้และการโทรใหม่deleteหลังจากเราได้ทำการวัดเวลาของเราแล้ว

บนเครื่องของฉันโดยใช้ g ++ 3.4.4 บน Windows ฉันได้รับ "0 นาฬิกา ticks" สำหรับทั้งการจัดสรรสแต็คและฮีปสำหรับการจัดสรรน้อยกว่า 100,000 การจัดสรรและจากนั้นฉันได้รับ "0 นาฬิกาเห็บ" สำหรับการจัดสรรสแต็ค "สำหรับการจัดสรรฮีป เมื่อฉันวัดการจัดสรร 10,000,000 ครั้งการจัดสรรสแต็กจะใช้เวลา 31 นาฬิกาและการจัดสรรฮีปใช้เวลา 1562 นาฬิกา


ใช่คอมไพเลอร์ปรับให้เหมาะสมอาจช่วยสร้างวัตถุว่างได้ หากฉันเข้าใจอย่างถูกต้องมันอาจกำจัดลูปแรกทั้งหมด เมื่อฉันเพิ่มค่าการจัดสรรซ้ำไปเป็น 10,000,000 การจัดสรรสแต็กใช้เวลา 31 นาฬิกาและการจัดสรรฮีปใช้เวลา 1562 นาฬิกา ฉันคิดว่ามันปลอดภัยที่จะบอกว่าโดยไม่บอก g ++ เพื่อให้สามารถใช้งานได้ดีที่สุด g ++ ไม่ได้ช่วยสร้างคอนสตรัคชัน


ในปีที่ผ่านมาตั้งแต่ฉันเขียนสิ่งนี้การตั้งค่าใน Stack Overflow เป็นการโพสต์ประสิทธิภาพจากการสร้างที่ปรับให้เหมาะสม โดยทั่วไปแล้วฉันคิดว่ามันถูกต้อง อย่างไรก็ตามฉันคิดว่ามันเป็นเรื่องโง่ที่จะขอให้คอมไพเลอร์ทำการปรับรหัสให้เหมาะสมเมื่อคุณไม่ต้องการใช้รหัสนั้น มันทำให้ฉันรู้สึกคล้ายกับการจ่ายเงินพิเศษสำหรับบริการจอดรถ แต่ปฏิเสธที่จะมอบกุญแจ ในกรณีนี้โดยเฉพาะฉันไม่ต้องการให้เครื่องมือเพิ่มประสิทธิภาพทำงาน

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

#include <cstdio>
#include <chrono>

namespace {
    void on_stack()
    {
        int i;
    }

    void on_heap()
    {
        int* i = new int;
        delete i;
    }
}

int main()
{
    auto begin = std::chrono::system_clock::now();
    for (int i = 0; i < 1000000000; ++i)
        on_stack();
    auto end = std::chrono::system_clock::now();

    std::printf("on_stack took %f seconds\n", std::chrono::duration<double>(end - begin).count());

    begin = std::chrono::system_clock::now();
    for (int i = 0; i < 1000000000; ++i)
        on_heap();
    end = std::chrono::system_clock::now();

    std::printf("on_heap took %f seconds\n", std::chrono::duration<double>(end - begin).count());
    return 0;
}

แสดง:

on_stack took 2.070003 seconds
on_heap took 57.980081 seconds

บนระบบของฉันเมื่อคอมไพล์ด้วยบรรทัดคำสั่ง cl foo.cc /Od /MT /EHscในระบบของฉันเมื่อรวบรวมกับบรรทัดคำสั่ง

คุณอาจไม่เห็นด้วยกับวิธีการของฉันในการสร้างงานที่ไม่เหมาะ ไม่เป็นไร: อย่าลังเลที่จะปรับเปลี่ยนมาตรฐานมากเท่าที่คุณต้องการ เมื่อฉันเปิดการปรับให้เหมาะสมฉันจะได้รับ:

on_stack took 0.000000 seconds
on_heap took 51.608723 seconds

ไม่ใช่เพราะการจัดสรรสแต็กนั้นเกิดขึ้นทันที แต่เนื่องจากคอมไพเลอร์ที่มีค่าครึ่งเดียวสามารถสังเกตเห็นว่าon_stackไม่มีประโยชน์ใด ๆ และสามารถปรับให้เหมาะสม GCC บนแล็ปท็อป Linux ของฉันยังสังเกตเห็นว่าon_heapไม่มีประโยชน์อะไรเลยและปรับให้เหมาะสมเช่นกัน:

on_stack took 0.000003 seconds
on_heap took 0.000002 seconds

2
นอกจากนี้คุณควรเพิ่มลูป "การปรับเทียบ" ที่จุดเริ่มต้นของฟังก์ชั่นหลักของคุณบางสิ่งบางอย่างเพื่อให้คุณทราบว่าคุณใช้เวลาเท่าไรต่อรอบการวนรอบและปรับลูปอื่น ๆ เพื่อให้แน่ใจว่าตัวอย่างของคุณทำงาน บางเวลาแทนที่จะเป็นค่าคงที่ที่คุณใช้
Joe Pineda

2
ฉันยังดีใจที่เพิ่มจำนวนครั้งที่ตัวเลือกแต่ละวงวนทำงาน (บวกกับการแนะนำ g ++ ที่จะไม่เพิ่มประสิทธิภาพ?) ให้ผลลัพธ์ที่สำคัญ ดังนั้นตอนนี้เรามีข้อเท็จจริงที่ยากที่จะบอกว่าสแต็คเร็วขึ้น ขอบคุณสำหรับความพยายามของคุณ!
Joe Pineda

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

3
ฉันช้ามาก แต่ก็มีค่ามากที่กล่าวถึงในที่นี้ว่าการจัดสรรฮีปจะร้องขอหน่วยความจำผ่านเคอร์เนลดังนั้นประสิทธิภาพการทำงานของ Hit ก็ขึ้นอยู่กับประสิทธิภาพของเคอร์เนลด้วย ใช้รหัสนี้กับ Linux (Linux 3.10.7-gentoo # 2 SMP พุธ 4 ก.ย. 18:58:21 MDT 2013 x86_64), การปรับเปลี่ยนสำหรับตัวจับเวลา HR และการใช้ 100 ล้านครั้งในแต่ละลูปทำให้ประสิทธิภาพการทำงานนี้: stack allocation took 0.15354 seconds, heap allocation took 0.834044 secondsด้วยการ-O0ตั้งค่าการสร้าง การจัดสรรลินุกซ์ของฮีปจะช้าลงเพียงประมาณ 5.5 เท่านั้นในเครื่องของฉัน
Taywee

4
บน windows ที่ไม่มีการออปติไมซ์ (บิลด์ debug) มันจะใช้ debug heap ซึ่งช้ากว่าฮีปที่ไม่ใช่ debug มาก ฉันไม่คิดว่าเป็นความคิดที่ดีที่จะ "หลอกลวง" เครื่องมือเพิ่มประสิทธิภาพเลย นักเขียนคอมไพเลอร์ฉลาด แต่คอมไพเลอร์ไม่ใช่ AI
paulm

30

สิ่งที่น่าสนใจที่ฉันเรียนรู้เกี่ยวกับ Stack vs. Heap Allocation บนตัวประมวลผล Xbox 360 Xenon ซึ่งอาจนำไปใช้กับระบบมัลติคอร์อื่น ๆ ก็คือการจัดสรร Heap เป็นสาเหตุทำให้ส่วนสำคัญเข้าสู่การหยุดคอร์อื่น ๆ ทั้งหมดเพื่อให้การจัดสรรไม่ ไม่ขัดแย้ง ดังนั้นในการวนรอบอย่างแน่นหนา Stack Allocation เป็นวิธีที่จะใช้สำหรับอาร์เรย์ที่มีขนาดคงที่เนื่องจากป้องกันแผงลอย

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


4
นั่นเป็นความจริงสำหรับเครื่องมัลติคอร์ส่วนใหญ่ไม่ใช่แค่ซีนอน แม้แต่เซลล์ก็ต้องทำเพราะคุณอาจใช้เธรดฮาร์ดแวร์สองอันบนแกน PPU นั้น
Crashworks

15
นั่นเป็นผลของการใช้งานตัวจัดสรรฮีป (โดยเฉพาะอย่างยิ่งแย่) ตัวจัดสรรฮีปที่ดีกว่านั้นไม่จำเป็นต้องได้รับการล็อคในทุกการจัดสรร
Chris Dodd

19

คุณสามารถเขียนตัวจัดสรรฮีปพิเศษสำหรับขนาดเฉพาะของวัตถุที่มีประสิทธิภาพมาก อย่างไรก็ตามเรื่องทั่วไปตัวจัดสรรฮีปไม่ได้มีประสิทธิภาพโดยเฉพาะ

ฉันเห็นด้วยกับTorbjörn Gyllebring เกี่ยวกับอายุการใช้งานของวัตถุที่คาดหวัง จุดดี!


1
บางครั้งเรียกว่าการจัดสรรพื้น
เบอนัวต์

8

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

ฉันขอแนะนำอย่างยิ่งสำหรับรายการเล็ก ๆ แล้วแต่อย่างใดจะเหมาะสมกับขอบเขตของการจัดสรร สำหรับรายการขนาดใหญ่ฮีปอาจจำเป็น

บนระบบปฏิบัติการแบบ 32 บิตที่มีหลายเธรดสแต็กมักจะค่อนข้าง จำกัด (แม้ว่าโดยทั่วไปจะมีอย่างน้อยสองสามเมกะไบต์) เนื่องจากพื้นที่ที่อยู่จะต้องถูกแกะสลักขึ้นและไม่ช้าก็เร็วสแต็กเธรดหนึ่งจะทำงาน บนระบบเธรดเดี่ยว (Linux glibc เธรดเดี่ยวต่อไป) ข้อ จำกัด นั้นน้อยกว่ามากเนื่องจากสแต็กสามารถเติบโตและเติบโตได้

บนระบบปฏิบัติการ 64 บิตมีพื้นที่ที่อยู่เพียงพอที่จะทำให้เธรดสแต็คมีขนาดใหญ่มาก


6

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

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


ฉันไม่คิดว่า heap นั้น "ค้นหา" เว้นแต่ว่าจะได้รับการเพจ ค่อนข้างแน่ใจว่าหน่วยความจำโซลิดสเตตใช้มัลติเพล็กเซอร์และสามารถเข้าถึงหน่วยความจำโดยตรงดังนั้นหน่วยความจำเข้าถึงโดยสุ่ม
Joe Phillips

4
นี่คือตัวอย่าง โปรแกรมการโทรขอให้จัดสรร 37 ไบต์ ฟังก์ชั่นห้องสมุดค้นหาบล็อกอย่างน้อย 40 ไบต์ บล็อกแรกในรายการว่างมี 16 ไบต์ บล็อกที่สองในรายการว่างมี 12 ไบต์ บล็อกที่สามมีขนาด 44 ไบต์ ห้องสมุดหยุดค้นหา ณ จุดนั้น
โปรแกรมเมอร์ Windows

6

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


4

กองซ้อนมีความจุ จำกัด ในขณะที่กองไม่ได้ สแต็กทั่วไปสำหรับกระบวนการหรือเธรดคือประมาณ 8K คุณไม่สามารถเปลี่ยนขนาดได้เมื่อจัดสรรแล้ว

ตัวแปร stack ตามกฎการกำหนดขอบเขตในขณะที่ heap ไม่มี หากตัวชี้คำสั่งของคุณมีมากกว่าฟังก์ชั่นตัวแปรใหม่ทั้งหมดที่เกี่ยวข้องกับฟังก์ชั่นจะหายไป

ที่สำคัญที่สุดคือคุณไม่สามารถคาดการณ์การเรียกฟังก์ชั่นโดยรวมล่วงหน้าได้ ดังนั้นการจัดสรรเพียง 200 ไบต์ในส่วนของคุณอาจทำให้เกิดการล้นสแต็ก สิ่งนี้สำคัญอย่างยิ่งหากคุณกำลังเขียนไลบรารีไม่ใช่แอปพลิเคชัน


1
จำนวนพื้นที่ที่อยู่เสมือนที่จัดสรรสำหรับสแต็กโหมดผู้ใช้บนระบบปฏิบัติการที่ทันสมัยมีแนวโน้มว่าจะเป็นอย่างน้อย 64kB หรือใหญ่กว่าโดยค่าเริ่มต้น (1MB บน Windows) คุณกำลังพูดถึงขนาดเคอร์เนลสแต็กหรือไม่
bk1e

1
บนเครื่องของฉันขนาดสแต็กเริ่มต้นสำหรับกระบวนการคือ 8MB ไม่ใช่ kB คอมพิวเตอร์ของคุณอายุเท่าไหร่
Greg Rogers

3

ฉันคิดว่าอายุการใช้งานมีความสำคัญและไม่ว่าสิ่งที่จัดสรรจะต้องถูกสร้างขึ้นในลักษณะที่ซับซ้อนหรือไม่ ตัวอย่างเช่นในการสร้างแบบจำลองธุรกรรมที่ขับเคลื่อนด้วยคุณมักจะต้องกรอกและส่งผ่านในโครงสร้างการทำธุรกรรมที่มีพวงของเขตข้อมูลไปยังฟังก์ชั่นการดำเนินงาน ดูตัวอย่างมาตรฐาน OSCI SystemC TLM-2.0

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

เร็วกว่าการจัดสรรออบเจ็กต์ในการเรียกแต่ละครั้ง

เหตุผลก็คือวัตถุมีการก่อสร้างที่มีราคาแพงและอายุการใช้งานที่ค่อนข้างยาวนาน

ฉันจะบอกว่า: ลองทั้งคู่และดูว่าอะไรดีที่สุดในกรณีของคุณเพราะมันขึ้นอยู่กับพฤติกรรมของรหัสของคุณ


3

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

สำหรับแอปพลิเคชันอื่น ๆ ที่เวลาไม่เป็นปัญหาอาจไม่สำคัญมากนัก แต่ถ้าคุณจัดสรรจำนวนมากสิ่งนี้จะส่งผลต่อความเร็วในการเรียกใช้งาน พยายามใช้สแต็กสำหรับหน่วยความจำระยะสั้นและมักจะจัดสรร (เช่นในลูป) และนานที่สุด - ทำการจัดสรรฮีปในระหว่างการเริ่มต้นแอปพลิเคชัน


3

ไม่ใช่การจัดสรรสแต็ก jsut ที่เร็วขึ้น นอกจากนี้คุณยังชนะมากในการใช้ตัวแปรสแต็ค พวกเขามีสถานที่อ้างอิงที่ดีกว่า และในที่สุดการยกเลิกการจัดสรรก็ถูกกว่ามากเช่นกัน


3

การจัดสรรสแต็กเป็นสองคำสั่งในขณะที่ตัวจัดสรรฮีป rtos ที่เร็วที่สุดที่ฉันรู้จัก (TLSF) ใช้โดยเฉลี่ยตามคำสั่ง 150 คำสั่ง การจัดสรรสแต็กไม่ต้องการการล็อกเนื่องจากใช้หน่วยเก็บโลคัลเธรดซึ่งเป็นอีกหนึ่งประสิทธิภาพที่ยอดเยี่ยม ดังนั้นการจัดสรรสแต็กอาจเร็วกว่าคำสั่ง 2-3 เท่าขึ้นอยู่กับว่าสภาพแวดล้อมของคุณเป็นแบบมัลติเธรดอย่างหนักเพียงใด

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


3

ข้อกังวลเฉพาะของภาษา C ++

ครั้งแรกของทั้งหมดไม่มีสิ่งที่เรียกว่า "สแต็ค" หรือ "กอง" จัดสรรได้รับคำสั่งจาก C หากคุณกำลังพูดถึงวัตถุอัตโนมัติในขอบเขตบล็อกพวกเขาจะไม่ "จัดสรร" (BTW, ระยะเวลาการจัดเก็บอัตโนมัติใน C แน่นอนไม่เหมือนกันกับ "จัดสรร"; หลังคือ "ไดนามิก" ใน C + + parlance.) หน่วยความจำที่จัดสรรแบบไดนามิกอยู่ในร้านค้าฟรีไม่จำเป็นต้องอยู่ใน "กอง" แม้ว่า หลังมักจะเป็น (เริ่มต้น) การดำเนินงาน

แม้ว่าตามกฎความหมายเชิงนามธรรมของเครื่องวัตถุอัตโนมัติยังคงครอบครองหน่วยความจำการใช้งาน C ++ ที่สอดคล้องกันได้รับอนุญาตให้เพิกเฉยต่อความจริงนี้เมื่อมันสามารถพิสูจน์ได้ว่ามันไม่สำคัญ (เมื่อไม่เปลี่ยนพฤติกรรมที่สังเกตได้ของโปรแกรม) การอนุญาตนี้ได้รับการอนุญาตโดยกฎ as-ifใน ISO C ++ ซึ่งเป็นมาตราทั่วไปที่เปิดใช้งานการปรับให้เหมาะสมตามปกติ (และยังมีกฎเกือบเหมือนกันใน ISO C) นอกเหนือจากกฎ as-if ISO C ++ ยังมีกฎการคัดลอกเพื่ออนุญาตให้ละเว้นการสร้างวัตถุเฉพาะ ตัวเรียกและคอนสตรัคเตอร์ที่เกี่ยวข้องจึงถูกละเว้น เป็นผลให้วัตถุอัตโนมัติ (ถ้ามี) ในตัวสร้างและ destructors เหล่านี้จะถูกกำจัดเมื่อเทียบกับความหมายที่ไร้เดียงสานามธรรมโดยนัยโดยรหัสแหล่งที่มา

ในทางกลับกันการจัดสรรร้านค้าฟรีเป็น "การจัดสรร" อย่างแน่นอนโดยการออกแบบ ภายใต้กฎ ISO C ++ การจัดสรรดังกล่าวสามารถทำได้โดยการเรียกใช้ฟังก์ชันการจัดสรรฟังก์ชั่นการจัดสรรอย่างไรก็ตามตั้งแต่ ISO C ++ 14 มีกฎใหม่ (ไม่ใช่ as-if)เพื่ออนุญาตการรวมฟังก์ชั่นการจัดสรรทั่วโลก (เช่น::operator new) โทรในกรณีที่เฉพาะเจาะจง ดังนั้นบางส่วนของการดำเนินการจัดสรรแบบไดนามิกจึงไม่เหมือนในกรณีของวัตถุอัตโนมัติ

ฟังก์ชั่นการจัดสรรจัดสรรทรัพยากรของหน่วยความจำ สามารถจัดสรรวัตถุเพิ่มเติมตามการจัดสรรโดยใช้ตัวจัดสรร สำหรับวัตถุอัตโนมัติพวกเขาจะถูกนำเสนอโดยตรง - แม้ว่าหน่วยความจำพื้นฐานสามารถเข้าถึงได้และใช้เพื่อให้หน่วยความจำกับวัตถุอื่น ๆ (ตามตำแหน่งnew) แต่สิ่งนี้ไม่สมเหตุสมผลสำหรับร้านค้าฟรีเนื่องจากไม่มีวิธีการย้าย แหล่งข้อมูลอื่น ๆ

ข้อกังวลอื่น ๆ ทั้งหมดอยู่นอกขอบเขตของ C ++ อย่างไรก็ตามพวกเขายังคงมีความสำคัญ

เกี่ยวกับการใช้ C ++

C ++ ไม่เปิดเผยเร็กคอร์ดการเปิดใช้งาน reified หรือบางประเภทของการต่อเนื่องชั้นหนึ่ง (เช่นโดยที่มีชื่อเสียง call/cc )ไม่มีวิธีจัดการเฟรมเรคคอร์ดการเปิดใช้งานโดยตรง - ในกรณีที่การติดตั้งวัตถุอัตโนมัติต้อง เมื่อไม่มีการทำงานร่วมกัน (ไม่ใช่แบบพกพา) ที่มีการใช้งานพื้นฐาน ("ดั้งเดิม" ไม่ใช่แบบพกพารหัสเช่นรหัสการประกอบแบบอินไลน์), การละเลยของการจัดสรรพื้นฐานของเฟรมสามารถเล็กน้อย ตัวอย่างเช่นเมื่อฟังก์ชั่นที่เรียกว่า inlined กรอบสามารถผสานได้อย่างมีประสิทธิภาพในคนอื่น ๆ ดังนั้นจึงไม่มีวิธีที่จะแสดงสิ่งที่ "จัดสรร"

อย่างไรก็ตามเมื่อมีการเคารพการทำงานร่วมกันสิ่งที่กำลังจะซับซ้อน การใช้งานทั่วไปของ C ++ จะเปิดเผยความสามารถในการทำงานร่วมกันบน ISA (สถาปัตยกรรมชุดคำสั่ง) ด้วยการประชุมเรียกบางอย่างเป็นขอบเขตไบนารีที่ใช้ร่วมกันกับรหัสพื้นเมือง (เครื่องระดับ ISA) สิ่งนี้จะมีค่าใช้จ่ายอย่างชัดเจนโดยเฉพาะอย่างยิ่งเมื่อทำการบำรุงรักษาตัวชี้แบบสแต็กซึ่งมักจะถูกเก็บไว้โดยตรงโดยการลงทะเบียนระดับ ISA (พร้อมคำแนะนำเฉพาะเครื่องในการเข้าถึง) ตัวชี้สแต็กบ่งชี้ขอบเขตของเฟรมด้านบนของการเรียกใช้ฟังก์ชั่น (ที่ใช้งานอยู่ในปัจจุบัน) เมื่อมีการป้อนการเรียกฟังก์ชันจำเป็นต้องมีเฟรมใหม่และตัวชี้สแต็กจะถูกเพิ่มหรือลบ (ขึ้นอยู่กับระเบียบของ ISA) ด้วยค่าไม่น้อยกว่าขนาดเฟรมที่ต้องการ กรอบพูดแล้วจัดสรรเมื่อตัวชี้สแต็คหลังจากการดำเนินการ พารามิเตอร์ของฟังก์ชันอาจถูกส่งไปยังสแต็กเฟรมเช่นกันขึ้นอยู่กับระเบียบการโทรที่ใช้สำหรับการโทร เฟรมสามารถเก็บหน่วยความจำของวัตถุอัตโนมัติ (อาจรวมถึงพารามิเตอร์) ที่ระบุโดยซอร์สโค้ด C ++ ในความหมายของการใช้งานดังกล่าววัตถุเหล่านี้ "จัดสรร" เมื่อตัวควบคุมออกจากการเรียกใช้ฟังก์ชั่นเฟรมก็ไม่ต้องการอีกต่อไปมันมักจะถูกปล่อยออกมาโดยการคืนค่าตัวชี้สแต็กกลับสู่สถานะก่อนการโทร (บันทึกไว้ก่อนหน้านี้ตามแบบแผนการโทร) ซึ่งสามารถดูได้ว่า "ยกเลิกการจัดสรร" การดำเนินการเหล่านี้ทำให้บันทึกการเปิดใช้งานมีประสิทธิภาพโครงสร้างข้อมูล LIFO ดังนั้นจึงมักจะเรียกว่า " สแตก (โทร) "

เนื่องจากการใช้งาน C ++ ส่วนใหญ่ (โดยเฉพาะอย่างยิ่งเป้าหมายที่ใช้รหัสเนทีฟระดับ ISA และการใช้ภาษาแอสเซมบลีเป็นเอาท์พุทแบบทันที) จึงใช้กลยุทธ์ที่คล้ายคลึงกันเช่นนี้ การจัดสรรดังกล่าว (รวมถึงการจัดสรรคืน) ใช้รอบเครื่องและอาจมีค่าใช้จ่ายสูงเมื่อการเรียก (ไม่ได้รับการปรับให้เหมาะสม) เกิดขึ้นบ่อยครั้งถึงแม้ว่า microarchitectures CPU ที่ทันสมัยสามารถมีการปรับแต่งที่ซับซ้อนโดยฮาร์ดแวร์สำหรับรูปแบบรหัสทั่วไปกองเครื่องยนต์ในการใช้งาน PUSH/ POPคำแนะนำ)

แต่โดยทั่วไปแล้วมันเป็นความจริงที่ว่าค่าใช้จ่ายของการจัดสรรสแต็กเฟรมนั้นน้อยกว่าการเรียกใช้ฟังก์ชั่นการจัดสรรที่ดำเนินการฟรีสโตร์ (ยกเว้นว่ามันจะถูกปรับให้เหมาะสมโดยสิ้นเชิง)ซึ่งตัวมันเองมีหลายร้อย การดำเนินการ :-) เพื่อรักษาตัวชี้สแต็กและสถานะอื่น ๆ โดยทั่วไปฟังก์ชั่นการจัดสรรจะขึ้นอยู่กับ API ที่จัดทำโดยสภาพแวดล้อมที่โฮสต์ (เช่นรันไทม์จากระบบปฏิบัติการ) แตกต่างกับวัตถุประสงค์ของการถือวัตถุอัตโนมัติสำหรับการเรียกใช้ฟังก์ชันการจัดสรรดังกล่าวมีวัตถุประสงค์ทั่วไปดังนั้นจึงไม่มีโครงสร้างเฟรมเหมือนสแต็ก ตามเนื้อผ้าพวกเขาจัดสรรพื้นที่จากที่เก็บสระว่ายน้ำเรียกว่าฮีป (หรือหลายฮีป ) แตกต่างจาก "สแต็ค" แนวคิด "ฮีป" ที่นี่ไม่ได้ระบุโครงสร้างข้อมูลที่ใช้ . (BTW, call stack มักถูกจัดสรรด้วยขนาดคงที่หรือขนาดที่ผู้ใช้กำหนดจาก heap โดยสภาพแวดล้อมในโปรแกรมหรือการเริ่มต้นเธรด) ลักษณะของกรณีการใช้งานทำให้การจัดสรรและการจัดสรรคืนจาก heap มีความซับซ้อนมากขึ้น เฟรมสแต็ก) และเป็นไปได้ยากที่จะปรับให้เหมาะสมโดยฮาร์ดแวร์

ผลกระทบต่อการเข้าถึงหน่วยความจำ

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

เห็นพ้องด้วย

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

ประสิทธิภาพของอวกาศ

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

ข้อ จำกัด ของการจัดสรรสแต็ก

แม้ว่าการจัดสรรสแต็กมักจะเหนือกว่าในประสิทธิภาพมากกว่าการจัดสรรฮีปในความเป็นจริง แต่ก็ไม่ได้หมายความว่าการจัดสรรสแต็กสามารถแทนที่การจัดสรรฮีปได้เสมอ

ขั้นแรกไม่มีวิธีการจัดสรรพื้นที่บนสแต็กด้วยขนาดที่ระบุไว้ที่รันไทม์ในแบบพกพาด้วย ISO C ++ มีส่วนขยายที่ให้บริการโดยการใช้งานเช่นallocaและ VLA ของ G ++ (อาร์เรย์ความยาวผันแปร) แต่มีเหตุผลที่ควรหลีกเลี่ยง (IIRC แหล่ง Linux ลบการใช้ VLA เมื่อเร็ว ๆ นี้) (เช่นกันหมายเหตุ ISO C99 ทำหน้าที่บังคับ VLA แต่ ISO C11 เปลี่ยนการสนับสนุนเสริม)

ประการที่สองไม่มีวิธีที่เชื่อถือได้และพกพาในการตรวจสอบการหมดพื้นที่สแต็ก นี้มักจะเรียกว่าสแตกล้น(อืมรากศัพท์ของเว็บไซต์นี้)แต่อาจจะถูกต้องมากขึ้นสแต็คการใช้จ่ายเกิน ในความเป็นจริงสิ่งนี้มักทำให้การเข้าถึงหน่วยความจำไม่ถูกต้องและสถานะของโปรแกรมนั้นเสียหาย (... หรืออาจแย่กว่านั้นคือช่องโหว่ความปลอดภัย) ในความเป็นจริง, ISO C ++ มีแนวคิดของ "สแต็ค" และไม่ทำให้มันไม่ได้กำหนดพฤติกรรมเมื่อทรัพยากรหมด ระมัดระวังเกี่ยวกับจำนวนที่เหลือในวัตถุอัตโนมัติ

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

อย่างไรก็ตามบางครั้งอาจต้องการการเรียกซ้ำแบบเรียกซ้ำ ในการปรับใช้ภาษาที่ต้องการการสนับสนุนการโทรที่ไม่ทำงาน (ที่ความลึกการโทร จำกัด โดยหน่วยความจำทั้งหมดเท่านั้น) มันเป็นไปไม่ได้ที่จะใช้สแต็คการโทรเนทีฟ (ร่วมสมัย) โดยตรงเป็นเร็กคอร์ดการเปิดใช้งานภาษาเป้าหมาย เพื่อแก้ไขปัญหาจำเป็นต้องมีวิธีการอื่นในการสร้างบันทึกการเปิดใช้งาน ยกตัวอย่างเช่นSML / นิวเจอร์ซีย์อย่างชัดเจนจัดสรรเฟรมในกองและใช้สแต็คแคคตัส การจัดสรรเฟรมเร็กคอร์ดการเปิดใช้งานที่ซับซ้อนเช่นนี้มักจะไม่เร็วเท่ากับการเรียกเฟรมสแต็ก อย่างไรก็ตามหากมีการใช้ภาษาดังกล่าวต่อไปด้วยการรับประกันการเรียกซ้ำหางที่เหมาะสมการจัดสรรสแต็คโดยตรงในภาษาวัตถุ (นั่นคือ "วัตถุ" ในภาษาไม่ได้เก็บไว้เป็นข้อมูลอ้างอิง แต่ค่าดั้งเดิมดั้งเดิมซึ่งสามารถแมปแบบหนึ่งต่อหนึ่งแมปกับวัตถุ C ++ ที่ไม่ได้ใช้ร่วมกัน) มีความซับซ้อนมากกว่า โทษประสิทธิภาพโดยทั่วไป เมื่อใช้ C ++ เพื่อใช้ภาษาดังกล่าวเป็นการยากที่จะประเมินผลกระทบด้านประสิทธิภาพ


เช่นเดียวกับ stl น้อยลงและน้อยเต็มใจที่จะแตกต่างแนวคิดเหล่านี้ เป็ดหลายตัวใน cppcon2018 ก็ใช้heapบ่อยเช่นกัน
陳力

@ 陳力 "The heap" นั้นไม่คลุมเครือกับการใช้งานเฉพาะบางอย่างที่จำไว้ดังนั้นบางครั้งมันก็โอเค มันซ้ำซ้อน "โดยทั่วไป" แม้ว่า
FrankHB

Interop คืออะไร
陳力

@ 陳力ฉันหมายถึงการทำงานร่วมกันของรหัส "เนทีฟ" ใด ๆ ที่เกี่ยวข้องในซอร์ส C ++ ตัวอย่างเช่นแอสเซมบลีโค้ดอินไลน์ใด ๆ สิ่งนี้อาศัยสมมติฐาน (ของ ABI) ที่ไม่ครอบคลุมโดย C ++ COM interop (ขึ้นอยู่กับ ABI ของ Windows บางรุ่น) จะคล้ายกันมากหรือน้อยแม้ว่ามันจะเป็นกลางกับ C ++
FrankHB

2

มีจุดทั่วไปที่จะต้องทำเกี่ยวกับการเพิ่มประสิทธิภาพดังกล่าว

การปรับให้เหมาะสมที่คุณได้รับนั้นเป็นสัดส่วนกับจำนวนเวลาที่ตัวนับโปรแกรมอยู่ในรหัสนั้น

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

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


2

การจัดสรรสแต็กเกือบจะเร็วหรือเร็วกว่าการจัดสรรฮีปแม้ว่าจะเป็นไปได้แน่นอนที่ฮีปการจัดสรรจะใช้เทคนิคการจัดสรรสแต็กตาม

อย่างไรก็ตามมีปัญหาที่ใหญ่กว่าเมื่อจัดการกับประสิทธิภาพโดยรวมของการจัดสรรแบบสแต็กเทียบกับฮีป (หรือในแง่ที่ดีขึ้นเล็กน้อยการจัดสรรโลคอลและการจัดสรรภายนอก) โดยปกติการจัดสรรฮีป (ภายนอก) จะช้าเนื่องจากเป็นการจัดการกับการปันส่วนและรูปแบบการจัดสรรประเภทต่างๆ การลดขอบเขตของตัวจัดสรรที่คุณใช้อยู่ (ทำให้เป็นแบบโลคัลกับอัลกอริทึม / รหัส) จะมีแนวโน้มที่จะเพิ่มประสิทธิภาพโดยไม่มีการเปลี่ยนแปลงที่สำคัญ ตัวอย่างเช่นการเพิ่มโครงสร้างที่ดีกว่าให้กับรูปแบบการจัดสรรของคุณการบังคับให้มีการสั่งซื้อ LIFO สำหรับคู่การจัดสรรและการจัดสรรคืนสามารถปรับปรุงประสิทธิภาพของตัวจัดสรรโดยใช้ตัวจัดสรรในวิธีที่ง่ายและมีโครงสร้างมากขึ้น หรือคุณสามารถใช้หรือเขียนตัวจัดสรรที่ปรับสำหรับรูปแบบการจัดสรรเฉพาะของคุณ โปรแกรมส่วนใหญ่จัดสรรขนาดที่ไม่ต่อเนื่องไม่กี่ครั้งบ่อยครั้ง ดังนั้นฮีปที่ขึ้นอยู่กับ lookaside buffer ที่มีขนาดคงที่ (ควรเป็นที่รู้จัก) จะทำงานได้ดีมาก Windows ใช้ฮีปแบบกระจายตัวต่ำด้วยเหตุผลอย่างนี้

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


ฉันไม่เห็นด้วยกับประโยคแรกของคุณ
ไบรอัน beuning

2

อย่างที่คนอื่น ๆ พูดกันว่าการจัดสรรสแต็กโดยทั่วไปเร็วกว่ามาก

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

ตัวอย่างเช่นถ้าคุณจัดสรรบางสิ่งบางอย่างบนสแต็กจากนั้นใส่ลงในคอนเทนเนอร์มันจะเป็นการดีกว่าที่จะจัดสรรในฮีปและเก็บตัวชี้ไว้ในคอนเทนเนอร์ (เช่นด้วย std :: shared_ptr <>) สิ่งเดียวกันนี้เป็นจริงถ้าคุณกำลังส่งผ่านหรือคืนค่าวัตถุและสถานการณ์อื่น ๆ ที่คล้ายคลึงกัน

ประเด็นก็คือแม้ว่าการจัดสรรสแต็กมักจะดีกว่าการจัดสรรฮีปในหลายกรณี แต่บางครั้งหากคุณออกจากการจัดสรรสแต็กเมื่อไม่เหมาะกับรูปแบบการคำนวณมากที่สุด


2
class Foo {
public:
    Foo(int a) {

    }
}
int func() {
    int a1, a2;
    std::cin >> a1;
    std::cin >> a2;

    Foo f1(a1);
    __asm push a1;
    __asm lea ecx, [this];
    __asm call Foo::Foo(int);

    Foo* f2 = new Foo(a2);
    __asm push sizeof(Foo);
    __asm call operator new;//there's a lot instruction here(depends on system)
    __asm push a2;
    __asm call Foo::Foo(int);

    delete f2;
}

มันจะเป็นเช่นนี้ใน asm เมื่อคุณอยู่ในfuncนั้นf1และตัวชี้f2ได้รับการจัดสรรในสแต็ค (การจัดเก็บอัตโนมัติ) และโดยวิธีการที่ฟูf1(a1)มีผลการเรียนการสอนเกี่ยวกับตัวชี้สแต็ค ( esp) จะได้รับการจัดสรรหากfuncต้องการได้รับสมาชิกคำแนะนำและมันคืออะไรเช่นนี้f1 lea ecx [ebp+f1], call Foo::SomeFunc()สิ่งที่กองการจัดสรรอาจทำให้บางคนคิดว่าหน่วยความจำคือสิ่งFIFOที่FIFOเกิดขึ้นเมื่อคุณเข้าสู่ฟังก์ชั่นบางอย่างถ้าคุณอยู่ในฟังก์ชั่นและจัดสรรบางอย่างเช่นint i = 0ไม่มีการผลักเกิดขึ้น


1

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

ระบบปฏิบัติการเก็บรักษาส่วนต่าง ๆ ของหน่วยความจำว่างเป็นรายการที่เชื่อมโยงกับข้อมูล payload ซึ่งประกอบด้วยตัวชี้ไปยังที่อยู่เริ่มต้นของส่วนที่ว่างและขนาดของส่วนที่ว่าง ในการจัดสรรหน่วยความจำ X ไบต์รายการลิงก์จะถูกข้ามและแต่ละโน้ตจะถูกเยี่ยมชมตามลำดับตรวจสอบเพื่อดูว่าขนาดอย่างน้อย X หรือไม่เมื่อพบส่วนที่มีขนาด P> = X P จะแบ่งออกเป็นสองส่วน ขนาด X และ PX รายการที่เชื่อมโยงถูกอัพเดตและตัวชี้ไปยังส่วนแรกถูกส่งคืน

อย่างที่คุณเห็นการจัดสรรฮีปขึ้นอยู่กับปัจจัยที่อาจต้องการจำนวนหน่วยความจำที่คุณร้องขอหน่วยความจำที่แยกส่วนและอื่น ๆ


1

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

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

เมื่อคุณพูดถึง Metrowerks และ PPC ฉันคิดว่าคุณหมายถึง Wii ในกรณีนี้หน่วยความจำอยู่ในระดับพรีเมี่ยมและใช้วิธีการจัดสรรสแต็คทุกที่ที่เป็นไปได้รับประกันได้ว่าคุณจะไม่เสียหน่วยความจำในเศษ แน่นอนว่าการทำเช่นนี้ต้องใช้ความระมัดระวังมากกว่าวิธีการจัดสรรฮีปแบบ "ปกติ" ควรประเมินการแลกเปลี่ยนสำหรับแต่ละสถานการณ์


1

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

ตอนนี้เป็นตัวอย่างที่ไม่สามารถใช้สแต็กได้:

Proc P
{
  pointer x;
  Proc S
  {
    pointer y;
    y = allocate_some_data();
    x = y;
  }
}

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


0

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

หากคุณจริงจังกับแอพพลิเคชั่น VTune หรือใช้เครื่องมือทำโปรไฟล์ที่คล้ายกันและดูฮอตสปอต

Ketan


-1

ฉันต้องการจะบอกว่าจริง ๆ แล้วสร้างรหัสโดย GCC (ฉันจำได้ว่า VS ด้วย) ไม่มีค่าใช้จ่ายในการจัดสรรสแต็คไม่ได้มีค่าใช้จ่ายที่จะทำกองจัดสรร

พูดตามฟังก์ชั่นต่อไปนี้:

  int f(int i)
  {
      if (i > 0)
      {   
          int array[1000];
      }   
  }

ต่อไปนี้เป็นรหัสที่สร้าง:

  __Z1fi:
  Leh_func_begin1:
      pushq   %rbp
  Ltmp0:
      movq    %rsp, %rbp
  Ltmp1:
      subq    $**3880**, %rsp <--- here we have the array allocated, even the if doesn't excited.
  Ltmp2:
      movl    %edi, -4(%rbp)
      movl    -8(%rbp), %eax
      addq    $3880, %rsp
      popq    %rbp
      ret 
  Leh_func_end1:

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

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