การใช้คีย์เวิร์ด "stackalloc" ในทางปฏิบัติ


139

มีใครเคยใช้จริงstackallocขณะเขียนโปรแกรม C # บ้าง? ฉันรู้ว่าอะไรคืออะไร แต่ครั้งเดียวที่มันปรากฏในรหัสของฉันคือโดยบังเอิญเพราะ Intellisense แนะนำเมื่อฉันเริ่มพิมพ์staticเช่น

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

และเนื่องจากขนาดสแต็กสำหรับเธรดเดียวใน. Net คือ ~ 1Mb (แก้ไขฉันถ้าฉันผิด) ฉันจึงถูกสงวนไว้จากการใช้stackalloc.

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


5
เพิ่งสังเกตว่าSystem.Numbersใช้มันมากreferencesource.microsoft.com/#mscorlib/system/…
Slai

คำตอบ:


160

เหตุผลเดียวที่ใช้stackallocคือประสิทธิภาพ (สำหรับการคำนวณหรือการทำงานร่วมกัน) ด้วยการใช้stackallocแทนอาร์เรย์ที่จัดสรรฮีปคุณจะสร้างแรงดัน GC น้อยลง (GC ต้องทำงานน้อยลง) คุณไม่จำเป็นต้องตรึงอาร์เรย์ลงการจัดสรรเร็วกว่าอาร์เรย์ฮีปซึ่งเป็นอิสระโดยอัตโนมัติในเมธอด exit (อาร์เรย์ที่จัดสรรฮีปจะถูกยกเลิกการจัดสรรเมื่อ GC ทำงานเท่านั้น) นอกจากนี้การใช้stackallocแทนตัวจัดสรรแบบเนทีฟ (เช่น malloc หรือเทียบเท่า. net) คุณยังได้รับความเร็วและการยกเลิกการจัดสรรอัตโนมัติเมื่อออกจากขอบเขต

ประสิทธิภาพที่ชาญฉลาดหากคุณใช้stackallocคุณจะเพิ่มโอกาสในการแคชบน CPU ได้อย่างมากเนื่องจากพื้นที่ของข้อมูล


28
ตำแหน่งของข้อมูลจุดดี! นั่นคือสิ่งที่หน่วยความจำที่มีการจัดการจะไม่ค่อยบรรลุเมื่อคุณต้องการจัดสรรโครงสร้างหรืออาร์เรย์หลาย ๆ ขอบคุณ!
Groo

22
การจัดสรรฮีปมักจะเร็วกว่าสำหรับอ็อบเจ็กต์ที่มีการจัดการมากกว่าสำหรับอ็อบเจ็กต์ที่ไม่มีการจัดการเนื่องจากไม่มีรายการว่างให้สำรวจ CLR เพียงแค่เพิ่มตัวชี้ฮีป สำหรับตำแหน่งที่ตั้งการจัดสรรตามลำดับมีแนวโน้มที่จะจบลงด้วยการ colocated สำหรับกระบวนการที่มีการจัดการที่รันเป็นเวลานานเนื่องจากการบดอัดแบบฮีป
Shea

1
"จัดสรรได้เร็วกว่าฮีปอาร์เรย์" ทำไมล่ะ? แค่ท้องที่? ไม่ว่าจะด้วยวิธีใดก็เป็นเพียงตัวชี้ชนไม่ใช่หรือ?
Max Barraclough

2
@MaxBarraclough เนื่องจากคุณเพิ่มต้นทุน GC ในการจัดสรรฮีปตลอดอายุการใช้งานของแอปพลิเคชัน ต้นทุนการจัดสรรทั้งหมด = การจัดสรร + การจัดสรรตำแหน่งในกรณีนี้ตัวชี้ Bump + GC Heap เทียบกับตัวชี้ตัวชี้ + กองการลดตัวชี้
Pop Catalin

36

ฉันใช้ stackalloc เพื่อจัดสรรบัฟเฟอร์สำหรับงาน DSP เรียลไทม์ [ใกล้] เป็นกรณีที่เฉพาะเจาะจงมากซึ่งจำเป็นต้องมีประสิทธิภาพที่สอดคล้องกันมากที่สุด โปรดทราบว่ามีความแตกต่างระหว่างความสอดคล้องและปริมาณงานโดยรวม - ในกรณีนี้ฉันไม่ได้กังวลว่าการจัดสรรฮีปจะช้าเกินไปเพียง แต่ไม่มีปัจจัยกำหนดของการรวบรวมขยะ ณ จุดนั้นในโปรแกรม ฉันจะไม่ใช้มันใน 99% ของกรณี


25

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

ขนาดสแต็กเริ่มต้นสำหรับแอปพลิเคชัน vanilla .NET ธรรมดาคือ 1 MB แต่คุณสามารถเปลี่ยนแปลงได้ในส่วนหัว PE หากคุณกำลังเริ่มเธรดอย่างชัดเจนคุณสามารถกำหนดขนาดที่แตกต่างกันได้โดยใช้ตัวสร้างโอเวอร์โหลด สำหรับแอปพลิเคชัน ASP.NET ขนาดสแต็กเริ่มต้นคือ 256K เท่านั้นซึ่งเป็นสิ่งที่ควรคำนึงถึงหากคุณสลับไปมาระหว่างสองสภาพแวดล้อม


เป็นไปได้ไหมที่จะเปลี่ยนขนาดสแต็กเริ่มต้นจาก Visual Studio
กำหนดค่า

@configurator: ไม่เท่าที่ฉันรู้
Brian Rasmussen

20

การเริ่มต้น Stackalloc ของช่วงเวลา ใน C # เวอร์ชันก่อนหน้าผลลัพธ์ของ stackalloc สามารถเก็บไว้ในตัวแปรโลคัลตัวชี้เท่านั้น ใน C # 7.2 ตอนนี้ stackalloc สามารถใช้เป็นส่วนหนึ่งของนิพจน์และสามารถกำหนดเป้าหมายเป็นช่วงและสามารถทำได้โดยไม่ต้องใช้คีย์เวิร์ดที่ไม่ปลอดภัย ดังนั้นแทนที่จะเขียน

Span<byte> bytes;
unsafe
{
  byte* tmp = stackalloc byte[length];
  bytes = new Span<byte>(tmp, length);
}

คุณสามารถเขียนง่ายๆ:

Span<byte> bytes = stackalloc byte[length];

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

Span<byte> bytes = length <= 128 ? stackalloc byte[length] : new byte[length];
... // Code that operates on the Span<byte>

ที่มา: C # - All About Span: Exploring a New .NET Mainstay


4
ขอบคุณสำหรับทิป. ดูเหมือนว่า C # เวอร์ชันใหม่แต่ละเวอร์ชันจะเข้าใกล้ C ++ มากขึ้นเล็กน้อยซึ่งเป็นสิ่งที่ดี IMHO
Groo

2
ที่สามารถเห็นได้ที่นี่และที่นี่ , Spanเป็นอนิจจาไม่สามารถใช้ได้ใน .NET Framework 4.7.2 และแม้ไม่ได้อยู่ใน 4.8 ... ดังนั้นว่าคุณลักษณะภาษาใหม่ยังคงมีการใช้ที่ จำกัด สำหรับช่วงเวลาที่
Frederic

1
@Frederic: เห็นได้ชัด .NET Framework 4.8 เป็นรุ่นใหญ่สุดท้ายของกรอบ NET NET Core เวอร์ชันถัดไปจะถูกเรียกว่า. NET 5 (ไม่มี "คอร์" อีกต่อไปและเวอร์ชัน 4 จะถูกข้ามไปเพื่อหลีกเลี่ยงความสับสนกับเฟรมเวิร์ก) ดังนั้นอนาคตของ. NET จึงเป็น. NET Core และขณะนี้เห็นได้ชัดแล้วว่า. NET Framework แอปจะไม่ได้รับการอัปเดตนี้ ฉันยังเชื่อว่าแนวคิดนี้คือการลบ. NET Standard ในอนาคต (เนื่องจากจะมีเพียง ".NET" จากจุดนี้เป็นต้นไป)
กรู

2

มีคำตอบที่ดีในคำถามนี้ แต่ฉันแค่อยากจะชี้ให้เห็นว่า

Stackalloc สามารถใช้เพื่อเรียก API ดั้งเดิมได้

ฟังก์ชันเนทีฟจำนวนมากต้องการให้ผู้เรียกจัดสรรบัฟเฟอร์เพื่อให้ได้ผลลัพธ์ที่ส่งคืน ตัวอย่างเช่นฟังก์ชันCfGetPlaceholderInfoในcfapi.hมีลายเซ็นต่อไปนี้

HRESULT CfGetPlaceholderInfo(
HANDLE                    FileHandle,
CF_PLACEHOLDER_INFO_CLASS InfoClass,
PVOID                     InfoBuffer,
DWORD                     InfoBufferLength,
PDWORD                    ReturnedLength);

เพื่อที่จะเรียกมันใน C # ผ่าน interop

[DllImport("Cfapi.dll")]
public static unsafe extern HResult CfGetPlaceholderInfo(IntPtr fileHandle, uint infoClass, void* infoBuffer, uint infoBufferLength, out uint returnedLength);

คุณสามารถใช้ stackalloc

byte* buffer = stackalloc byte[1024];
CfGetPlaceholderInfo(fileHandle, 0, buffer, 1024, out var returnedLength);
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.