ไม่เคยเห็น "คุณลักษณะ" นี้จากที่อื่น ฉันรู้ว่าบิตที่ 32 ใช้สำหรับการรวบรวมขยะ แต่เหตุใดจึงเป็นเช่นนั้นสำหรับ ints เท่านั้นไม่ใช่สำหรับประเภทพื้นฐานอื่น ๆ
ไม่เคยเห็น "คุณลักษณะ" นี้จากที่อื่น ฉันรู้ว่าบิตที่ 32 ใช้สำหรับการรวบรวมขยะ แต่เหตุใดจึงเป็นเช่นนั้นสำหรับ ints เท่านั้นไม่ใช่สำหรับประเภทพื้นฐานอื่น ๆ
คำตอบ:
สิ่งนี้เรียกว่าการแสดงตัวชี้ที่ติดแท็กและเป็นเคล็ดลับการเพิ่มประสิทธิภาพทั่วไปที่ใช้ในล่าม VMs และระบบรันไทม์ที่แตกต่างกันมานานหลายทศวรรษ การใช้งาน Lisp เกือบทั้งหมดใช้พวกมัน, Smalltalk VMs, ตัวแปล Ruby จำนวนมากและอื่น ๆ
โดยปกติแล้วในภาษาเหล่านั้นคุณมักจะส่งคำชี้ไปยังวัตถุ อ็อบเจ็กต์เองประกอบด้วยส่วนหัวของอ็อบเจ็กต์ซึ่งมีข้อมูลเมตาของอ็อบเจ็กต์ (เช่นชนิดของอ็อบเจ็กต์คลาสของอ็อบเจ็กต์อาจมีข้อ จำกัด ในการควบคุมการเข้าถึงหรือหมายเหตุด้านความปลอดภัยเป็นต้น) จากนั้นข้อมูลอ็อบเจ็กต์จริง ดังนั้นจำนวนเต็มอย่างง่ายจะแสดงเป็นตัวชี้บวกกับวัตถุที่ประกอบด้วยข้อมูลเมตาและจำนวนเต็มจริง แม้จะมีการแทนค่าที่กะทัดรัดมาก แต่ก็มีค่าเช่น 6 ไบต์สำหรับจำนวนเต็มธรรมดา
นอกจากนี้คุณไม่สามารถส่งออบเจ็กต์จำนวนเต็มดังกล่าวไปยัง CPU เพื่อคำนวณเลขคณิตจำนวนเต็มอย่างรวดเร็ว ถ้าคุณต้องการที่จะเพิ่มสองจำนวนเต็มคุณจริงๆมีเพียงสองตัวชี้ซึ่งชี้ไปที่จุดเริ่มต้นของส่วนหัวของวัตถุของทั้งสองจำนวนเต็มวัตถุที่คุณต้องการเพิ่ม ดังนั้นก่อนอื่นคุณต้องดำเนินการเลขคณิตจำนวนเต็มบนตัวชี้แรกเพื่อเพิ่มค่าชดเชยลงในวัตถุที่เก็บข้อมูลจำนวนเต็ม จากนั้นคุณต้องยกเลิกการอ้างอิงที่อยู่นั้น ทำเช่นเดียวกันอีกครั้งกับจำนวนเต็มที่สอง ตอนนี้คุณมีจำนวนเต็มสองจำนวนที่คุณสามารถขอให้ CPU เพิ่มได้ แน่นอนตอนนี้คุณต้องสร้างวัตถุจำนวนเต็มใหม่เพื่อเก็บผลลัพธ์ไว้
ดังนั้นเพื่อที่จะดำเนินการ เพิ่มจำนวนเต็มหนึ่งครั้งคุณต้องทำการบวกจำนวนเต็มสามตัวบวกการหักล้างตัวชี้สองตัวบวกการสร้างวัตถุหนึ่ง และคุณใช้เวลาเกือบ 20 ไบต์
อย่างไรก็ตามเคล็ดลับคือด้วยชนิดของค่าที่ไม่เปลี่ยนรูปที่เรียกว่าจำนวนเต็มคุณมักจะไม่จำเป็นต้องมีข้อมูลเมตาทั้งหมดในส่วนหัวของวัตถุ: คุณสามารถทิ้งสิ่งนั้นทั้งหมดออกไปและทำการสังเคราะห์ (ซึ่งก็คือ VM-nerd- พูดเพื่อ "ปลอม") เมื่อใครก็ตามที่สนใจที่จะมอง จำนวนเต็มจะมีคลาสเสมอInteger
ไม่จำเป็นต้องจัดเก็บข้อมูลนั้นแยกกัน หากมีคนใช้การสะท้อนเพื่อหาคลาสของจำนวนเต็มคุณเพียงแค่ตอบกลับInteger
และจะไม่มีใครรู้เลยว่าคุณไม่ได้เก็บข้อมูลนั้นไว้ในส่วนหัวของออบเจ็กต์จริง ๆ และในความเป็นจริงไม่มีแม้แต่ส่วนหัวของวัตถุ (หรือ วัตถุ).
ดังนั้นเคล็ดลับคือการจัดเก็บค่าของวัตถุภายในตัวชี้ไปยังวัตถุโดยยุบทั้งสองเป็นหนึ่งอย่างมีประสิทธิภาพ
มีซีพียูที่มีพื้นที่เพิ่มเติมภายในตัวชี้ (เรียกว่า แท็กบิต ) ที่ช่วยให้คุณสามารถจัดเก็บข้อมูลเพิ่มเติมเกี่ยวกับตัวชี้ภายในตัวชี้ได้ ข้อมูลเพิ่มเติมเช่น "นี่ไม่ใช่ตัวชี้ แต่เป็นจำนวนเต็ม" ตัวอย่างเช่น Burroughs B5000, Lisp Machines ต่างๆหรือ AS / 400 น่าเสียดายที่ซีพียูกระแสหลักส่วนใหญ่ไม่มีคุณสมบัตินั้น
อย่างไรก็ตามมีทางออก: ซีพียูกระแสหลักส่วนใหญ่ทำงานช้าลงอย่างมากเมื่อที่อยู่ไม่อยู่ในแนวขอบของคำ บางคนไม่รองรับการเข้าถึงที่ไม่ตรงแนวเลยด้วยซ้ำ
สิ่งที่หมายถึงนี้ก็คือว่าในทางปฏิบัติทุกตัวชี้จะหารด้วย 4 ซึ่งหมายความว่าพวกเขาจะมักจะจบลงด้วยสอง0
บิต สิ่งนี้ช่วยให้เราสามารถแยกความแตกต่างระหว่างพอยน์เตอร์จริง (ที่ลงท้ายด้วย00
) และพอยน์เตอร์ซึ่งเป็นจำนวนเต็มปลอม (ที่ลงท้ายด้วย1
) และมันยังทิ้งพอยน์เตอร์ทั้งหมดที่ทำให้เรา10
ทำอย่างอื่นได้ฟรี นอกจากนี้ระบบปฏิบัติการที่ทันสมัยส่วนใหญ่สงวนที่อยู่ที่ต่ำมากสำหรับตัวมันเองซึ่งทำให้เรามีพื้นที่อื่นในการยุ่งเหยิง (พอยน์เตอร์ที่ขึ้นต้นด้วยพูด 24 0
วินาทีและลงท้ายด้วย00
)
ดังนั้นคุณสามารถเข้ารหัสจำนวนเต็ม 31 บิตเป็นตัวชี้ได้โดยเพียงแค่เลื่อนไปทางซ้าย 1 บิตแล้วเพิ่ม1
เข้าไป และคุณสามารถดำเนินการได้อย่างรวดเร็วคำนวณเลขคณิตจำนวนเต็มด้วยการเปลี่ยนเลขคณิตให้เหมาะสม (บางครั้งก็ไม่จำเป็นด้วยซ้ำ)
เราจะทำอย่างไรกับพื้นที่ที่อยู่อื่น ๆ ดีตัวอย่างทั่วไปรวมถึงการเข้ารหัสfloat
ในพื้นที่ที่อยู่อื่น ๆ ที่มีขนาดใหญ่และจำนวนของวัตถุพิเศษเช่นtrue
, false
, nil
, 127 อักขระ ASCII บางที่ใช้กันทั่วไปสายระยะสั้นรายการที่ว่างเปล่าวัตถุว่างอาร์เรย์ที่ว่างเปล่าและอื่น ๆ ที่อยู่ใกล้0
ที่อยู่
ตัวอย่างเช่นในล่าม MRI, YARV และ Rubinius Ruby จำนวนเต็มถูกเข้ารหัสตามที่ฉันอธิบายไว้ข้างต้นfalse
ถูกเข้ารหัสเป็นที่อยู่0
(ซึ่งก็เกิดขึ้นได้เช่นกันเพื่อเป็นตัวแทนของfalse
C) true
เป็นที่อยู่2
(ซึ่งก็เป็นเช่นนั้น การเป็นตัวแทนของ C true
ขยับโดยหนึ่งบิต) และเป็นnil
4
int
OCaml
ดูส่วน "การแทนค่าจำนวนเต็มแท็กบิตค่าที่จัดสรรฮีป" ของhttps://ocaml.org/learn/tutorials/performance_and_profiling.htmlสำหรับคำอธิบายที่ดี
คำตอบสั้น ๆ ก็คือเพื่อประสิทธิภาพ เมื่อส่งอาร์กิวเมนต์ไปยังฟังก์ชันจะถูกส่งเป็นจำนวนเต็มหรือตัวชี้ ในระดับภาษาระดับเครื่องไม่มีวิธีใดที่จะบอกได้ว่ารีจิสเตอร์มีจำนวนเต็มหรือตัวชี้เป็นเพียงค่า 32 หรือ 64 บิต ดังนั้นเวลาทำงานของ OCaml จะตรวจสอบบิตแท็กเพื่อดูว่าสิ่งที่ได้รับเป็นจำนวนเต็มหรือตัวชี้ หากตั้งค่าบิตแท็กค่าจะเป็นจำนวนเต็มและจะถูกส่งผ่านไปยังโอเวอร์โหลดที่ถูกต้อง มิฉะนั้นจะเป็นการค้นหาตัวชี้และประเภท
ทำไมจำนวนเต็มจึงมีแท็กนี้ เพราะอย่างอื่นจะถูกส่งผ่านเป็นตัวชี้ สิ่งที่ส่งผ่านเป็นจำนวนเต็มหรือตัวชี้ไปยังชนิดข้อมูลอื่น ๆ ด้วยบิตแท็กเดียวสามารถมีได้สองกรณีเท่านั้น
ไม่ใช่ "ใช้สำหรับเก็บขยะ" ใช้สำหรับแยกแยะภายในระหว่างตัวชี้และจำนวนเต็มที่ไม่มีกล่อง
ฉันต้องเพิ่มลิงค์นี้เพื่อช่วยให้ OP เข้าใจมากขึ้นประเภททศนิยม 63 บิตสำหรับ OCaml 64 บิต
แม้ว่าชื่อของบทความจะดูเหมือนเกี่ยวกับfloat
แต่ก็พูดถึงไฟล์extra 1 bit
รันไทม์ OCaml อนุญาตให้มีความหลากหลายผ่านการแสดงประเภทที่เหมือนกัน ค่า OCaml ทุกค่าจะแสดงเป็นคำ ๆ เดียวดังนั้นจึงเป็นไปได้ที่จะมีการนำไปใช้งานเพียงคำเดียวสำหรับพูดว่า“ รายการของสิ่งต่างๆ” พร้อมด้วยฟังก์ชันในการเข้าถึง (เช่น List.length) และสร้าง (เช่น List.map) รายการเหล่านี้ ที่ทำงานเหมือนกันไม่ว่าจะเป็นรายการ ints ของโฟลตหรือรายการชุดจำนวนเต็ม
สิ่งใดที่ไม่พอดีกับคำจะถูกจัดสรรในบล็อกในฮีป คำที่แสดงถึงข้อมูลนี้จะเป็นตัวชี้ไปยังบล็อก เนื่องจากฮีปมีเพียงกลุ่มคำเท่านั้นตัวชี้เหล่านี้ทั้งหมดจึงถูกจัดแนว: บิตที่มีนัยสำคัญน้อยที่สุดจะไม่ได้ตั้งค่าเสมอ
ตัวสร้างแบบไม่มีอาร์กิวเมนต์ (เช่นประเภทผลไม้ = แอปเปิ้ล | ส้ม | กล้วย) และจำนวนเต็มไม่ได้แสดงถึงข้อมูลจำนวนมากที่จำเป็นต้องจัดสรรในฮีป การเป็นตัวแทนของพวกเขาไม่มีกล่อง ข้อมูลจะอยู่ภายในคำที่อาจเป็นตัวชี้ได้โดยตรง ดังนั้นในขณะที่ลิสต์ของลิสต์เป็นลิสต์ของพอยน์เตอร์ แต่ลิสต์ของ int จะมีอินสแตนซ์ที่มีทิศทางน้อยกว่า ฟังก์ชันการเข้าถึงและการสร้างรายการไม่สังเกตเห็นเนื่องจาก ints และ pointers มีขนาดเท่ากัน
ถึงกระนั้น Garbage Collector ก็ต้องสามารถจดจำพอยน์เตอร์จากจำนวนเต็มได้ ตัวชี้จะชี้ไปที่บล็อกที่มีรูปทรงดีในฮีปซึ่งเป็นไปตามนิยามที่ยังมีชีวิตอยู่ (เนื่องจาก GC กำลังเยี่ยมชม) และควรทำเครื่องหมายไว้ จำนวนเต็มสามารถมีค่าใด ๆ ก็ได้และหากไม่ใช้มาตรการป้องกันอาจมีลักษณะเหมือนตัวชี้โดยบังเอิญ สิ่งนี้อาจทำให้บล็อกที่ตายแล้วดูมีชีวิต แต่ที่แย่กว่านั้นก็คือทำให้ GC เปลี่ยนบิตในสิ่งที่คิดว่าเป็นส่วนหัวของบล็อกสดเมื่อเป็นจริงตามจำนวนเต็มที่ดูเหมือนตัวชี้และทำให้ผู้ใช้สับสน ข้อมูล.
นี่คือเหตุผลว่าทำไมจำนวนเต็มที่ไม่มีกล่องจึงให้ 31 บิต (สำหรับ OCaml 32 บิต) หรือ 63 บิต (สำหรับ OCaml 64 บิต) ให้กับโปรแกรมเมอร์ OCaml ในการเป็นตัวแทนเบื้องหลังจะมีการตั้งค่าบิตที่มีนัยสำคัญน้อยที่สุดที่มีจำนวนเต็มเสมอเพื่อแยกความแตกต่างจากตัวชี้ จำนวนเต็ม 31 หรือ 63 บิตนั้นค่อนข้างผิดปกติดังนั้นใครก็ตามที่ใช้ OCaml จะรู้เรื่องนี้ สิ่งที่ผู้ใช้ OCaml มักไม่ทราบคือเหตุใดจึงไม่มีประเภทลอยตัวแบบไม่มีกล่อง 63 บิตสำหรับ OCaml 64 บิต
เหตุใด int ใน OCaml จึงมีเพียง 31 บิต
โดยพื้นฐานแล้วเพื่อให้ได้ประสิทธิภาพที่ดีที่สุดเท่าที่จะเป็นไปได้ในสุภาษิตทฤษฎีบท Coq ซึ่งการดำเนินการที่โดดเด่นคือการจับคู่รูปแบบและชนิดข้อมูลที่โดดเด่นเป็นประเภทตัวแปร การแสดงข้อมูลที่ดีที่สุดพบว่าเป็นการแสดงที่เหมือนกันโดยใช้แท็กเพื่อแยกแยะตัวชี้จากข้อมูลที่ไม่มีกล่อง
แต่เหตุใดจึงเป็นเช่นนั้นสำหรับ ints เท่านั้นไม่ใช่สำหรับประเภทพื้นฐานอื่น ๆ
int
ไม่เพียง แต่ ประเภทอื่น ๆ เช่นchar
และ enums ใช้การแสดงแท็กเดียวกัน