การใช้ตัวแปรพอยน์เตอร์เป็นค่าใช้จ่ายในหน่วยความจำใช่หรือไม่


29

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


11
ประโยชน์ของการจัดสรรหน่วยความจำแบบไดนามิกมีมากกว่าค่าใช้จ่ายของตัวชี้อย่างมากมาย
James McLeod

32
คุณคิดว่าภาษาอื่น (Java, C #, ... ) จัดเก็บข้อมูลอ้างอิงกับวัตถุได้อย่างไร (คำแนะนำ: พวกเขาใช้ตัวชี้)
Erik Eidt

6
ตัวชี้อาจอยู่ในการลงทะเบียนหรือถูกส่งผ่านเป็นอาร์กิวเมนต์ ในทั้งสองกรณีไม่มีค่าใช้จ่ายหน่วยความจำที่ชัดเจน และตัวชี้อาจคำนวณได้ (เช่นผ่านตัวชี้เลขคณิต, ฟังก์ชันส่งคืนพอยน์เตอร์ ฯลฯ )
Basile Starynkevitch

9
วิธีการเกี่ยวกับการส่งผ่าน (ขนาดใหญ่) structโต้แย้งตามที่อยู่? หากคุณนับว่าเป็นตัวแปรพอยน์เตอร์มันเป็นสิ่งที่หลีกเลี่ยงไม่ได้สำหรับอัลกอริทึมมากมายและใช้พื้นที่น้อยกว่าการส่งผ่านstructตามค่า!
PJTraill

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

คำตอบ:


34

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

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

นอกจากนี้โจชัวเทย์เลอร์เตือนเราในความคิดเห็นพอยน์เตอร์ถูกใช้เพื่อส่งบางอย่างโดยการอ้างอิง เช่นstruct foo f; init_foo(&f);จะจัดสรรฉในกองแล้วโทรกับชี้ไปที่init_foo() structเป็นเรื่องธรรมดามาก (โปรดระวังอย่าส่งพอยน์เตอร์เหล่านั้น "ขึ้น") ใน C ++ คุณอาจเห็นสิ่งนี้ถูกทำโดยใช้ "การอ้างอิง" ( foo&) แทนที่จะเป็นตัวชี้ แต่การอ้างอิงนั้นไม่มีอะไรนอกจากพอยน์เตอร์ที่คุณอาจไม่เปลี่ยนแปลง จำนวนหน่วยความจำเท่ากัน

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

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

ดังนั้นตัวชี้จะไม่ได้ใช้เฉพาะในเวลาที่สำคัญการใช้งานหน่วยความจำต่ำพวกเขาจะใช้ทุกที่


5
"เหตุผลหลักที่ใช้พอยน์เตอร์สำหรับการจัดสรรหน่วยความจำแบบไดนามิก" ซึ่งอาจเป็นกรณีใน C ++ แต่ไม่จำเป็นต้องเป็น C ใน C ในพอยน์เตอร์พอยน์เตอร์เป็นวิธีเดียวที่จะส่งผ่านการอ้างอิง หากคุณไม่ต้องการคัดลอกโครงสร้างทั้งหมดคุณจะต้องมีตัวชี้ นั่นยังคงสมเหตุสมผลแม้ว่าคุณจะไม่มีการจัดสรรแบบไดนามิกเลยก็ตาม ตัวอย่างเช่นstruct foo f; init_foo(&f);จะจัดสรรให้fกับสแต็กจากนั้นโทรinit_fooด้วยตัวชี้ไปยังโครงสร้างนั้น นั่นเป็นเรื่องธรรมดามาก (โปรดระวังอย่าส่งตัวชี้ "ขึ้นไป")
Joshua Taylor

@JoshuaTaylor ที่ถูกต้องมากฉันลืมไปแล้ว ฉันขอแก้ไขคำตอบของฉันได้ไหม (นี่ถือว่าเป็นวิธีปฏิบัติที่ดีสำหรับโปรแกรมเมอร์ SE เพราะความคิดเห็นนั้นไม่สำคัญในขณะที่คำตอบไม่ใช่)
Mike Nakis

โดยรวมเพิ่มคำตอบของคุณ :)
Joshua Taylor

2
สิ่งนี้แสดงถึงค่าโสหุ้ยในตัวมันเองเนื่องจากบล็อกนี้จะมีส่วนหัว (ซ่อน) ซึ่งมักจะมีขนาดใหญ่เท่ากับตัวชี้ไม่กี่ตัว => ที่จริงแล้วการใช้งานที่ทันสมัยของmallocมีค่าใช้จ่ายส่วนหัวที่ต่ำมากขณะที่กลุ่มพวกเขาจัดสรรบล็อกใน "ถัง" ในทางตรงกันข้ามสิ่งนี้แปลเป็นการจัดสรรเกินโดยทั่วไป: คุณขอ 35 ไบต์และรับ 64 (โดยที่คุณไม่รู้) ดังนั้นจึงสิ้นเปลือง 29 ...
Matthieu M.

1
@MikeNakis: หน้าตาดีกว่าขอบคุณสำหรับการผสานกับฉัน :)
Matthieu เอ็ม

35

ดังนั้นนี่ไม่ใช่ค่าใช้จ่ายหน่วยความจำ?

แน่นอนว่าที่อยู่พิเศษ (โดยทั่วไปคือ 4/8 ไบต์ขึ้นอยู่กับโปรเซสเซอร์)

สิ่งนี้ได้รับการชดเชยอย่างไร?

มันไม่ใช่. หากคุณต้องการการชี้ทางอ้อมที่จำเป็นสำหรับพอยน์เตอร์คุณจะต้องจ่าย

พอยน์เตอร์ใช้ในเวลาที่แอปพลิเคชันหน่วยความจำเหลือน้อยหรือไม่?

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


1
@DavidGrinberg นั่นเป็นเพียงการสมมติว่าไม่มีการเพิ่มประสิทธิภาพของค่าตอบแทน
Darkhogg

3
ฉันใช้พอยน์เตอร์เมื่อเขียนแอปพลิเคชัน TSR สำหรับ DOS ที่ต้องพอดีกับ 15k ในยุคแปดสิบ ใช่แล้วมันถูกใช้ในแอพพลิเคชั่นหน่วยความจำต่ำ
Gort the Robot

1
@StevenBurnap คุณโทรหน่วยความจำต่ำ 15k สำหรับแอป TSR หรือไม่? ฉันเคยเขียนเครื่องมือ TSR ที่ใช้หน่วยความจำเพียง 16 ไบต์
kasperd

22
@kasperd: และในสมัยของฉันเรามีเลขศูนย์เท่านั้น
แจ็ค


11

ฉันไม่ได้หมุนแบบเดียวกันกับ Telastyn

globals ระบบในโปรเซสเซอร์ที่ฝังตัวอาจได้รับการแก้ไขด้วยที่อยู่ที่ระบุไว้แบบตายตัว

Globals ในโปรแกรมจะได้รับการจัดการเป็นออฟเซ็ตจากตัวชี้พิเศษที่ชี้ไปยังสถานที่ในหน่วยความจำที่จัดเก็บกลมและสแตติก

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

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


6

ใช่แน่นอน แต่มันเป็นการกระทำที่สมดุล

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

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


5

ในภาษาอย่าง C และ C ++ ในขณะที่ใช้พอยน์เตอร์กับตัวแปรเราต้องการที่ตั้งหน่วยความจำอีกหนึ่งแห่งเพื่อเก็บที่อยู่นั้น ดังนั้นนี่ไม่ใช่ค่าใช้จ่ายหน่วยความจำ?

คุณคิดว่าตัวชี้จะต้องเก็บไว้ นั่นไม่ใช่กรณีเสมอไป ตัวแปรทุกตัวจะถูกเก็บไว้ในที่อยู่หน่วยความจำบางส่วน ว่าคุณมีการประกาศให้เป็นlong long n = 5L;นี้จัดสรรที่เก็บข้อมูลสำหรับnที่อยู่บางแห่ง เราสามารถใช้ที่อยู่ที่จะทำสิ่งแฟนซีเช่นการจัดการกับส่วนของ*((char *) &n) = (char) 0xFF; nที่อยู่ของnไม่ได้เก็บไว้ที่ใดก็ได้เป็นค่าใช้จ่ายเพิ่มเติม

สิ่งนี้ได้รับการชดเชยอย่างไร?

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

พอยน์เตอร์ใช้ในเวลาที่แอปพลิเคชันหน่วยความจำเหลือน้อยหรือไม่?

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


ตัวแปรบางตัวไม่ได้เก็บไว้ในหน่วยความจำ แต่เฉพาะในการลงทะเบียน
Basile Starynkevitch

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

คอมไพเลอร์อาจได้รับอนุญาตให้เก็บตัวแปรเฉพาะบางตัวในรีจิสเตอร์เท่านั้นและคอมไพเลอร์ที่ปรับให้เหมาะสมที่สุดกำลังทำเช่นนั้น (ลองgcc -fverbose-asm -S -O2รวบรวมคอมไพล์ C บางตัว)
Basile Starynkevitch

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

5

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

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

แม้แต่ตัวแปรปกติของคุณก็มีรายการในตารางสัญลักษณ์ที่เก็บที่อยู่ซึ่งตัวแปรของคุณชี้ไป ดังนั้นฉันไม่คิดว่ามันสร้างค่าใช้จ่ายมากในแง่ของหน่วยความจำ (เพียง 4 หรือ 8 ไบต์) แม้แต่ภาษาอย่าง java ใช้พอยน์เตอร์ภายใน (อ้างอิง) พวกเขาก็ไม่ยอมให้คุณจัดการกับมันเพราะมันจะทำให้ JVM ปลอดภัยน้อยกว่า

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


3

ดังนั้นนี่ไม่ใช่ค่าใช้จ่ายหน่วยความจำ?

ใช่ .... ไม่ ... อาจจะ?

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

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

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

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

สิ่งนี้ได้รับการชดเชยอย่างไร?

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

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

พอยน์เตอร์ใช้ในเวลาที่แอปพลิเคชันหน่วยความจำเหลือน้อยหรือไม่?

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

กลับไปที่คำถามนี้:

สิ่งนี้ได้รับการชดเชยอย่างไร? (ตอนที่สอง)

ตัวชี้มักจะสกปรกราคาถูกเว้นแต่ว่าคุณจัดเก็บเหมือนล้านคน (ซึ่งยังคงเป็น * 8 เมกะไบต์บนเครื่อง 64 บิต)

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

ในกรณีที่ตัวชี้ราคาแพงไม่ใช่ตัวชี้ แต่:

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

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

การเข้าถึงหน่วยความจำ

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

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

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

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

การเพิ่มประสิทธิภาพการเข้าถึงตัวชี้โดยทางอ้อม

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

พอยน์เตอร์ไล่ตามสถานที่ต่างๆ

สมมติว่าเรามีรายการที่เชื่อมโยงเดี่ยว:

Foo->Bar->Baz->null

ปัญหาคือว่าถ้าเราจัดสรรโหนดเหล่านี้ทั้งหมดแยกจากกันกับตัวจัดสรรวัตถุประสงค์ทั่วไป (และอาจไม่ทั้งหมดในคราวเดียว) หน่วยความจำจริงอาจจะกระจายไปในลักษณะนี้ (แผนภาพง่าย):

ป้อนคำอธิบายรูปภาพที่นี่

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

ป้อนคำอธิบายรูปภาพที่นี่

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

ป้อนคำอธิบายรูปภาพที่นี่

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

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


1
"เลวทรามต่ำช้า 8 เมกะไบต์" เป็นขนาดปกติของแคช L3 ทั้งหมดในซีพียูสมัยใหม่
Ben Voigt

1
@ BenVoigt ฉันจะพยายามทำให้เข้าใจผิดเล็กน้อย แน่นอนว่าน่ากลัวถ้าตัวชี้แต่ละตัวชี้ไปที่หน่วยความจำ 32 บิตเช่น

@BenVoigt เพิ่มเชิงอรรถเพื่อพยายามอธิบายส่วนนั้น!

คำอธิบายนั้นดูดี
Ben Voigt

1

ดังนั้นนี่ไม่ใช่ค่าใช้จ่ายหน่วยความจำ?

มันเป็นค่าใช้จ่ายในความทรงจำจริง ๆ แต่มีขนาดเล็กมาก (จนถึงจุดที่ไม่มีนัยสำคัญ)

สิ่งนี้ได้รับการชดเชยอย่างไร?

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

พอยน์เตอร์ใช้ในเวลาที่แอปพลิเคชันหน่วยความจำเหลือน้อยหรือไม่?

ใช่.


0

คุณต้องการการใช้หน่วยความจำเพิ่มเติมเท่านั้น (โดยทั่วไปแล้ว 4-8 ไบต์ต่อตัวชี้) ในขณะที่คุณต้องการตัวชี้นั้น มีเทคนิคมากมายที่ทำให้ราคาไม่แพงมาก

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

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

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

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

โดยพื้นฐานแล้วคุณไม่สามารถกำจัดค่าใช้จ่ายทั้งหมดได้ อย่างไรก็ตามคุณสามารถควบคุมมันได้ โดยใช้เทคนิคอัลกอริทึมคุณสามารถลดจำนวนพอยน์เตอร์ที่คุณสามารถจัดเก็บได้ หากคุณกำลังใช้พอยน์เตอร์กับหน่วยความจำแบบไดนามิกคุณจะไม่ได้รับค่าใช้จ่ายในการรักษาตัวชี้ 1 ไปยังจุดหน่วยความจำแบบไดนามิกนั้นเพราะนั่นเป็นข้อมูลขั้นต่ำที่จำเป็นในการเข้าถึงทุกอย่างในบล็อกหน่วยความจำนั้น อย่างไรก็ตามในสถานการณ์ข้อ จำกัด ของหน่วยความจำ ultra-tight นี่เป็นกรณีพิเศษ (หน่วยความจำแบบไดนามิกและข้อ จำกัด ของหน่วยความจำแบบ ultra-tight มักจะไม่ปรากฏในสถานการณ์เดียวกัน)


0

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

ตัวอย่างหนึ่งของแอปพลิเคชั่นที่สำคัญเวลาคือสแต็กเครือข่าย สแต็กเครือข่ายที่ดีจะได้รับการออกแบบให้เป็น "zero copy" - และการทำเช่นนี้ต้องใช้พอยน์เตอร์ที่ฉลาด

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