เหตุใด int ใน OCaml จึงมีเพียง 31 บิต


115

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


10
โปรดทราบว่าในระบบปฏิบัติการ 64 บิต int ใน OCaml คือ 63 บิตไม่ใช่ 31 ซึ่งจะช่วยขจัดปัญหาในทางปฏิบัติส่วนใหญ่ (เช่นขีด จำกัด ขนาดอาร์เรย์) ของแท็กบิต และแน่นอนว่ามีประเภท int32 หากคุณต้องการจำนวนเต็ม 32 บิตจริงสำหรับอัลกอริทึมมาตรฐานบางอย่าง
Porculus

1
nekoVM ( nekovm.org ) ยังมี ints 31 บิตจนกระทั่งเมื่อไม่นานมานี้
TheHippo

คำตอบ:


244

สิ่งนี้เรียกว่าการแสดงตัวชี้ที่ติดแท็กและเป็นเคล็ดลับการเพิ่มประสิทธิภาพทั่วไปที่ใช้ในล่าม 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(ซึ่งก็เกิดขึ้นได้เช่นกันเพื่อเป็นตัวแทนของfalseC) trueเป็นที่อยู่2(ซึ่งก็เป็นเช่นนั้น การเป็นตัวแทนของ C trueขยับโดยหนึ่งบิต) และเป็นnil4


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

5
@threeFourOneSixOneThree คำตอบนี้ไม่ถูกต้องทั้งหมดสำหรับ OCaml เนื่องจากใน OCaml คำตอบนี้ไม่เคยเกิดขึ้น OCaml ไม่ใช่ภาษาเชิงวัตถุเช่น Smalltalk หรือ Java ไม่ต้องมีเหตุผลที่จะเรียกวิธีการที่ตารางใด ๆ intOCaml
Pascal Cuoq

เครื่องมือ V8 ของ Chrome ยังใช้ตัวชี้ที่ติดแท็กและจัดเก็บจำนวนเต็ม 31 บิตซึ่งเรียกว่าsmi (Small Integer)เป็นการเพิ่มประสิทธิภาพ \
phuclv

@phuclv: ไม่น่าแปลกใจแน่นอน เช่นเดียวกับ HotSpot JVM V8 จะขึ้นอยู่กับ Animorphic Smalltalk VM ซึ่งจะขึ้นอยู่กับ Self VM และ V8 ได้รับการพัฒนาโดย (บางคน) คนเดียวกับที่พัฒนา HotSpot JVM, Animorphic Smalltalk VM และ Self VM โดยเฉพาะอย่างยิ่ง Lars Bak ทำงานกับสิ่งเหล่านี้ทั้งหมดรวมถึง Smalltalk VM ของเขาเองที่เรียกว่า OOVM ดังนั้นจึงไม่น่าแปลกใจเลยที่ V8 ใช้เทคนิคที่รู้จักกันดีจากโลก Smalltalk เนื่องจาก Smalltalkers สร้างขึ้นโดยใช้เทคโนโลยี Smalltalk
Jörg W Mittag

28

ดูส่วน "การแทนค่าจำนวนเต็มแท็กบิตค่าที่จัดสรรฮีป" ของhttps://ocaml.org/learn/tutorials/performance_and_profiling.htmlสำหรับคำอธิบายที่ดี

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

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


1
"คำตอบสั้น ๆ คือเพื่อประสิทธิภาพ". โดยเฉพาะประสิทธิภาพของ Coq ประสิทธิภาพของสิ่งอื่น ๆ เกือบทั้งหมดต้องทนทุกข์ทรมานจากการตัดสินใจออกแบบนี้
JD

17

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


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

2
ข้อมูลนั้นคือสิ่งที่ GC ต้องการเพื่อนำทางกราฟตัวชี้
Tobu

"ใช้สำหรับแยกแยะภายในระหว่างตัวชี้และจำนวนเต็มที่ไม่มีกล่อง" มีอะไรใช้อย่างอื่นนอกเหนือจาก GC หรือไม่?
JD

13

ฉันต้องเพิ่มลิงค์นี้เพื่อช่วยให้ 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 บิต


3

เหตุใด int ใน OCaml จึงมีเพียง 31 บิต

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

แต่เหตุใดจึงเป็นเช่นนั้นสำหรับ ints เท่านั้นไม่ใช่สำหรับประเภทพื้นฐานอื่น ๆ

intไม่เพียง แต่ ประเภทอื่น ๆ เช่นcharและ enums ใช้การแสดงแท็กเดียวกัน

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