การสำรองการอ้างสิทธิ์ที่ C ++ สามารถทำได้เร็วกว่า JVM หรือ CLR ด้วย JIT คืออะไร [ปิด]


119

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

ฉันต้องการทราบคำอธิบายพื้นฐาน (หากมีสิ่งนั้น ... ) ว่าทำไมและอย่างไรงานบางอย่างจะเร็วขึ้นใน C ++ กว่า JVM หรือ CLR? มันเป็นเพราะ C ++ ถูกคอมไพล์เป็นรหัสเครื่องในขณะที่ JVM หรือ CLR ยังคงมีโอเวอร์เฮดของการประมวลผลของการคอมไพล์ JIT ณ รันไทม์?

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


ประสิทธิภาพยังขึ้นอยู่กับความซับซ้อนของโปรแกรมด้วย
pandu

23
ฉันจะเพิ่มใน "C ++ มีประสิทธิภาพมากขึ้นถ้าคุณรู้ว่าคุณกำลังทำอะไรและทำไมการทำสิ่งต่าง ๆ ด้วยวิธีบางอย่างจะทำให้ประสิทธิภาพการทำงานเพิ่มขึ้น" โดยการบอกว่ามันไม่ใช่แค่เรื่องของความรู้เท่านั้น แต่เป็นเรื่องของเวลาสำหรับนักพัฒนา มันไม่ได้มีประสิทธิภาพเสมอไปในการเพิ่มประสิทธิภาพสูงสุด นี่คือเหตุผลที่ภาษาระดับสูงกว่าเช่น Java และ Python มีอยู่ (ด้วยเหตุผลอื่น ๆ ) - เพื่อลดระยะเวลาที่โปรแกรมเมอร์ต้องใช้การเขียนโปรแกรมเพื่อทำงานให้สำเร็จตามที่กำหนด
Joel Cornett

4
@ Joel Cornett: ฉันเห็นด้วยอย่างยิ่ง ฉันมีประสิทธิภาพมากขึ้นใน Java มากกว่าใน C ++ และฉันจะพิจารณาเฉพาะ C ++ เมื่อฉันต้องเขียนโค้ดที่รวดเร็วจริงๆ ในทางกลับกันฉันได้เห็นรหัส C ++ ที่เขียนไม่ดีช้ามาก: C ++ มีประโยชน์น้อยกว่าในมือของโปรแกรมเมอร์ที่ไม่มีทักษะ
Giorgio

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

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

คำตอบ:


200

มันคือทั้งหมดที่เกี่ยวกับหน่วยความจำ (ไม่ใช่ JIT) ข้อได้เปรียบของ JIT เหนือ C นั้นส่วนใหญ่ถูก จำกัด ให้ปรับการโทรเสมือนหรือการโทรเสมือนโดยไม่ใช้อินไลน์ซึ่งเป็นสิ่งที่ CPU BTB ทำงานหนักอยู่แล้ว

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

  • มีโอเวอร์เฮดหน่วยความจำอย่างน้อย 8 ไบต์สำหรับแต่ละวัตถุและจำเป็นต้องใช้หรือแทนที่วัตถุดั้งเดิมในหลาย ๆ ที่ (เช่นคอลเลกชันมาตรฐาน)
  • สตริงประกอบด้วยสองวัตถุและมีค่าใช้จ่าย 38 ไบต์
  • UTF-16 ถูกใช้ภายในซึ่งหมายความว่าอักขระ ASCII แต่ละตัวต้องใช้สองไบต์แทนหนึ่งตัว (Oracle JVM เพิ่งเปิดตัว optimizaion เพื่อหลีกเลี่ยงปัญหานี้สำหรับสตริง ASCII บริสุทธิ์)
  • ไม่มีประเภทการอ้างอิงรวม (เช่น structs) และในทางกลับกันไม่มีอาร์เรย์ของประเภทการอ้างอิงรวม วัตถุ Java หรืออาร์เรย์ของวัตถุ Java มีตำแหน่งแคช L1 / L2 ที่แย่มากเมื่อเทียบกับ C-structs และอาร์เรย์
  • Java generics ใช้ประเภทการลบซึ่งมีตำแหน่งแคชที่ไม่ดีเมื่อเทียบกับการสร้างอินสแตนซ์
  • การจัดสรรวัตถุนั้นทึบและต้องแยกต่างหากสำหรับแต่ละวัตถุดังนั้นจึงเป็นไปไม่ได้ที่แอปพลิเคชั่นจะวางโครงสร้างข้อมูลในลักษณะที่เป็นมิตรกับแคชและยังคงถือว่าเป็นข้อมูลที่มีโครงสร้าง

ปัจจัยความจำอื่น - แต่ไม่ใช่ปัจจัยที่เกี่ยวข้องกับแคช:

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

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

เป็นที่น่าสังเกตว่าการแลกเปลี่ยนเหล่านี้จำนวนมากนั้นแตกต่างกันมากสำหรับ Java / JVM มากกว่าที่จะเป็น C # / CIL .NET CIL มีโครงสร้างประเภทอ้างอิงการจัดสรร / การผ่านสแต็กอาร์เรย์ที่บรรจุของโครงสร้างและข้อมูลทั่วไปชนิดอินสแตนซ์


37
+1 - โดยรวมนี่เป็นคำตอบที่ดี อย่างไรก็ตามฉันไม่แน่ใจว่าสัญลักษณ์แสดงหัวข้อย่อย "ไม่มีการจัดสรรสแต็ก" ถูกต้องทั้งหมด Java JIT มักทำการหลบหนีการวิเคราะห์เพื่ออนุญาตการจัดสรรสแต็กหากเป็นไปได้ - สิ่งที่คุณควรพูดคือภาษาจาวาไม่อนุญาตให้โปรแกรมเมอร์ตัดสินใจเมื่อวัตถุถูกจัดสรรสแต็กกับการจัดสรรฮีป นอกจากนี้หากตัวรวบรวมขยะทั่วไป (ซึ่งใช้ JVM สมัยใหม่ทั้งหมด) ใช้งานอยู่ "การจัดสรรฮีป" หมายถึงสิ่งที่แตกต่างอย่างสิ้นเชิง (ด้วยคุณสมบัติด้านประสิทธิภาพที่แตกต่างอย่างสิ้นเชิง) กว่าในสภาพแวดล้อม C ++
Daniel Pryden

5
ฉันคิดว่ามีอีกสองอย่าง แต่ส่วนใหญ่ฉันทำงานกับสิ่งที่ระดับสูงกว่ามากดังนั้นบอกว่าฉันผิด คุณไม่สามารถเขียน C ++ ได้โดยไม่ต้องพัฒนาความรู้ทั่วไปเพิ่มเติมเกี่ยวกับสิ่งที่เกิดขึ้นจริงในหน่วยความจำและวิธีการทำงานของรหัสเครื่องในขณะที่การเขียนสคริปต์หรือภาษาเครื่องเสมือนเป็นนามธรรมทำให้สิ่งต่าง ๆ ไม่อยู่ในความสนใจของคุณ นอกจากนี้คุณยังมีการควบคุมที่ละเอียดยิ่งขึ้นเกี่ยวกับวิธีการทำงานของสิ่งต่าง ๆ ในขณะที่ใน VM หรือภาษาที่แปลแล้วคุณกำลังพึ่งพาสิ่งที่ผู้เขียนไลบรารีหลักอาจปรับให้เหมาะสมสำหรับสถานการณ์ที่เฉพาะเจาะจงมากเกินไป
Erik Reppen

18
+1 อีกสิ่งหนึ่งที่ฉันต้องการเพิ่ม (แต่ไม่เต็มใจที่จะส่งคำตอบใหม่): การทำดัชนีอาร์เรย์ใน Java มักจะเกี่ยวข้องกับการตรวจสอบขอบเขต ด้วย C และ C ++ นี่ไม่ใช่กรณี
riwalk

7
เป็นที่น่าสังเกตว่าการจัดสรรฮีปของ Java นั้นเร็วกว่ารุ่นไร้เดียงสาอย่างมากด้วย C ++ (เนื่องจากการรวมกลุ่มภายในและสิ่งต่าง ๆ ) แต่การจัดสรรหน่วยความจำใน C ++ อาจดีกว่ามากหากคุณรู้ว่าคุณกำลังทำอะไร
เบรนแดน Long

10
@BrendanLong จริง .. แต่ถ้าหน่วยความจำหมด - เมื่อแอพทำงานอยู่ครู่หนึ่งการจัดสรรหน่วยความจำจะช้าลงเนื่องจากความต้องการ GC ซึ่งจะทำให้สิ่งต่าง ๆ ช้าลงอย่างมากเนื่องจากต้องมีหน่วยความจำว่าง กะทัดรัด มันเป็นการแลกเปลี่ยนที่เป็นประโยชน์ต่อมาตรฐาน แต่โดยรวม (IMHO) ทำให้แอปช้าลง
gbjbaanb

67

เป็นเพราะ C ++ ถูกคอมไพล์เป็นชุดประกอบ / รหัสเครื่องในขณะที่ Java / C # ยังคงมีโอเวอร์เฮดการประมวลผลของการคอมไพล์ JIT ที่รันไทม์?

บางส่วน แต่โดยทั่วไปสมมติว่าคอมไพเลอร์ JIT ที่ยอดเยี่ยมอย่างแน่นอนรหัส C ++ ที่เหมาะสมยังคงมีแนวโน้มที่จะทำงานได้ดีกว่าโค้ด Java สำหรับเหตุผลหลักสองประการ:

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

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

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

พื้นที่หนึ่งที่ C ++ อาจช้ากว่า Java คือสถานการณ์ใด ๆ ที่จำเป็นต้องจัดสรรวัตถุขนาดเล็กจำนวนมากบนฮีป ในกรณีนี้ระบบรวบรวมขยะของ Java อาจส่งผลให้ประสิทธิภาพดีกว่ามาตรฐานnewและdeleteใน C ++ เนื่องจาก Java GC เปิดใช้งานการจัดสรรคืนจำนวนมาก แต่อีกครั้งโปรแกรมเมอร์ C ++ สามารถชดเชยสิ่งนี้ได้โดยใช้พูลหน่วยความจำหรือตัวจัดสรรพื้นในขณะที่โปรแกรมเมอร์ Java ไม่มีการขอความช่วยเหลือเมื่อต้องเผชิญกับรูปแบบการจัดสรรหน่วยความจำที่รันไทม์ของ Java ไม่เหมาะสำหรับ

ดูคำตอบที่ยอดเยี่ยมนี้สำหรับข้อมูลเพิ่มเติมเกี่ยวกับหัวข้อนี้


6
คำตอบที่ดี แต่มีเพียงจุดเดียว: "แม่แบบ C ++ ให้ทั้งสองอย่าง (ในเวลาที่คอมไพล์นานกว่า)" ฉันจะเพิ่มด้วยขนาดโปรแกรมที่ใหญ่ขึ้น อาจไม่ได้เป็นปัญหาเสมอไป แต่หากพัฒนาขึ้นสำหรับอุปกรณ์พกพา
ลีโอ

9
@ luiscubal: ไม่ในแง่นี้ C # generics นั้นมีลักษณะคล้าย Java มาก (ซึ่งเป็นเส้นทางรหัส "generic" แบบเดียวกันไม่ว่าจะผ่านประเภทใด) เคล็ดลับสู่เทมเพลต C ++ คือโค้ดนั้นถูกสร้างอินสแตนซ์ครั้งเดียวสำหรับ ทุกประเภทจะใช้กับ ดังนั้นstd::vector<int>เป็นอาร์เรย์แบบไดนามิกการออกแบบเพียงสำหรับ ints และคอมไพเลอร์จะสามารถเพิ่มประสิทธิภาพของมันตาม AC # ยังคงเป็นเพียงแค่List<int> List
ศุกร์ที่

12
@jalf C # List<int>ใช้สิ่งที่int[]ไม่Object[]เหมือนกับ Java ดูstackoverflow.com/questions/116988/…
luiscubal

5
@luiscubal: คำศัพท์ของคุณไม่ชัดเจน JIT ไม่ได้ทำในสิ่งที่ฉันคิดว่าเป็น "เวลารวบรวม" แน่นอนว่าคุณได้รับคอมไพเลอร์ JIT ที่ฉลาดและก้าวร้าวเพียงพอไม่มีข้อ จำกัด ในสิ่งที่มันสามารถทำได้ แต่ C ++ ต้องการพฤติกรรมนี้ นอกจากนี้เทมเพลต C ++ ยังช่วยให้โปรแกรมเมอร์สามารถระบุความเชี่ยวชาญเฉพาะด้านได้อย่างชัดเจน C # ไม่มีอะไรเทียบเท่า ยกตัวอย่างเช่นใน C ++ ฉันสามารถกำหนดvector<N>ที่สำหรับกรณีที่เฉพาะเจาะจงของvector<4>, SIMD การดำเนินงานมือรหัสของฉันควรจะใช้
jalf

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

46

สิ่งที่คำตอบอื่น ๆ (6 จนถึง) ดูเหมือนจะลืมที่จะพูดถึง แต่สิ่งที่ฉันคิดว่าสำคัญมากสำหรับการตอบนี้เป็นหนึ่งในปรัชญาการออกแบบขั้นพื้นฐาน C ++ ซึ่งเป็นสูตรและการจ้างงานโดย Stroustrup จากวันที่ 1:

คุณไม่จ่ายเงินสำหรับสิ่งที่คุณไม่ได้ใช้

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


ในหนังสือของเขาเรื่องการออกแบบและวิวัฒนาการของ C ++ (มักเรียกว่า [D&E]) Stroustrup อธิบายถึงสิ่งที่เขาต้องการซึ่งทำให้เขาเกิด C ++ ขึ้นมาตั้งแต่แรก ในคำพูดของฉัน: สำหรับวิทยานิพนธ์ปริญญาเอกของเขา (บางสิ่งเกี่ยวกับการจำลองเครือข่าย IIRC) เขาใช้ระบบใน SIMULA ซึ่งเขาชอบมากเพราะภาษาดีมากที่ทำให้เขาสามารถแสดงความคิดของเขาโดยตรงในโค้ด อย่างไรก็ตามโปรแกรมที่เกิดขึ้นทำงานช้าเกินไปและเพื่อให้ได้ปริญญาเขาเขียนสิ่งใหม่ใน BCPL ซึ่งเป็นบรรพบุรุษของ C. เขียนรหัสใน BCPL เขาอธิบายว่าเป็นความเจ็บปวด แต่โปรแกรมผลลัพธ์นั้นเร็วพอที่จะส่งมอบได้ ผลลัพธ์ที่ได้รับอนุญาตให้เขาสำเร็จการศึกษาระดับปริญญาเอก

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


ดังนั้นเป้าหมายที่ยกมาข้างต้นไม่ได้เป็นเพียงหนึ่งในหลักการออกแบบพื้นฐานหลายอย่าง แต่ใกล้กับraison d'etreสำหรับ C ++ และสามารถพบได้ทุกที่ในภาษา: ฟังก์ชั่นเป็นเพียงvirtualเมื่อคุณต้องการให้พวกเขา (เพราะการเรียกฟังก์ชั่นเสมือนมาพร้อมกับค่าใช้จ่ายเล็กน้อย) POD จะเริ่มต้นโดยอัตโนมัติเมื่อคุณร้องขออย่างชัดเจนเท่านั้นยกเว้นเมื่อคุณต้องการ โยนพวกเขา (ในขณะที่มันเป็นเป้าหมายการออกแบบที่ชัดเจนเพื่อให้การตั้งค่า / การล้างสแต็คเฟรมมีราคาถูกมาก) ไม่มี GC ทำงานทุกครั้งที่รู้สึกเช่นนั้นเป็นต้น

C ++ เลือกที่จะไม่ให้ความสะดวกสบายกับคุณอย่างชัดเจน ("ฉันต้องทำให้วิธีนี้เป็นเสมือนที่นี่หรือไม่?") เพื่อแลกเปลี่ยนกับประสิทธิภาพ ("ไม่ฉันไม่และตอนนี้คอมไพเลอร์สามารถทำได้inlineและปรับค่า heck ให้เหมาะสมที่สุด สิ่งทั้งหมด! ") และไม่น่าแปลกใจเลยที่นี่ส่งผลให้ประสิทธิภาพเพิ่มขึ้นเมื่อเทียบกับภาษาที่สะดวกกว่า


4
คุณไม่ต้องจ่ายเงินสำหรับสิ่งที่คุณไม่ได้ใช้ => จากนั้นพวกเขาเพิ่ม RTTI :(
Matthieu M.

11
@ Matthieu: ในขณะที่ฉันเข้าใจความรู้สึกของคุณฉันไม่สามารถช่วย แต่สังเกตเห็นว่าแม้จะมีการเพิ่มด้วยความระมัดระวังเกี่ยวกับการแสดง RTTI มีการระบุไว้เพื่อให้สามารถใช้งานได้โดยใช้ตารางเสมือนจริงและเพิ่มค่าใช้จ่ายน้อยมากหากคุณไม่ได้ใช้ ถ้าคุณไม่ใช้ความหลากหลายก็ไม่มีค่าใช้จ่ายเลย ฉันพลาดอะไรไปรึเปล่า?
sbi

9
@ Matthieu: แน่นอนมีเหตุผล แต่เหตุผลนี้คือเหตุผล? จากสิ่งที่ฉันเห็น "ต้นทุนของ RTTI" หากไม่ได้ใช้เป็นตัวชี้เพิ่มเติมในตารางเสมือนของคลาส polymorphic ทุกตัวชี้ไปที่วัตถุ RTTI บางตัวที่ถูกจัดสรรแบบคงที่ หากคุณไม่ต้องการตั้งโปรแกรมชิปในเครื่องปิ้งขนมปังของฉันสิ่งนี้จะเกี่ยวข้องกันอย่างไร
sbi

4
@Aaraught: ฉันจะสูญเสียเป็นสิ่งที่ตอบกลับว่า คุณเพียงแค่ยกเลิกคำตอบของฉันเพราะชี้ให้เห็นถึงปรัชญาพื้นฐานที่ทำให้ Stroustrup et al เพิ่มคุณสมบัติในแบบที่ช่วยให้มีประสิทธิภาพแทนที่จะแสดงวิธีและคุณลักษณะเหล่านี้แยกกันใช่หรือไม่
sbi

9
@Anaught: คุณมีความเห็นอกเห็นใจของฉัน
sbi

29

คุณรู้บทความวิจัยของ Googleเกี่ยวกับหัวข้อนั้นหรือไม่?

จากบทสรุป:

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

นี่เป็นคำอธิบายบางส่วนอย่างน้อยก็ในแง่ของ "เพราะคอมไพเลอร์ C ++ ในโลกแห่งความจริงสร้างโค้ดได้เร็วกว่าคอมไพเลอร์ Java โดยการวัดเชิงประจักษ์"


4
นอกเหนือจากความแตกต่างของการใช้หน่วยความจำและแคชแล้วหนึ่งในสิ่งที่สำคัญที่สุดคือปริมาณการปรับให้เหมาะสม เปรียบเทียบจำนวนการปรับให้เหมาะสม GCC / LLVM (และอาจเป็น Visual C ++ / ICC) ที่เกี่ยวข้องกับคอมไพเลอร์ Java HotSpot: มีมากขึ้นโดยเฉพาะอย่างยิ่งเกี่ยวกับลูป, กำจัดกิ่งที่ซ้ำซ้อนและการจัดสรรรีจิสเตอร์ คอมไพเลอร์ของ JIT มักจะไม่มีเวลาสำหรับการเพิ่มประสิทธิภาพที่ก้าวร้าวเหล่านี้แม้จะคิดว่าพวกเขาสามารถนำไปใช้งานได้ดีขึ้นโดยใช้ข้อมูลรันไทม์ที่มีอยู่
Gratian Lup

2
@GratianLup: ฉันสงสัยว่ามันยังคงเป็นจริงกับ LTO หรือไม่
Deduplicator

2
@GratianLup: อย่าลืมการเพิ่มประสิทธิภาพที่แนะนำโดยโปรไฟล์สำหรับ C ++ ...
Deduplicator

23

นี่ไม่ใช่คำถามของคุณซ้ำ แต่คำตอบที่ได้รับการยอมรับตอบคำถามส่วนใหญ่ของคุณ: การตรวจสอบ Java สมัยใหม่

วิธีสรุป:

พื้นฐานความหมายของ Java บอกว่ามันเป็นภาษาที่ช้ากว่า C ++

ดังนั้นขึ้นอยู่กับภาษาอื่นที่คุณเปรียบเทียบ C ++ คุณอาจได้รับคำตอบเดียวกันหรือไม่

ใน C ++ คุณมี:

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

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

  • ใช้ทางอ้อมอย่างหนาแน่น ("ทุกอย่างเป็นภาษาอ้างอิง / ตัวชี้" ที่มีการจัดการ): ทางอ้อมหมายถึง CPU ต้องกระโดดในหน่วยความจำเพื่อรับข้อมูลที่จำเป็นเพิ่มความล้มเหลวของแคช CPU ซึ่งหมายถึงการประมวลผลช้าลง มากแม้ว่ามันจะมีข้อมูลขนาดเล็กเป็น C ++;
  • สร้างวัตถุขนาดใหญ่ที่สมาชิกเข้าถึงโดยอ้อม: นี่คือผลของการอ้างอิงโดยค่าเริ่มต้นสมาชิกเป็นตัวชี้ดังนั้นเมื่อคุณได้รับสมาชิกคุณอาจไม่ได้รับข้อมูลใกล้กับแกนกลางของวัตถุหลักอีกครั้งทำให้แคชหายไป
  • ใช้ตัวสะสม garbarge: มันทำให้การคาดการณ์ของประสิทธิภาพเป็นไปไม่ได้ (โดยการออกแบบ)

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

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


7

มันเกี่ยวกับความทรงจำเป็นหลัก (อย่างที่ Michael Borgwardt พูด) ด้วยการเพิ่มประสิทธิภาพ JIT เล็กน้อย

สิ่งหนึ่งที่ไม่ได้กล่าวถึงคือการใช้แคชอย่างเต็มที่คุณต้องใช้ข้อมูลของคุณในการจัดวางอย่างต่อเนื่อง ขณะนี้ด้วยระบบ GC หน่วยความจำจะถูกจัดสรรบนฮีป GC ซึ่งรวดเร็ว แต่เมื่อหน่วยความจำถูกใช้ GC จะทำการเตะอย่างสม่ำเสมอและลบบล็อกที่ไม่ได้ใช้แล้วจากนั้นทำการย่อส่วนที่เหลือด้วยกัน ตอนนี้นอกเหนือจากความช้าที่เห็นได้ชัดในการย้ายบล็อกที่ใช้ร่วมกันนั่นหมายความว่าข้อมูลที่คุณใช้อาจไม่ติดกัน หากคุณมีองค์ประกอบ 1,000 รายการยกเว้นว่าคุณได้จัดสรรทั้งหมดในครั้งเดียว (จากนั้นอัปเดตเนื้อหาของพวกเขาแทนที่จะลบและสร้างองค์ประกอบใหม่ - ที่จะถูกสร้างขึ้นในตอนท้ายของกอง) สิ่งเหล่านี้จะกระจัดกระจายไปทั่วกอง ดังนั้นจึงต้องใช้หน่วยความจำจำนวนมากเพื่ออ่านทั้งหมดลงในแคชของ CPU แอป AC / C ++ ส่วนใหญ่จะจัดสรรหน่วยความจำสำหรับองค์ประกอบเหล่านี้และจากนั้นคุณอัปเดตบล็อกด้วยข้อมูล (ตกลงมีโครงสร้างข้อมูลเช่นรายการที่ทำงานมากกว่าการจัดสรรหน่วยความจำ GC แต่คนรู้ว่าสิ่งเหล่านี้ช้ากว่าเวกเตอร์)

คุณสามารถเห็นสิ่งนี้ในการดำเนินการเพียงแค่แทนที่วัตถุ StringBuilder ด้วย String ... Stringbuilders ทำงานโดยการจัดสรรหน่วยความจำล่วงหน้าและกรอกข้อมูลและเป็นเคล็ดลับประสิทธิภาพที่รู้จักกันดีสำหรับระบบ java / .NET

อย่าลืมว่ากระบวนทัศน์ 'ลบเก่าและจัดสรรสำเนาใหม่' ถูกใช้อย่างหนักใน Java / C # เพียงเพราะมีคนบอกว่าการจัดสรรหน่วยความจำนั้นเร็วมากเนื่องจาก GC ดังนั้นโมเดลหน่วยความจำที่กระจัดกระจายจึงถูกนำไปใช้ทุกที่ ( แน่นอนว่าสำหรับนักสร้างสายอักขระ) ดังนั้นไลบรารีทั้งหมดของคุณมักจะสิ้นเปลืองหน่วยความจำและใช้งานจำนวนมาก แต่ก็ไม่มีสิ่งใดที่จะได้รับประโยชน์จากความต่อเนื่อง ตำหนิ hype รอบ ๆ GC สำหรับเรื่องนี้ - พวกเขาบอกว่าคุณจำได้ฟรีฮ่า ๆ

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

ความนิยมที่สมบูรณ์แบบสำหรับหน่วยความจำ C ++ นั้นมาจากการจัดสรรหน่วยความจำ - เมื่อคุณต้องการบล็อกใหม่คุณต้องเดินกองเพื่อค้นหาพื้นที่ว่างถัดไปที่ใหญ่พอด้วยกองที่กระจัดกระจายมากซึ่งไม่เร็วเท่ากับ GC ของ 'จัดสรรบล็อกอื่นที่ส่วนท้าย' แต่ฉันคิดว่ามันไม่ช้าเท่ากับการทำงานของการบีบอัด GC ทั้งหมดและสามารถลดลงได้โดยใช้ฮีปบล็อกขนาดคงที่หลาย ๆ อัน (เรียกอีกอย่างว่าพูลหน่วยความจำ)

มีอีกมาก ... เช่นการโหลดแอสเซมบลีออกจาก GAC ที่ต้องการการตรวจสอบความปลอดภัยเส้นทางโพรบ (เปิดsxstraceและดูสิ่งที่กำลังจะเกิดขึ้น!) และการวางกำลังทั่วไปอื่น ๆ กว่า C / C ++


2
หลายสิ่งที่คุณเขียนไม่เป็นความจริงสำหรับนักสะสมขยะรุ่นใหม่
Michael Borgwardt

3
@MichaelBorgwardt เช่น? ฉันพูดว่า "GC ทำงานเป็นประจำ" และ "มันกระชับกอง" ส่วนที่เหลือของคำตอบของฉันเกี่ยวข้องกับวิธีโครงสร้างข้อมูลแอปพลิเคชันใช้หน่วยความจำ
gbjbaanb

6

"เป็นเพราะ C ++ ถูกคอมไพล์เป็นชุดประกอบ / รหัสเครื่องในขณะที่ Java / C # ยังคงมีโอเวอร์เฮดของการประมวลผลของการคอมไพล์ JIT เมื่อรันไทม์?" โดยทั่วไปใช่!

แม้ว่าบันทึกย่ออย่างรวดเร็ว Java มีค่าโสหุ้ยมากกว่าการรวบรวม JIT ตัวอย่างเช่นจะทำการตรวจสอบให้คุณมากขึ้น (ซึ่งเป็นสิ่งที่ชอบArrayIndexOutOfBoundsExceptionsและNullPointerExceptions) ตัวรวบรวมขยะเป็นค่าใช้จ่ายที่สำคัญอีกประการหนึ่ง

มีการเปรียบเทียบรายละเอียดสวยเป็นที่นี่


2

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

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

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

การอ้างสิทธิ์ว่าA เร็วกว่า Bหมายถึงเวลาที่โปรแกรมใช้ในการทำงานตามที่ผู้ใช้เห็น หากเราสมมติว่าโค้ดทั้งสองชิ้นทำงานเหมือนกันในขั้นตอนการดำเนินการเราต้องสมมติว่ากระบวนการทำงานของ JIT ช้าลงสำหรับผู้ใช้เนื่องจากเขาต้องดูเวลา T ของการรวบรวมไปยังรหัสเครื่องโดยที่ T> 0 ดังนั้น เพื่อความเป็นไปได้ของกระบวนการทำงานของ JIT ที่จะดำเนินการเช่นเดียวกับขั้นตอนการทำงานดั้งเดิมสำหรับผู้ใช้เราจะต้องลดเวลาการเรียกใช้โค้ดเช่น Execution + Compilation ไปยังรหัสเครื่องต่ำกว่าขั้นตอนการเรียกใช้งานเท่านั้น ของกระบวนการทำงานดั้งเดิม ซึ่งหมายความว่าเราต้องปรับรหัสให้เหมาะสมในการคอมไพล์ JIT มากกว่าการคอมไพล์เนทีฟ

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

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

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

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

มีปัญหาเกี่ยวกับเรื่องนี้แม้ว่าจะถือว่าการปรับให้เหมาะสมที่แนะนำโดยโปรไฟล์และสิ่งนั้นเป็นสิ่งที่ไม่เหมือนใครสำหรับ VMs ซึ่งไม่เป็นความจริง เราสามารถนำไปใช้กับการคอมไพล์เนทีฟได้เช่นกัน - โดยการรวบรวมแอปพลิเคชันของเราด้วยการเปิดใช้งานการทำโปรไฟล์บันทึกข้อมูลแล้วคอมไพล์แอปพลิเคชันอีกครั้งด้วยโปรไฟล์นั้น อาจเป็นมูลค่าที่ชี้ให้เห็นว่าการแลกเปลี่ยนรหัสที่ร้อนแรงไม่ใช่สิ่งที่ผู้เรียบเรียง JIT เท่านั้นที่สามารถทำได้เราสามารถทำได้สำหรับรหัสเนทีฟแม้ว่าโซลูชั่นที่ใช้ JIT สำหรับการทำเช่นนี้จะมีให้ใช้ง่ายกว่าและง่ายกว่ามากสำหรับนักพัฒนา ดังนั้นคำถามใหญ่ก็คือ: VM สามารถให้ข้อมูลบางอย่างที่คอมไพล์เนทีฟไม่สามารถที่จะเพิ่มประสิทธิภาพของโค้ดของเราได้หรือไม่?

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

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


0

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


0

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

มันยังห่างไกลจากเรื่องเต็ม

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

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

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


3
"การเพิ่มประสิทธิภาพคอมไพเลอร์ที่เหมาะสมสำหรับเครื่องหนึ่งอาจผิดอย่างสมบูรณ์สำหรับเครื่องอื่น" ดีนั่นไม่ใช่การตำหนิภาษา รหัสประสิทธิภาพที่สำคัญอย่างแท้จริงสามารถรวบรวมแยกต่างหากสำหรับแต่ละเครื่องที่จะทำงานซึ่งไม่ต้องใช้สมองหากคุณรวบรวมภายในเครื่องจากแหล่งที่มา ( -march=native) - "มันเป็นระดับที่ต่ำกว่าของนามธรรม" ไม่เป็นความจริง C ++ ใช้ abstractions ระดับสูงเช่นเดียวกับ Java (หรืออันที่จริงแล้วตัวที่สูงกว่า: programming programming? template metaprogramming?) มันใช้ abstractions น้อย "หมดจด" มากกว่า Java
leftaroundabout

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

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

1
ในทางทฤษฎีแล้ว JIT สามารถดึงทริกเกอร์ได้มากกว่าสแตติกคอมไพเลอร์ในทางปฏิบัติ (สำหรับ. NET อย่างน้อยฉันก็ไม่รู้จาวาด้วย) แต่ก็ไม่ได้ทำสิ่งนี้ ฉันได้ทำการแยกส่วนของรหัส. NET JIT เมื่อเร็ว ๆ นี้และมีการเพิ่มประสิทธิภาพทุกประเภทเช่นการยกรหัสออกจากลูปการกำจัดโค้ดที่ตายแล้ว ฯลฯ ซึ่ง. NET JIT นั้นไม่ได้ทำ ฉันหวังว่าจะเป็นเช่นนั้น แต่เฮ้ทีม windows ใน microsoft พยายามฆ่า. NET มาหลายปีดังนั้นฉันจึงไม่กลั้นหายใจ
Orion Edwards

-1

การรวบรวม JIT จริง ๆ แล้วมีผลกระทบด้านลบต่อประสิทธิภาพ หากคุณออกแบบคอมไพเลอร์ "สมบูรณ์แบบ" และคอมไพเลอร์ "สมบูรณ์แบบ" JIT ตัวเลือกแรกจะชนะในประสิทธิภาพเสมอ

ทั้ง Java และ C # ถูกแปลเป็นภาษากลางแล้วเรียบเรียงเป็นโค้ดเนทีฟที่รันไทม์ซึ่งช่วยลดประสิทธิภาพ

แต่ตอนนี้ความแตกต่างนั้นไม่ชัดเจนสำหรับ C #: Microsoft CLR สร้างรหัสเนทีฟที่แตกต่างกันสำหรับซีพียูที่แตกต่างกันดังนั้นจึงทำให้รหัสมีประสิทธิภาพมากขึ้นสำหรับเครื่องที่ทำงานอยู่ซึ่งคอมไพเลอร์ C ++ ไม่ได้ทำเสมอ

PS C # เขียนได้อย่างมีประสิทธิภาพมากและมีเลเยอร์นามธรรมไม่มากนัก สิ่งนี้ไม่เป็นความจริงสำหรับ Java ซึ่งไม่มีประสิทธิภาพ ดังนั้นในกรณีนี้ด้วย greate CLR โปรแกรม C # มักจะแสดงประสิทธิภาพที่ดีกว่าโปรแกรม C ++ สำหรับข้อมูลเพิ่มเติมเกี่ยว NET และ CLR จะดูที่เจฟฟรีย์ริกเตอร์ "CLR ผ่าน C #"


8
หาก JIT มีผลกระทบทางลบต่อประสิทธิภาพจริง ๆ แล้วจะไม่ถูกใช้อย่างแน่นอนหรือ
ช่วยให้รอด

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

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

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

4
@ Adammmers จริง ๆ แล้วก็มีองค์ประกอบด้านประสิทธิภาพด้วย ดูjava.sun.com/products/hotspot/whitepaper.html การปรับให้เหมาะสมอาจรวมถึงสิ่งต่าง ๆ เช่นการปรับแบบไดนามิกเพื่อปรับปรุงการคาดคะเนสาขาและการเข้าชมแคชการอินไลน์แบบไดนามิกการยกเลิกการจำลองเสมือนการปิดใช้งานการตรวจสอบขอบเขตและการวนซ้ำ การเรียกร้องคือในหลายกรณีสิ่งเหล่านี้สามารถจ่ายได้มากกว่าค่าใช้จ่ายของ JIT
Charles E. Grant
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.