รวบรวมเพื่อ bytecode vs รหัสเครื่อง


13

การรวบรวมที่สร้างรหัสไบต์ชั่วคราว (เช่นกับ Java) แทนที่จะไปที่ "ตลอดทาง" กับรหัสเครื่องมักจะมีความซับซ้อนน้อยลง (และอาจใช้เวลาน้อยลง)?

คำตอบ:


22

ใช่การรวบรวม Java bytecode นั้นง่ายกว่าการคอมไพล์ไปยังรหัสเครื่อง นี่เป็นเพียงบางส่วนเนื่องจากมีรูปแบบที่จะกำหนดเป้าหมายเพียงรูปแบบเดียว (ตามที่ Mandrill กล่าวถึงแม้ว่าจะเป็นการลดความซับซ้อนของคอมไพเลอร์เท่านั้นไม่ใช่เวลารวบรวม) ส่วนหนึ่งเป็นเพราะ JVM เป็นเครื่องจักรที่ง่ายกว่ามากและสะดวกกว่าโปรแกรมจริง ควบคู่ไปกับภาษา Java การดำเนินการ Java ส่วนใหญ่แมปการดำเนินการ bytecode เดียวในวิธีที่ง่ายมาก อีกเหตุผลที่สำคัญมากคือไม่จริงการเพิ่มประสิทธิภาพเกิดขึ้น ความกังวลเรื่องประสิทธิภาพเกือบทั้งหมดถูกทิ้งไว้ในคอมไพเลอร์ของ JIT (หรือ JVM โดยรวม) ดังนั้นคอมไพเลอร์กลางส่วนกลางทั้งหมดจึงหายไป โดยทั่วไปสามารถเดินผ่าน AST เพียงครั้งเดียวและสร้างลำดับ bytecode สำเร็จรูปสำหรับแต่ละโหนด มี "ค่าใช้จ่ายในการบริหาร" ในการสร้างตารางวิธี, พูลคงที่, ฯลฯ แต่ไม่มีอะไรเทียบกับความซับซ้อนของ, พูด, LLVM


คุณเขียนว่า "... กึ่งกลางของ ... " คุณหมายถึง "... กลางถึงปลาย ... " หรือไม่? หรืออาจจะ "... ส่วนตรงกลางของ ... "?
Julian A.

6
@ Julian "middle end" เป็นคำศัพท์จริงประกาศเกียรติคุณในการเปรียบเทียบกับ "front end" และ "back end" โดยไม่คำนึงถึงความหมาย :)

7

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

ต้องมีขั้นตอนอะไรบ้างในการรวบรวมโปรแกรม

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

มีเพียงสองความแตกต่างที่แท้จริงระหว่างสอง

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

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

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

1 ไม่นับภาษาที่ลึกลับ


3
ไม่สนใจการเพิ่มประสิทธิภาพและนั่นเป็นเรื่องโง่ "ขั้นตอนเพิ่มเติม" เหล่านี้ประกอบขึ้นเป็นรหัสฐานความซับซ้อนและเวลารวบรวมของคอมไพเลอร์ส่วนใหญ่

ในทางปฏิบัตินั้นถูกต้อง ฉันกำลังศึกษาเชิงวิชาการที่นี่ฉันอัพเดตคำตอบของฉัน

มีข้อกำหนดภาษาที่ห้ามการเพิ่มประสิทธิภาพจริงหรือไม่ ฉันเข้าใจว่าบางภาษาทำให้มันยาก แต่ไม่อนุญาตให้เริ่มต้นด้วยหรือ
Davidmh

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

4

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

ในทางกลับกันเครื่องแต่ละเครื่องจะต้องโหลด Java Virtual Machine เพื่อให้สามารถตีความ "รหัสไบต์" (ซึ่งเป็นรหัสเครื่องเสมือนที่เกิดจากการรวบรวมรหัส java) แปลเป็นรหัสเครื่องจริงและเรียกใช้ .

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


ฉันเห็น. ดังนั้นคุณคิดว่าความซับซ้อนของการทำแผนที่ bytecode กับเครื่องมาตรฐาน (เช่น JVM) จะตรงกับการทำแผนที่ซอร์สโค้ดไปยังเครื่องทางกายภาพโดยไม่มีเหตุผลใดที่คิดว่า bytecode จะทำให้การรวบรวมสั้นลง?
Julian A.

นั่นไม่ใช่สิ่งที่ฉันพูด ฉันบอกว่าการจับคู่โค้ด Java กับโค้ดไบต์ (ซึ่งเป็น Virtual Machine Assembler) จะตรงกับการแมปซอร์สโค้ด (Java) กับโค้ดเครื่องจริง
Mandrill

3

ความซับซ้อนของการรวบรวมขึ้นอยู่กับช่องว่างทางความหมายระหว่างภาษาต้นฉบับและภาษาเป้าหมายและระดับของการปรับให้เหมาะสมที่คุณต้องการใช้ขณะเชื่อมช่องว่างนี้

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

การคอมไพล์ซอร์สโค้ด Ruby กับโค้ดไบต์ JVM นั้นมีส่วนเกี่ยวข้องมากกว่า (โดยเฉพาะก่อนหน้านี้invokedynamicและMethodHandlesถูกนำมาใช้ใน Java 7 หรือมากกว่านั้นใน JVM ข้อมูลจำเพาะรุ่นที่ 3) ใน Ruby สามารถเปลี่ยนเมธอดได้ที่ runtime บน JVM หน่วยของโค้ดที่เล็กที่สุดที่สามารถถูกแทนที่ในขณะรันไทม์เป็นคลาสดังนั้นเมธอด Ruby ต้องถูกคอมไพล์ไม่ใช่เมธอด JVM แต่เป็นคลาส JVM การแจกจ่ายเมธอด Ruby ไม่ตรงกับการจัดส่งเมธอด JVM และก่อนหน้าinvokedynamicนี้ไม่มีวิธีฉีดกลไกการจัดส่งเมธอดของคุณเองลงใน JVM ทับทิมมีการต่อเนื่องและ coroutines แต่ JVM ขาดสิ่งอำนวยความสะดวกในการดำเนินการเหล่านั้น (JVM ของGOTO ถูก จำกัด ให้กระโดดข้ามเป้าหมายภายในเมธอด) JVM แบบดั้งเดิมเท่านั้นที่ควบคุมการไหลที่จะมีประสิทธิภาพเพียงพอที่จะใช้การดำเนินการต่อเนื่องเป็นข้อยกเว้นและใช้เธรด coroutines ซึ่งทั้งสองมีความหนามากในขณะที่วัตถุประสงค์ทั้งหมดของ coroutines คือ เบามาก

OTOH, การคอมไพล์ซอร์สโค้ด Ruby กับ Rubinius byte code หรือ YARV byte code เป็นเรื่องไม่สำคัญอีกครั้งเนื่องจากทั้งสองอย่างนั้นได้รับการออกแบบอย่างชัดเจนว่าเป็นเป้าหมายการรวบรวมสำหรับ Ruby (แม้ว่า Rubinius ยังถูกใช้สำหรับภาษาอื่นเช่น CoffeeScript และชื่อเสียงที่สุด) .

ในทำนองเดียวกันการคอมไพล์โค้ดเนทีฟ x86 ไปยังโค้ดไบต์ JVM ไม่ใช่การส่งต่อตรงอีกครั้งมีช่องว่างความหมายที่ค่อนข้างใหญ่

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

ดังนั้นอาจกล่าวได้ว่าระดับภาษาเป้าหมายที่สูงกว่าคือยิ่งใกล้เคียงกับความหมายของภาษาต้นฉบับเพื่อลดความซับซ้อนของคอมไพเลอร์


0

ในทางปฏิบัติ JVM ส่วนใหญ่ในปัจจุบันเป็นซอฟต์แวร์ที่ซับซ้อนมากทำการรวบรวม JIT (ดังนั้น bytecode จึงถูกแปลเป็นรหัสเครื่องจักรโดย JVM แบบไดนามิก )

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

ความจริงที่ว่าการแปล JIT ภายใน JVM นั้นเป็นแบบไดนามิกช่วยให้ JVM สามารถมุ่งเน้นไปที่ส่วนที่เกี่ยวข้องที่สุดของไบต์ ในทางปฏิบัติแล้ว JVM ส่วนใหญ่จะเพิ่มประสิทธิภาพส่วนที่ร้อนแรงที่สุด (เช่นวิธีที่เรียกมากที่สุดหรือบล็อกพื้นฐานที่ถูกเรียกใช้มากที่สุด) ของ JVM bytecode

ฉันไม่แน่ใจว่าความซับซ้อนรวมของคอมไพเลอร์ JVM + Java to bytecode นั้นน้อยกว่าความซับซ้อนของคอมไพเลอร์ก่อนเวลาอย่างมาก

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

BTW ด้วย GCC ล่าสุด (โดยเฉพาะอย่างยิ่งlibgccjit ) และโครงสร้างพื้นฐาน LLVM คุณสามารถใช้ภาษาเหล่านี้เพื่อรวบรวมภาษาอื่น ๆ (หรือภาษาของคุณเอง) เป็นตัวแทน Gimple หรือ LLVM ภายในของพวกเขาจากนั้นได้รับประโยชน์จากความสามารถในการเพิ่มประสิทธิภาพมากมาย ส่วนท้ายของคอมไพเลอร์เหล่านี้

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