อะไรคือความท้าทายที่เกี่ยวข้องกับการพิมพ์ในการเขียนคอมไพเลอร์สำหรับภาษาที่พิมพ์แบบไดนามิก?


9

ในการพูดคุยครั้งนี้ Guido van Rossum กำลังพูดคุย (27:30) เกี่ยวกับความพยายามในการเขียนคอมไพเลอร์สำหรับรหัสไพ ธ อน

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

อะไรคือความท้าทาย (ที่เป็นไปได้) ที่เกี่ยวข้องกับการพิมพ์ในการเขียนคอมไพเลอร์สำหรับภาษาที่พิมพ์แบบไดนามิกเช่น Python?


ในกรณีนี้การพิมพ์แบบไดนามิกไม่ได้เป็นปัญหาใหญ่ที่สุด สำหรับไพ ธ อนมันเป็นการกำหนดขอบเขตแบบไดนามิก
SK-logic

เป็นที่น่าสังเกตว่าคนอื่น ๆ แย้งว่าการสร้างการพิมพ์แบบไดนามิกลงในแพลตฟอร์มเป็นคำตอบที่ถูกต้องที่นี่ Microsoft ใส่เงินจำนวนมากลงในDLRด้วยเหตุผลนี้ - และ NeXT / Apple ได้รับครึ่งทางใน bandwagon นั้นมานานหลายทศวรรษ นั่นไม่ได้ช่วย CPython แต่ IronPython พิสูจน์ว่าคุณสามารถรวบรวม Python แบบคงที่ได้อย่างมีประสิทธิภาพและ PyPy พิสูจน์ว่าคุณไม่จำเป็นต้องทำ
abarnert

2
@ SK-logic การกำหนดขอบเขตแบบไดนามิกใน Python? ล่าสุดฉันตรวจสอบโครงสร้างทั้งหมดในภาษาใช้การกำหนดขอบเขตศัพท์

1
@ SK- ลอจิกคุณสามารถสร้างรหัสแบบไดนามิกและดำเนินการได้ แต่รหัสนั้นยังทำงานขอบเขตเชิงศัพท์ สำหรับตัวแปรเดี่ยวทุกตัวในโปรแกรม Python คุณสามารถกำหนดขอบเขตของตัวแปรได้อย่างง่ายดายเพียงตรวจสอบ AST คุณอาจจะคิดถึงexecคำแถลงซึ่งหายไปตั้งแต่ 3.0 และด้วยเหตุนี้นอกเหนือจากการพิจารณาของฉัน (และอาจจะเป็นกุยโดเนื่องจากการพูดคุยจากปี 2012) คุณยกตัวอย่างได้ไหม และคำจำกัดความของคุณของ "การกำหนดขอบเขตแบบไดนามิก" ถ้าเป็น [ต่างจากของฉัน] (en.wikipedia.org/wiki/Dynamic_scoping)

1
@ SK-ตรรกะสิ่งเดียวที่เป็นรายละเอียดการดำเนินงานสำหรับฉันก็คือการเปลี่ยนแปลงค่าตอบแทนของlocals()persisting localsทั่วโทรไป สิ่งที่บันทึกไว้และไม่ใช่รายละเอียดการใช้งานอย่างแน่นอนคือไม่สามารถเปลี่ยนแปลงlocalsหรือglobalsเปลี่ยนแปลงขอบเขตการค้นหาของตัวแปรแต่ละตัวสำหรับการใช้งานตัวแปรทุกครั้งขอบเขตที่อ้างอิงจะถูกกำหนดแบบคงที่ ซึ่งทำให้ขอบเขตอย่างแน่นอนศัพท์ (และ btw evalและexecไม่มีรายละเอียดการใช้งานอย่างแน่นอน - ดูคำตอบของฉัน!)

คำตอบ:


16

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

ง่ายในการติดตั้ง Python 90% (ไม่ผ่านเกณฑ์ 1) และรวดเร็วในการใช้งาน ในทำนองเดียวกันมันเป็นเรื่องง่ายที่จะสร้างตัวแปร Python ที่เร็วขึ้นด้วยการพิมพ์แบบสแตติก การปรับใช้ 100% นั้นง่าย (ตราบเท่าที่การใช้ภาษาที่ซับซ้อนนั้นง่าย) แต่จนถึงทุก ๆ วิธีที่ง่ายในการนำไปใช้นั้นจะค่อนข้างช้า (ไม่ผ่านเกณฑ์ 3)

การใช้ล่ามและ JITที่ถูกต้องนั้นใช้ทั้งภาษาและเร็วกว่าสำหรับบางโค้ดกลายเป็นไปได้แม้ว่าจะยากขึ้นอย่างมาก (เทียบกับ PyPy) และดังนั้นถ้าคุณทำการสร้างคอมไพเลอร์ JIT โดยอัตโนมัติ (Psyco ทำได้โดยไม่มีมัน แต่มีข้อ จำกัด มากในโค้ดที่สามารถเพิ่มความเร็วได้) แต่โปรดทราบว่านี่ไม่ใช่ขอบเขตอย่างชัดเจนเนื่องจากเรากำลังพูดถึงสเตติกคอมไพเลอร์ (aka ล่วงหน้า) ฉันเพียงแค่พูดถึงสิ่งนี้เพื่ออธิบายว่าทำไมวิธีการของมันจึงไม่สามารถใช้งานกับคอมไพเลอร์แบบสแตติก (หรืออย่างน้อยก็ไม่มีตัวอย่างที่มีอยู่เดิม): ก่อนอื่นต้องทำการตีความและสังเกตโปรแกรมจากนั้นสร้างรหัสสำหรับการวนซ้ำ เส้นทาง) จากนั้นปรับค่านรกให้เหมาะสมตามสมมติฐานที่เป็นจริงสำหรับการทำซ้ำเฉพาะนั้น (หรืออย่างน้อยไม่ใช่การทำซ้ำที่เป็นไปได้ทั้งหมด) ความคาดหวังคือการประมวลผลในภายหลังของรหัสนั้นจะตรงกับความคาดหวังและทำให้ได้รับประโยชน์จากการปรับให้เหมาะสม มีการเพิ่มการตรวจสอบ (ค่อนข้างถูก) บางอย่างเพื่อรับรองความถูกต้อง ในการทำสิ่งนี้คุณต้องมีความคิดในสิ่งที่ต้องทำและการใช้งานที่ช้า แต่โดยทั่วไปกลับไปใช้ คอมไพเลอร์ AOT ไม่มี พวกเขาไม่สามารถเชี่ยวชาญได้ทั้งหมดขึ้นอยู่กับรหัสที่พวกเขาไม่สามารถมองเห็นได้ (เช่นรหัสที่โหลดแบบไดนามิก) และผู้เชี่ยวชาญอย่างไม่ระมัดระวังหมายถึงการสร้างรหัสเพิ่มเติมซึ่งมีจำนวนของปัญหา (การใช้ icache, ขนาดไบนารี, เวลารวบรวม, สาขาเพิ่มเติม)

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

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

ทำไมเป็นอย่างนั้น? คอมไพเลอร์จะลบความสามารถในการเรียกใช้งานโค้ด Python ที่รันไทม์ (ไม่ผ่านเกณฑ์ 1) หรือไม่ได้ตั้งสมมติฐานใด ๆ ที่สามารถทำให้โมฆะโดยรหัส Python ใด ๆ เลย แต่น่าเสียดายที่มีทุกอย่างสวยมากมีประโยชน์สำหรับการเพิ่มประสิทธิภาพของโปรแกรม: Globals รวมทั้งฟังก์ชั่นสามารถฟื้นตัวเรียนสามารถกลายพันธุ์หรือแทนที่ทั้งหมดโมดูลสามารถแก้ไขได้โดยพลเกินไปนำเข้าสามารถแย่งชิงในหลายวิธีเป็นต้นสายเดียวผ่านไปeval, exec, __import__หรือฟังก์ชั่นอื่น ๆ อีกมากมายอาจดำเนินการใด ๆ ที่ ซึ่งหมายความว่าแทบจะไม่สามารถใช้การปรับแต่งขนาดใหญ่ให้เกิดประโยชน์ด้านประสิทธิภาพเพียงเล็กน้อย (เกณฑ์ความล้มเหลว 3) กลับไปที่ย่อหน้าด้านบน


4

ปัญหาที่ยากที่สุดคือการค้นหาว่าทุกอย่างมีประเภทอย่างไรในเวลาใดก็ตาม

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

ใน Python มันสามารถ นี่คือ Python ที่น่ากลัว แต่ถูกกฎหมาย:

i = 2
x = 3 + i

def prn(s):
    print(s)

i = prn
i(x)

ตอนนี้ตัวอย่างนี้ค่อนข้างงี่เง่า แต่มันแสดงให้เห็นถึงแนวคิดทั่วไป

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

PyPy ใช้การรวบรวมแบบ Just-In-Time หลังจากดูว่าโค้ดทำอะไรและทำให้ PyPy เร่งความเร็วได้มากขึ้น PyPy สามารถดูลูปและตรวจสอบว่าทุกครั้งที่ลูปรันตัวแปรfooจะเป็นจำนวนเต็มเสมอ จากนั้น PyPy สามารถปรับรหัสที่ค้นหาประเภทของการfooส่งผ่านลูปได้อย่างเหมาะสมและบ่อยครั้งที่สามารถกำจัดวัตถุ Python ที่แสดงถึงจำนวนเต็มและfooสามารถกลายเป็นตัวเลขที่อยู่ในทะเบียนบน CPU นี่คือวิธีที่ PyPy สามารถทำได้เร็วกว่า CPython CPython ทำการค้นหาชนิดเร็วที่สุดเท่าที่จะเป็นไปได้ แต่ไม่แม้แต่ทำการค้นหาก็ยิ่งเร็วขึ้น

ฉันไม่ทราบรายละเอียด แต่ฉันจำได้ว่ามีโครงการหนึ่งชื่อ Unladen Swallow ที่พยายามใช้เทคโนโลยีคอมไพเลอร์สแตติกเพื่อเร่งความเร็ว Python (ใช้ LLVM) คุณอาจต้องการ Google ในการค้นหา Unladen Swallow และดูว่าคุณสามารถค้นหาการสนทนาว่าทำไมมันไม่ได้ผลตามที่หวัง


Unladen Swallow ไม่ได้เกี่ยวกับการรวบรวมแบบคงที่หรือประเภทคงที่ การไปในที่สุดก็มีประสิทธิภาพในการแปลพอร์ต CPython ด้วยไดนามิก - เนสต์ไปที่ LLVM พร้อม JIT ใหม่ที่แปลกใหม่ (เช่น Parrot หรือ DLR สำหรับ. NET หรือ PyPy จริงๆ) แม้ว่าสิ่งที่พวกเขาจะลงเอยนั้นจริง ๆ การทำคือพบการปรับแต่งในท้องถิ่นจำนวนมากภายใน CPython (บางอันทำให้มันกลายเป็นฉีด 3.x) Shedskin น่าจะเป็นโครงการที่คุณคิดว่าใช้การอนุมานแบบคงที่เพื่อรวบรวม Python แบบคงที่ (แม้ว่าจะเป็น C ++ ไม่ใช่โดยตรงกับรหัสเนทีฟ)
abarnert

หนึ่งในนักเขียน Unladen Swallow ที่ชื่อ Reid Kleckner ได้โพสต์Unladen Swallow Retrospectiveซึ่งอาจคุ้มค่ากับการอ่านในบริบทนี้
abarnert

0

ดังที่คำตอบอื่น ๆ บอกว่าปัญหาสำคัญคือการหาข้อมูลประเภท ในส่วนที่คุณสามารถทำได้แบบคงที่คุณสามารถสร้างรหัสที่ดีได้โดยตรง

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

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