ทำไม Java ถึงเป็นไปได้เร็วกว่า C ++?


79

บางครั้ง Java มีประสิทธิภาพสูงกว่า C ++ ในการวัดประสิทธิภาพ แน่นอนว่าบางครั้ง C ++ มีประสิทธิภาพสูงกว่า

ดูลิงค์ต่อไปนี้:

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

ใครช่วยอธิบายหน่อยได้ไหม? ขอบคุณ!


2
คุณสามารถดูshootout.alioth.debian.org/u32/…เพื่อดูปัญหาที่ทำงานได้เร็วขึ้นบน java / c ++ ... ดูรูปแบบของปัญหาไม่ใช่ปัญหาเฉพาะเหล่านี้ ...
c0da

2
ดูเหตุใด java จึงมีชื่อเสียงว่าช้า สำหรับรายละเอียดมากมายในหัวข้อนี้
PéterTörök

11
มันผิดกฎหมาย (มาตรา 10.101.04.2c) เพื่อสร้าง Java VM ที่ทำงานได้เร็วกว่าไบนารีที่สามารถเรียกทำงานได้ที่สร้างด้วย C ++
Mateen Ulhaq

1
@muntoo คุณหมายถึงอะไรบนโลกใบนี้? มาตรา 10.101.04.2c ของอะไร
Highland Mark

3
@HighlandMark คุณไม่ได้เป็นสมาชิกของแวดวง คุณไม่ทราบว่ามีอะไรเกิดขึ้นภายในวงกลม วงกลมนั้นแน่นอน - กฎของวงกลมแทนที่ของธรรมชาติ คุณไม่สามารถท้าทายหรือตั้งคำถามกับแวดวงได้ ฉันคือวงกลมและฉันคือวงกลม
Mateen Ulhaq

คำตอบ:


108

ประการแรก JVM ส่วนใหญ่มีคอมไพเลอร์ดังนั้น "การตีความ bytecode" จึงค่อนข้างยาก (อย่างน้อยก็ในรหัสเกณฑ์มาตรฐาน - มันค่อนข้างหายากในชีวิตจริงโดยที่โค้ดของคุณมักจะเป็นวงเล็ก ๆ น้อย ๆ ที่ทำซ้ำบ่อยครั้งมาก )

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

  init0 = (int*)calloc(max_x,sizeof(int));
  init1 = (int*)calloc(max_x,sizeof(int));
  init2 = (int*)calloc(max_x,sizeof(int));
  for (x=0; x<max_x; x++) {
    init2[x] = 0;
    init1[x] = 0;
    init0[x] = 0;
  }

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

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

class Toggle {
public:
    Toggle(bool start_state) : state(start_state) { }
    virtual ~Toggle() {  }
    bool value() {
        return(state);
    }
    virtual Toggle& activate() {
        state = !state;
        return(*this);
    }
    bool state;
};

state = !state;ส่วนที่สำคัญจะออกมาเป็น พิจารณาสิ่งที่เกิดขึ้นเมื่อเราเปลี่ยนรหัสเพื่อเข้ารหัสสถานะintแทนbool:

class Toggle {
    enum names{ bfalse = -1, btrue = 1};
    const static names values[2];
    int state;

public:
    Toggle(bool start_state) : state(values[start_state]) 
    { }
    virtual ~Toggle() {  }
    bool value() {  return state==btrue;    }

    virtual Toggle& activate() {
        state = -state;
        return(*this);
    }
};

นี้มีการเปลี่ยนแปลงเล็ก ๆ น้อย ๆ ที่ช่วยเพิ่มความเร็วโดยรวมโดยประมาณ5: 1 อัตรากำไรขั้นต้น แม้ว่ามาตรฐานได้ตั้งใจที่จะวัดเวลาวิธีการโทรในความเป็นจริงมากที่สุดของสิ่งที่มันถูกวัดเป็นเวลาในการแปลงระหว่างและint boolฉันเห็นด้วยอย่างแน่นอนว่าต้นฉบับที่ไร้ประสิทธิภาพแสดงให้เห็นว่าเป็นเรื่องที่โชคร้าย - แต่เนื่องจากดูเหมือนว่าจะเกิดขึ้นจริงในรหัสจริงและความสะดวกในการแก้ไขเมื่อ / ถ้ามันเกิดขึ้นฉันไม่ค่อยมีเวลาคิด ของมันมีความหมายมาก

ในกรณีที่มีใครตัดสินใจที่จะเรียกใช้การวัดประสิทธิภาพที่เกี่ยวข้องอีกครั้งฉันควรเพิ่มว่ามีการปรับเปลี่ยนเล็กน้อยในรุ่น Java ที่สร้างขึ้นเล็กน้อยอย่างเท่าเทียมกัน (หรืออย่างน้อยก็ครั้งเดียวที่สร้างขึ้น - ฉันไม่ได้ทำการทดสอบ JVM เมื่อเร็ว ๆ นี้เพื่อยืนยันว่าพวกเขายังคงทำอยู่) การปรับปรุงที่ค่อนข้างเป็นธรรมในเวอร์ชั่น Java เช่นกัน เวอร์ชัน Java มี NthToggle :: enable () ที่มีลักษณะดังนี้:

public Toggle activate() {
this.counter += 1;
if (this.counter >= this.count_max) {
    this.state = !this.state;
    this.counter = 0;
}
return(this);
}

การเปลี่ยนสิ่งนี้เพื่อเรียกใช้ฟังก์ชันพื้นฐานแทนที่จะจัดการthis.stateโดยตรงให้การปรับปรุงความเร็วค่อนข้างมาก (แต่ไม่เพียงพอที่จะติดตามเวอร์ชัน C ++ ที่แก้ไขแล้ว)

ดังนั้นสิ่งที่เราท้ายที่สุดก็คือสมมติฐานที่ผิดพลาดเกี่ยวกับการตีความรหัสไบต์เทียบกับมาตรฐานที่เลวร้ายที่สุด (ฉัน) เคยเห็นมาบ้าง ไม่ให้ผลลัพธ์ที่มีความหมาย

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

[แก้ไข: ตามที่กล่าวไว้ในที่เดียวข้างต้น แต่ไม่เคยระบุอย่างที่ควรจะเป็นโดยตรงผลลัพธ์ที่ฉันอ้างถึงคือสิ่งที่ฉันได้รับเมื่อฉันทดสอบสิ่งนี้เมื่อ 5 ปีก่อนโดยใช้ C ++ และ Java implementations ที่เป็นปัจจุบันในเวลานั้น . ฉันไม่ได้ทำการทดสอบซ้ำกับการใช้งานปัจจุบัน อย่างไรก็ตามอย่างรวดเร็วบ่งชี้ว่ารหัสไม่ได้รับการแก้ไขดังนั้นสิ่งที่จะมีการเปลี่ยนแปลงจะเป็นความสามารถของคอมไพเลอร์เพื่อปกปิดปัญหาในรหัส]

อย่างไรก็ตามหากเราไม่สนใจตัวอย่าง Java เป็นไปได้ที่โค้ดที่แปลนั้นจะทำงานได้เร็วกว่าโค้ดที่คอมไพล์ (แม้ว่าจะยากและค่อนข้างผิดปกติ)

สิ่งที่เกิดขึ้นตามปกติคือรหัสที่ถูกตีความมีขนาดเล็กกว่ารหัสเครื่องมากหรือใช้งานบน CPU ที่มีแคชข้อมูลขนาดใหญ่กว่าโค้ดแคช

ในกรณีเช่นนี้ล่ามขนาดเล็ก (เช่นล่ามชั้นในของการใช้งาน Forth) อาจจะพอดีกับแคชโค้ดทั้งหมดและโปรแกรมที่ล่ามนั้นพอดีกับแคชข้อมูลทั้งหมด โดยทั่วไปแคชจะเร็วกว่าหน่วยความจำหลักอย่างน้อย 10 เท่าและบ่อยกว่านั้นมาก (ปัจจัย 100 ไม่ได้ยากยิ่งกว่านี้)

ดังนั้นหากแคชเร็วกว่าหน่วยความจำหลักโดยใช้ N เป็นปัจจัยและใช้เวลาน้อยกว่า N คำแนะนำในการใช้รหัสเครื่องเพื่อใช้รหัสไบต์แต่ละรหัสไบต์ควรชนะ (ฉันลดความซับซ้อน แต่ฉันคิดว่าความคิดทั่วไปควรจะยังคง ชัดเจน)


26
+1 เต็มรูปแบบ โดยเฉพาะอย่างยิ่ง "ภาษาจะสร้างความแตกต่างไม่มากเท่ากับโปรแกรมเมอร์และการออกแบบ" - คุณมักจะเจอปัญหาที่คุณสามารถปรับแต่งอัลกอริทึมได้เช่นปรับปรุง big-O ซึ่งจะให้การสนับสนุนมากกว่าคอมไพเลอร์ที่ดีที่สุด
schnaader

1
"ในกรณีที่มีใครตัดสินใจที่จะเรียกใช้มาตรฐานที่เกี่ยวข้องอีกครั้ง ... " ไม่! ย้อนกลับไปในปี 2005 งานเก่าเหล่านั้นถูกทิ้งและแทนที่ด้วยงานที่แสดงอยู่ในเกมมาตรฐาน หากใครต้องการเรียกใช้โปรแกรมบางตัวอีกครั้งโปรดเรียกใช้โปรแกรมปัจจุบันอีกครั้งสำหรับงานปัจจุบันที่แสดงในหน้าแรกของเกมเกณฑ์มาตรฐานshootout.alioth.debian.org
igouy

@igouy: บางคนอาจต้องการยืนยัน / ปฏิเสธผลลัพธ์จากการวัดประสิทธิภาพที่มีการแก้ไขขั้นต่ำที่จำเป็นเพื่อให้พวกเขามีความสัมพันธ์น้อยที่สุดกับความเป็นจริง ในขณะเดียวกันคุณก็ถูกต้องแล้ว: มาตรฐานในคำถามนั้นแย่มากจนการแก้ไขข้อผิดพลาดที่เห็นได้ชัดที่สุดจะไม่ช่วยอะไรมากนัก
Jerry Coffin

และนั่นคือเหตุผลว่าทำไมในปี 2005 พวกเขาถูกทิ้งและแทนที่ด้วยภารกิจที่แสดงในเกมมาตรฐาน ผู้ที่ไม่รู้จักโปรแกรมเก่าเหล่านั้นกลับมาทำงานอีก
igouy

13
+1 ฉันไม่ชอบคนที่เข้ารหัส C ++ ไม่ว่าจะในรูปแบบ C หรือ Java จากนั้นระบุว่า Java เป็นแบบที่เหนือกว่า ข้อจำกัดความรับผิดชอบ: ฉันไม่ได้เรียกภาษาที่เหนือกว่าใด ๆ แต่การเขียนรหัส C ++ ที่เส็งเคร็งในสไตล์ที่อาจเหมาะสมกับภาษาอื่นไม่ได้ทำให้ทั้งสองภาษาเปรียบเทียบกัน
Christian Rau

111

C / C ++ ที่ผ่านการรีดด้วยมือโดยผู้เชี่ยวชาญที่มีเวลาไม่ จำกัดจะต้องเป็นอย่างน้อยเร็วกว่าหรือเร็วกว่า Java ท้ายที่สุดแล้วตัว Java เองเขียนด้วย C / C ++ ดังนั้นคุณสามารถทำทุกอย่างที่ Java ทำหากคุณเต็มใจที่จะทุ่มเทด้านวิศวกรรมอย่างเพียงพอ

อย่างไรก็ตามในทางปฏิบัติ Java มักจะดำเนินการอย่างรวดเร็วด้วยเหตุผลดังต่อไปนี้:

  • การคอมไพล์ JIT - แม้ว่าคลาส Java จะถูกเก็บเป็น bytecode แต่คอมไพเลอร์ของ JIT จะถูกคอมไพล์เป็นรหัสเนทีฟโดยปกติเมื่อโปรแกรมเริ่มทำงาน เมื่อรวบรวมแล้วจะเป็นโค้ดเนทีฟบริสุทธิ์ - ดังนั้นในทางทฤษฎีจึงสามารถคาดการณ์ได้ว่าจะทำงานเช่นเดียวกับการคอมไพล์ C / C ++ เมื่อโปรแกรมรันนานพอ (เช่นหลังจากการรวบรวม JIT ทั้งหมดเสร็จสิ้นแล้ว)
  • การรวบรวมขยะใน Java นั้นรวดเร็วและมีประสิทธิภาพอย่างมาก - ฮอตสปอต GC น่าจะเป็นการใช้งาน GC ที่ดีที่สุดในโลก เป็นผลมาจากความพยายามอย่างผู้เชี่ยวชาญของซันและ บริษัท อื่น ๆ ระบบการจัดการหน่วยความจำที่ซับซ้อนที่คุณม้วนตัวเองใน C / C ++ จะแย่กว่านี้มาก แน่นอนว่าคุณสามารถเขียนโครงร่างการจัดการหน่วยความจำพื้นฐานที่รวดเร็ว / น้ำหนักเบาได้ใน C / C ++ แต่จะไม่สามารถใช้งานได้หลากหลายเหมือนกับระบบ GC เต็มรูปแบบ เนื่องจากระบบที่ทันสมัยส่วนใหญ่ต้องการการจัดการหน่วยความจำที่ซับซ้อนดังนั้น Java จึงมีข้อได้เปรียบที่ยิ่งใหญ่สำหรับสถานการณ์ในโลกแห่งความเป็นจริง
  • การกำหนดเป้าหมายแพลตฟอร์มที่ดีขึ้น - โดยการหน่วงเวลาการคอมไพล์ไปยังแอปพลิเคชันเริ่มต้น (การรวบรวม JIT เป็นต้น) คอมไพเลอร์ Java สามารถใช้ประโยชน์จากความจริงที่ว่ารู้โปรเซสเซอร์ที่แน่นอนซึ่งกำลังรันอยู่ สิ่งนี้สามารถเปิดใช้งานการเพิ่มประสิทธิภาพที่เป็นประโยชน์บางอย่างที่คุณไม่สามารถทำได้ในโค้ด C / C ++ ที่คอมไพล์แล้วซึ่งจำเป็นต้องกำหนดเป้าหมายชุดคำสั่งตัวประมวลผล "ตัวหารร่วมที่ต่ำที่สุด"
  • สถิติรันไทม์ - เนื่องจากการรวบรวม JIT เสร็จสิ้นที่รันไทม์จึงสามารถรวบรวมสถิติในขณะที่โปรแกรมกำลังดำเนินการซึ่งเปิดใช้งานการเพิ่มประสิทธิภาพที่ดีขึ้น (เช่นการรู้ว่ามีความน่าจะเป็นที่สาขาใดสาขาหนึ่ง) สิ่งนี้สามารถเปิดใช้งานคอมไพเลอร์ Java JIT ในการสร้างโค้ดที่ดีกว่าคอมไพเลอร์ C / C ++ (ซึ่งต้อง "เดา" สาขาที่เป็นไปได้มากที่สุดล่วงหน้าซึ่งเป็นข้อสันนิษฐานที่มักจะผิด)
  • ไลบรารีที่ดีมาก - รันไทม์ Java มีโฮสต์ของไลบรารีที่เขียนได้ดีมากและมีประสิทธิภาพที่ดี (โดยเฉพาะอย่างยิ่งสำหรับแอปพลิเคชันฝั่งเซิร์ฟเวอร์) บ่อยครั้งที่สิ่งเหล่านี้ดีกว่าที่คุณเขียนเองหรือรับได้ง่ายสำหรับ C / C ++

ในขณะเดียวกัน C / C ++ ก็มีข้อดีดังนี้:

  • มีเวลามากขึ้นในการเพิ่มประสิทธิภาพขั้นสูง - การคอมไพล์ C / C ++ ทำได้ครั้งเดียวดังนั้นจึงสามารถใช้เวลาพอสมควรในการทำออพติไมซ์ขั้นสูงหากคุณกำหนดค่าให้ทำเช่นนั้น ไม่มีเหตุผลทางทฤษฎีที่ว่าทำไม Java ไม่สามารถทำแบบเดียวกันได้ แต่ในทางปฏิบัติคุณต้องการ Java เพื่อคอมไพล์โค้ด JIT ค่อนข้างเร็วดังนั้นคอมไพเลอร์ JIT จึงมีแนวโน้มที่จะมุ่งเน้นไปที่การปรับให้เหมาะสมที่สุด
  • คำแนะนำที่ไม่สามารถแสดงออกเป็น bytecode ได้ในขณะที่ Java bytecode นั้นมีจุดประสงค์ทั่วไปอย่างสมบูรณ์ แต่ก็ยังมีบางสิ่งที่คุณสามารถทำได้ในระดับต่ำซึ่งคุณไม่สามารถทำได้ใน bytecode โดย (ab) การใช้ลูกเล่นชนิดนี้คุณจะได้รับข้อได้เปรียบด้านประสิทธิภาพ
  • ข้อ จำกัด "ความปลอดภัย" น้อยลง - Java ทำงานพิเศษบางอย่างเพื่อให้แน่ใจว่าโปรแกรมปลอดภัยและเชื่อถือได้ ตัวอย่างเช่นการตรวจสอบขอบเขตในอาร์เรย์การรับรองพร้อมกันบางอย่างการตรวจสอบตัวชี้โมฆะพิมพ์ความปลอดภัยในการปลดเปลื้องเป็นต้นโดยการหลีกเลี่ยงสิ่งเหล่านี้ใน C / C ++ คุณจะได้รับประสิทธิภาพการทำงานเพิ่มขึ้น

Adj] ทั้งหมด, รวมทั้งหมด, รวมทั้งสิ้น:

  • Java และ C / C ++ สามารถบรรลุความเร็วใกล้เคียงกัน
  • C / C ++ อาจมีขอบเล็กน้อยในสถานการณ์ที่รุนแรง (ไม่น่าแปลกใจที่นักพัฒนาเกม AAA ยังคงชอบเกมนี้อยู่)
  • ในทางปฏิบัติมันจะขึ้นอยู่กับว่าปัจจัยต่าง ๆ ที่ระบุไว้ข้างต้นมีความสมดุลกับการใช้งานเฉพาะ

9
โฆษณา "เวลามากขึ้นสำหรับการปรับให้เหมาะสมใน C ++": นั่นเป็นหนึ่งใน tweaks ที่ Oracle VM ทำเมื่อคุณเลือก Server VM: ยอมรับต้นทุนการเริ่มต้นที่สูงขึ้นเพื่อให้ประสิทธิภาพสูงขึ้นในระยะยาว อย่างไรก็ตามไคลเอนต์ VM ถูก tweaked สำหรับเวลาเริ่มต้นที่ดีที่สุด ดังนั้นความแตกต่างจึงมีอยู่ใน Java
Joachim Sauer

8
-1: คอมไพเลอร์ C ++ สามารถใช้เวลามากขึ้น (ชั่วโมงแท้จริงสำหรับห้องสมุดขนาดใหญ่) เพื่อสร้างไบนารีที่ปรับให้เหมาะสมมาก คอมไพเลอร์ Java JIT ไม่สามารถใช้เวลาได้มากแม้แต่เวอร์ชั่น "เซิร์ฟเวอร์" ฉันสงสัยอย่างจริงจังว่าคอมไพเลอร์ Java JIT จะสามารถดำเนินการทั้งการเพิ่มประสิทธิภาพโปรแกรมทั้งหมดวิธีที่คอมไพเลอร์ MS C ++ ทำ
quant_dev

20
@quant_dev: แน่นอน แต่นั่นไม่ใช่สิ่งที่ฉันพูดในคำตอบของฉันในฐานะข้อได้เปรียบ C ++ (มีเวลามากขึ้นในการเพิ่มประสิทธิภาพขั้นสูง) แล้วทำไม -1
mikera

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

4
... แต่ด้วย C ++ คุณสามารถวาง "เลเยอร์เหมือน JIT" ในทางทฤษฎีซึ่งจะทำการเพิ่มประสิทธิภาพของสาขาที่คล้ายกันที่รันไทม์ในขณะที่รักษาความเร็วดิบของโปรแกรม C ++ (ในทางทฤษฎี :()
Mateen Ulhaq

19

Java runtime ไม่ได้ตีความ bytecode แต่จะใช้อะไรที่เรียกว่าเพียงในเวลารวบรวม โดยทั่วไปขณะที่โปรแกรมกำลังทำงานจะใช้เวลา bytecode และแปลงเป็นรหัสเนทีฟที่เหมาะสำหรับ CPU เฉพาะ


ในทางปฏิบัติใช่ โดยหลักการแล้วมันขึ้นอยู่กับ - เครื่องเสมือน Java ยุคแรกที่ใช้เครื่องแปล bytecode และคุณอาจยังสามารถค้นหาการแปลโดยใช้ vtec หากคุณดูยากพอ
Steve314

10
@ Steve314: แต่การตีความ VMs ล้วนๆจะไม่ดีกว่า C ++ ดังนั้นจึงไม่เกี่ยวข้องกับคำถามนี้จริงๆ
Joachim Sauer

คอมไพเลอร์ JIT ยังอาจปรับให้เหมาะสมแบบไดนามิกสำหรับการใช้งานเฉพาะของรหัสซึ่งเป็นไปไม่ได้กับรหัสที่รวบรวมแบบคงที่
starblue

2
@ starblue ก็เป็นไปได้ด้วยการรวบรวมแบบคงที่ - ดูการเพิ่มประสิทธิภาพแนะนำโปรไฟล์
SK-logic

18

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

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

นอกจากนี้สิ่งนี้ทำให้ฉันนึกถึงการอภิปรายเกี่ยวกับ C ใน 80s / 90s ทุกคนต่างก็สงสัยว่า "C จะเร็วเท่ากับการชุมนุมได้ไหม" โดยพื้นฐานแล้วคำตอบคือ: ไม่มีบนกระดาษ แต่ในความเป็นจริงแล้วคอมไพเลอร์ C สร้างโค้ดที่มีประสิทธิภาพมากกว่า 90% ของโปรแกรมเมอร์แอสเซมบลี (ดีเมื่อมันสุกเล็กน้อย)


2
เกี่ยวกับ GC ไม่ใช่แค่ว่า GC อาจชะลอการทำลายวัตถุ (ซึ่งไม่น่าจะเกิดขึ้นในระยะยาว) ความจริงก็คือด้วย GCs ที่ทันสมัยการจัดสรร / การจัดสรรคืนวัตถุที่มีอายุสั้นนั้นมีราคาถูกมากใน Java เมื่อเทียบกับ C ++
PéterTörök

@ PéterTörökใช่คุณพูดถูก
Daniel B

9
@ PéterTörök แต่ใน C ++ วัตถุที่มีอายุสั้นมักจะวางบนสแต็กซึ่งในทางกลับกันจะเร็วกว่า GC-ed heap Java ใด ๆ ที่สามารถใช้งานได้
quant_dev

@quant_dev คุณลืมเอฟเฟกต์ GC อย่างมีนัยสำคัญอื่น: การย่อขนาด ดังนั้นฉันจะไม่แน่ใจว่าวิธีไหนเร็วกว่า
SK-logic

3
@DonalFellows อะไรที่ทำให้คุณคิดว่าฉันต้องกังวลเกี่ยวกับการจัดการหน่วยความจำใน C ++? เวลาส่วนใหญ่ที่ฉันทำไม่ได้ มีรูปแบบง่าย ๆ ที่คุณต้องใช้ซึ่งแตกต่างจาก Java แต่นั่นคือมัน
quant_dev

10

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

...

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

http://www.ibm.com/developerworks/java/library/j-jtp09275/index.html


นี่เป็นเพียงส่วนเล็ก ๆของภาพรวม แต่ก็มีความเกี่ยวข้อง
Joachim Sauer

2
ฉันชอบที่เนื้อหาของเรื่องนี้คือ: java สำหรับ noobs, เชื่อใจ magic GC, มันรู้ดีกว่า
Morg

1
@Morg: หรือคุณสามารถอ่านแบบนั้นได้: Java สำหรับคนที่ชอบทำสิ่งต่าง ๆ แทนที่จะเสียเวลากับ bit twiddling และ manual memory management
Landei

4
@Landei ฉันคิดว่าความคิดเห็นของคุณจะมีความน่าเชื่อถือมากขึ้นหากมีการเขียน codebase ที่ใช้กันอย่างแพร่หลายใน Java ในโลกของฉันระบบปฏิบัติการจริงเขียนด้วยภาษา C ซึ่งเป็น postgreSQL เขียนด้วยภาษา C เนื่องจากเป็นเครื่องมือที่สำคัญที่สุดที่จะทำให้การเขียนใหม่เป็นเรื่องที่เจ็บปวดจริงๆ Java เป็น (และเป็นเวอร์ชั่นอย่างเป็นทางการ) เพื่อให้ผู้ที่มีทักษะน้อยกว่าสามารถเขียนโปรแกรมในฝูงและยังได้ผลลัพธ์ที่เป็นรูปธรรม
Morg

1
@ มอร์กฉันคิดว่ามันแปลกมากที่คุณดูเหมือนจะให้ความสำคัญกับระบบปฏิบัติการ สิ่งนี้ไม่สามารถวัดได้ดีด้วยเหตุผลหลายประการ ข้อแรกข้อกำหนดของระบบปฏิบัติการแตกต่างอย่างมากจากซอฟต์แวร์อื่น ๆ ส่วนที่สองคุณมีหลักการ Thumb Panda (ผู้ที่ต้องการเขียนระบบปฏิบัติการที่สมบูรณ์ในภาษาอื่นใครต้องการเขียนระบบปฏิบัติการของตัวเองหากมีการทำงานและแม้กระทั่งทางเลือกฟรี) และซอฟต์แวร์ตัวที่สามใช้คุณสมบัติของระบบปฏิบัติการดังนั้นจึงไม่จำเป็นต้องเขียนไดรเวอร์ดิสก์ตัวจัดการงาน ฯลฯ หากคุณไม่สามารถหาข้อโต้แย้งที่ดีกว่า (ไม่ได้อิงกับระบบปฏิบัติการทั้งหมด) คุณจะฟังดูเหมือนเกลียด
Landei

5

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

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

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

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


5

โพสต์โดย Tim Holloway บน JavaRanch:

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

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

ที่มา: http://www.coderanch.com/t/547458/Performance/java/Ahead-Time-vs-Just-time


3
และอีกครั้งนี่เป็นสิ่งที่ผิด / ไม่สมบูรณ์ คอมไพเลอร์คงที่ที่มีรายละเอียด Optimsation แนะนำสามารถรับรู้นี้
Konrad Rudolph

2
Konrad คอมไพเลอร์สแตติกสามารถพลิกลอจิกตามปริมาณงานปัจจุบันได้หรือไม่ ดังที่ฉันเข้าใจคอมไพเลอร์สแตติกสร้างรหัสครั้งเดียวและมันยังคงเหมือนเดิมตลอดไป
Thiago Negri

2
ปริมาณงานปัจจุบันไม่มี แต่ภาระงานทั่วไป การปรับให้เหมาะสมที่แนะนำด้วยโปรไฟล์จะวิเคราะห์ว่าโปรแกรมของคุณทำงานอย่างไรภายใต้ภาระทั่วไปและปรับฮอตสปอตให้เหมาะสมเช่นเดียวกับ HotSpot JIT
Konrad Rudolph

4

นั่นเป็นเพราะขั้นตอนสุดท้ายในการสร้างรหัสเครื่องเกิดขึ้นอย่างโปร่งใสภายใน JVM เมื่อรันโปรแกรม Java ของคุณแทนที่จะชัดเจนเมื่อสร้าง C ++ proram ของคุณ

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

เพียงแค่เป็นสิ่งที่ฝังตัวใน getter โดยอัตโนมัติดังนั้น JUMP-RETURN ไม่จำเป็นต้องมีเพื่อรับค่าและเพิ่มความเร็วให้กับสิ่งต่าง ๆ

อย่างไรก็ตามสิ่งที่อนุญาตให้โปรแกรมอย่างรวดเร็วจริง ๆ คือการล้างข้อมูลในภายหลัง กลไกการรวบรวมขยะใน Java นั้นเร็วกว่าการไม่ใช้ malloc แบบแมนนวลใน C. การใช้งานแบบ malloc-free ที่ทันสมัยใช้ตัวเก็บขยะที่อยู่ด้านล่าง


โปรดทราบว่าสิ่งที่ฝังอยู่นี้ทำให้การเริ่มต้น JVM ใหญ่ขึ้นและช้าลงจนกว่าโค้ดที่ดีกว่าจะมีโอกาสได้ติดตาม

1
"การใช้งานที่ไม่มี malloc ที่ทันสมัยจำนวนมากใช้ตัวเก็บขยะที่อยู่ด้านล่าง" จริงๆ? ฉันต้องการทราบรายละเอียดเพิ่มเติม; คุณมีการอ้างอิงใด ๆ
Sean McMillan

ขอขอบคุณ. ฉันพยายามหาวิธีที่จะบอกว่า JVM ไม่ได้มีเพียงแค่คอมไพเลอร์ในเวลารวบรวมรหัสปฏิบัติการ แต่ฮอตสปอตคอมไพเลอร์ซึ่งโปรไฟล์รหัสทำงานและเพิ่มประสิทธิภาพต่อไปเป็นผล ฉันคอมไพเลอร์ครั้งเดียวเช่น C ++ ดิ้นรนเพื่อให้ตรงกับที่
Highland Mark

@SeanMcMillan ฉันเห็นการวิเคราะห์ในขณะที่กลับมาเกี่ยวกับประสิทธิภาพของการใช้งานที่ปราศจาก malloc ซึ่งได้รับการกล่าวถึงว่าคนที่เร็วที่สุดใช้ตัวรวบรวมขยะใต้ ฉันจำไม่ได้ว่าอ่านตรงไหน

มันคือ BDW อนุรักษ์นิยม GC หรือไม่?
Demi

4

คำตอบสั้น ๆ - ไม่ใช่ ลืมมันไปเถอะหัวข้อนั้นเก่าแก่เท่ากับไฟหรือล้อ Java หรือ. NET ไม่ได้และจะไม่เร็วกว่า C / C ++ มันเร็วพอสำหรับงานส่วนใหญ่ที่คุณไม่ต้องคิดเกี่ยวกับการปรับให้เหมาะสมเลย เช่นเดียวกับฟอร์มและการประมวลผล SQL แต่นั่นคือจุดสิ้นสุด

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

ในความเป็นจริงสิ่งที่ง่าย ๆ เช่นการจัดสรรหน่วยความจำบนสแต็คหรือเพียงแค่ใช้ memzones จะเป็นการฆ่า Java / .NET ตรงจุด

โลกเก็บขยะกำลังใช้ memzone เรียงกับบัญชีทั้งหมด เพิ่ม memzone ไปที่ C และ C จะเร็วขึ้น ณ จุดนั้น โดยเฉพาะอย่างยิ่งสำหรับการเปรียบเทียบ Java "C รหัสประสิทธิภาพสูง" กับ Java ที่เป็นเช่นนี้:

for(...)
{
alloc_memory//Allocating heap in a loop is verrry good, in't it?
zero_memory//Extra zeroing, we really need it in our performance code
do_stuff//something like memory[i]++
realloc//This is lovely speedup
strlen//loop through all memory, because storing string length is soo getting old
free//Java will do that outside out timing loop, but oh well, we're comparing apples to oranges here
}//loop 100000 times

ลองใช้ตัวแปรตามสแต็คใน C / C ++ (หรือวางตำแหน่งใหม่) พวกมันแปลเป็นsub esp, 0xffมันเป็นคำสั่ง x86 เดียวเอาชนะมันด้วย Java - คุณไม่สามารถ ...

เวลาส่วนใหญ่ที่ฉันเห็นม้านั่งที่เปรียบเทียบ Java กับ C ++ นั้นทำให้ฉันไปเหมือนกันใช่มั้ย กลยุทธ์การจัดสรรหน่วยความจำผิดบรรจุภัณฑ์ที่เติบโตเองโดยไม่ต้องสำรองหลายรายการใหม่ นี่ไม่ได้ใกล้เคียงกับโค้ด C / C ++ ที่เน้นประสิทธิภาพ

นอกจากนี้อ่านดี: https://days2011.scala-lang.org/sites/days2011/files/ws3-1-Hundt.pdf


1
ไม่ถูกต้อง. ผิดอย่างสิ้นเชิง คุณจะไม่สามารถทำได้ดีกว่า GC ที่มีขนาดกะทัดรัดด้วยการจัดการหน่วยความจำด้วยตนเองของคุณ การนับการอ้างอิงที่ไร้เดียงสาจะไม่ดีไปกว่าการร้องไห้ที่เหมาะสม ทันทีที่มันมาถึงการจัดการหน่วยความจำที่ซับซ้อน C ++ คือการชะลอ
SK-logic

3
@ SK-Logic: ผิดพลาดด้วย memzones หรือการจัดสรรสแต็กไม่มีการจัดสรรหน่วยความจำหรือการจัดสรรคืนทั้งหมด คุณมีบล็อกของหน่วยความจำและคุณเพิ่งเขียนไป ทำเครื่องหมายว่าบล็อกว่างกับตัวแปรระเหยเช่นการป้องกันการทำงานพร้อมกัน InterlockedExchange ฯลฯ และเธรดถัดไปจะทิ้งข้อมูลไปยังบล็อกที่จัดสรรล่วงหน้าโดยไม่ต้องไปที่ OS สำหรับหน่วยความจำเลยถ้าเห็นว่ามันว่าง ด้วยสแต็กมันง่ายยิ่งกว่าด้วยข้อยกเว้นเดียวที่คุณไม่สามารถดัมพ์ 50MB บนสแต็ก และอายุการใช้งานของวัตถุนั้นจะอยู่ภายใน {} เท่านั้น
Coder

2
@ SK-logic: คอมไพเลอร์มีความถูกต้องก่อนประสิทธิภาพที่สอง เสิร์ชเอ็นจิ้นฐานข้อมูลระบบการซื้อขายเรียลไทม์เกมเป็นสิ่งที่ฉันจะพิจารณาประสิทธิภาพที่สำคัญ และส่วนใหญ่นั้นพึ่งพาโครงสร้างที่ราบเรียบ คอมไพเลอร์ส่วนใหญ่เขียนด้วยภาษา C / C ++ ด้วยผู้จัดสรรที่กำหนดเองฉันเดา จากนั้นอีกครั้งฉันไม่เห็นปัญหาใด ๆ กับการใช้ทรีหรือรายการองค์ประกอบเหนือ rammap คุณเพิ่งใช้ตำแหน่งใหม่ ไม่มีความซับซ้อนในนั้น
Coder

3
@ SK-logic: มันไม่เร็วกว่านี้ทุกแอป NET. / Java ที่ฉันเคยเห็นกลายเป็นช้าลงและเป็นหมูแท้ การเขียนแอพที่ได้รับการจัดการทุกครั้งลงในโค้ด SANE C / C ++ จะทำให้แอปที่สะอาดและเบาขึ้น แอพที่มีการจัดการมักมีน้ำหนักมาก ดู VS2010 เทียบกับปี 2008 โครงสร้างข้อมูลเดียวกัน แต่ VS2010 เป็น HOG แอป C / C ++ ที่เขียนอย่างถูกต้องมักจะบู๊ตเป็นมิลลิวินาทีและไม่ติดขัดบนหน้าจอสแปลชในขณะที่ใช้หน่วยความจำน้อยลง ข้อเสียเพียงอย่างเดียวคือคุณต้องใช้รหัสกับฮาร์ดแวร์ในใจและผู้คนจำนวนมากไม่รู้ว่ามันเป็นอย่างไรในปัจจุบัน เป็นเพียงมาตรฐานที่ผู้บริหารมีโอกาส
Coder

2
หลักฐานประวัติของคุณจะไม่ถูกนับ มาตรฐานที่เหมาะสมแสดงความแตกต่างที่แท้จริง เป็นเรื่องแปลกที่คุณอ้างถึงแอปพลิเคชั่น GUI ที่ผูกกับไลบรารี GUI ขนาดใหญ่และไม่น่าสนใจ และที่สำคัญกว่านั้นคือในทางทฤษฎีแล้วขีด จำกัด ประสิทธิภาพนั้นสูงกว่ามากสำหรับการใช้งาน GC อย่างถูกต้อง
SK-logic

2

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

ภาษาไม่ได้ "ช้า" โปรแกรมเมอร์เขียนโปรแกรมช้า โปรแกรมจะเขียนวิธีที่ดีที่สุดในภาษาใดภาษาหนึ่ง (เพื่อจุดประสงค์ในทางปฏิบัติ) โปรแกรมที่ทำสิ่งเดียวกันโดยใช้วิธีที่ดีที่สุดของภาษาทางเลือกเว้นแต่ผู้เขียนการศึกษาจะบดขวานเฉพาะของเขา

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


2
ในทางทฤษฎี JITting VM ที่ "ดีเลิศ" จะต้องมีประสิทธิภาพสูงกว่าโค้ดที่คอมไพล์แบบสแตติกโดยการปรับออปติไมซ์ให้เหมาะสมกับข้อมูลการทำโปรไฟล์ที่รวบรวมแบบไดนามิก ในทางปฏิบัติคอมไพเลอร์ของ JIT นั้นยังไม่ฉลาด แต่อย่างน้อยพวกเขาก็สามารถสร้างโค้ดที่มีคุณภาพใกล้เคียงกันเช่นเดียวกับเพื่อนร่วมงานที่ใหญ่และช้ากว่า
SK-logic

2

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

  1. การโพสต์บล็อกเหล่านั้นมีหลักฐานที่น่าเชื่อถือหรือไม่
  2. การโพสต์บล็อกเหล่านั้นมีหลักฐานที่ชัดเจนหรือไม่?
  3. โพสต์บล็อกเหล่านั้นมีหลักฐานเกี่ยวกับ

Keith Lea บอกคุณว่ามี "ข้อบกพร่องที่เห็นได้ชัด" แต่ไม่ทำอะไรเลยเกี่ยวกับ "ข้อบกพร่องที่เห็นได้ชัด" เหล่านั้น กลับไปในปี 2005 งานเก่าที่ถูกทิ้งและถูกแทนที่ด้วยงานในขณะนี้แสดงให้เห็นในเกมมาตรฐาน

คี ธ Lea บอกคุณว่าเขา "เอารหัสมาตรฐานสำหรับ C ++ และ Java จากล้าสมัยในขณะนี้ที่ดีคอมพิวเตอร์ภาษายิงและวิ่งทดสอบ" แต่จริง ๆ แล้วเขาจะแสดงเฉพาะวัดที่ 14 จาก 25 ของการทดสอบที่ล้าสมัยเหล่านั้น

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

Christian Felde บอกคุณว่า "ฉันไม่ได้สร้างรหัสเพียงแค่เรียกใช้การทดสอบอีกครั้ง" ราวกับว่าจะปลดเปลื้องเขาจากความรับผิดชอบใด ๆ สำหรับการตัดสินใจของเขาในการเผยแพร่การวัดงานและโปรแกรมที่ Keith Lea เลือก

การวัดของโปรแกรมขนาดเล็กแม้แต่ 25 รายการนั้นมีหลักฐานที่ชัดเจนหรือไม่

การวัดเหล่านี้ใช้สำหรับโปรแกรมที่ทำงานเป็น "โหมดผสม" Java ไม่ได้แปล Java - "จำได้ว่า HotSpot ทำงานอย่างไร" คุณสามารถค้นหาว่า Java รัน "ตีความ bytecode" ได้ดีเพียงใดเพราะคุณสามารถบังคับให้ Java ตีความเฉพาะ bytecode ได้เพียงเวลาที่โปรแกรม Java บางตัวทำงานด้วยและไม่มีตัวเลือก -Xint


-1

มันทำให้ฉันประหลาดใจว่าความคิดแปลก ๆ ที่แพร่หลายของ "การตีความ bytecode" นี้เป็นอย่างไร คุณเคยได้ยินคนรวบรวม JIT บ้างไหม? อาร์กิวเมนต์ของคุณไม่สามารถใช้กับ Java

แต่การทิ้ง JVM ไว้ข้างๆมีหลายกรณีที่โค้ดแบบเธรดโดยตรงหรือแม้แต่การตีความ bytecode เล็กน้อยสามารถทำได้ดีกว่าโค้ดเนทีฟที่ได้รับการปรับปรุงอย่างมาก คำอธิบายนั้นง่ายมาก: bytecode นั้นค่อนข้างเล็กและจะพอดีกับแคชเล็ก ๆ ของคุณเมื่ออัลกอริธึมรุ่นเดียวกันของโค้ดเนทีฟจะจบลงด้วยการที่แคชหลายตัวขาดหายไปสำหรับการทำซ้ำเพียงครั้งเดียว


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

@ ดังนั้นโอกาส CS-fu ของคุณเองนั้นค่อนข้างอ่อนแอ JVM ไม่ได้ทำการตีความใด ๆ (สำหรับฮอตสปอต) - ในฐานะคนที่กล้าพูดถึง CS คุณต้องรู้ว่ามันหมายถึงอะไรและความหมายในการปฏิบัติการของการตีความนั้นแตกต่างจากการใช้รหัสพื้นเมือง แต่เป็นไปได้ว่าคุณไม่รู้จัก CS เพียงพอที่จะแยกแยะการรวบรวมจากการตีความ
SK-logic

2
Umm แต่ bytecode จะต้องถูกแปลงเป็นรหัสเนทีฟ - คุณไม่สามารถฟีด Java bytecode ไปยัง CPU อาร์กิวเมนต์ขนาดจึงไม่ถูกต้อง
quant_dev

@quant_dev แน่นอน - ฉันบอกว่ากรณีนี้ไม่เกี่ยวข้องกับ JVM ทั้งหมด คุณต้องการเอ็นจิ้น bytecode ที่ง่ายกว่ามากเพื่อให้เอฟเฟกต์นั้นทำงานได้
SK-logic

-1

JIT, GC และอื่น ๆ , C ++ สามารถทำได้ง่ายมากทำให้ง่ายกว่า Java สิ่งนี้จะไม่แสดงในการวัดประสิทธิภาพ แต่แอพเดียวกันที่เขียนโดยนักพัฒนา Java และนักพัฒนา C ++ อาจเร็วกว่าใน Java

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

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

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


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

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

ฉันโต้แย้งว่าความจริงทางจิตวิทยานี้เป็นจริงและแม้ว่าคุณจะไม่มีทางเลือก : ถ้าคุณต้องการฟังก์ชั่นคุณใช้มันไม่ว่ามันจะถูกห่อหุ้มในโอเปอเรเตอร์หรือวิธีการ จิตวิทยาไม่เกี่ยวข้องกับการเลือกความหมายของคุณ
Konrad Rudolph

1
คำถามซ่อนเงื่อน. ฉันจะไม่สองเดานี้ทั้งหมดผมจะวัดทำหน้าที่แล้ว ฉันไม่เคยมีปัญหากับชั้นเชิง
Konrad Rudolph

1
@ KonradRudolph: ทั้งหมดนี้เป็นจริงเมื่อมันมาถึงความชัดเจนและความง่ายในการเขียนรหัสทำให้ปราศจากข้อผิดพลาดและสามารถบำรุงรักษาได้ จุดเกี่ยวกับประสิทธิภาพของการใช้อัลกอริทึมยังคงอยู่: หากคุณกำลังจะเขียนobj.fetchFromDatabase("key")สามครั้งภายในห้าบรรทัดของรหัสสำหรับคีย์เดียวกันคุณจะคิดว่าสองครั้งเพื่อดึงค่านั้นครั้งเดียวและแคชในตัวแปรท้องถิ่น ถ้าคุณเขียนobj->"key"ด้วย->การโอเวอร์โหลดเพื่อทำหน้าที่ดึงฐานข้อมูลคุณมักจะปล่อยให้มันผ่านไปเพราะค่าใช้จ่ายในการดำเนินการไม่ชัดเจน
เอสเอฟ

-2

อย่างใดการแลกเปลี่ยนสแต็คไม่ได้ใช้สแต็คพอยต์อื่น ๆ ของฉันดังนั้น ... ไม่มีคำตอบน่าเสียดาย ...

อย่างไรก็ตามคำตอบที่ได้รับการโหวตมากที่สุดเป็นอันดับสองที่นี่เต็มไปด้วยข้อมูลที่ผิดในความเห็นที่ต่ำต้อยของฉัน

แอปที่เขียนด้วยมือโดยผู้เชี่ยวชาญใน C / C ++ นั้นจะเร็วกว่าแอปพลิเคชัน Java อย่างมากตลอดเวลา ไม่มี 'เร็วเท่ากับ Java หรือเร็วกว่า' มันเร็วขึ้นอย่างแม่นยำเนื่องจากรายการที่คุณอ้างอิงด้านล่าง:

การรวบรวม JIT : คุณคาดหวังว่าเครื่องมือเพิ่มประสิทธิภาพอัตโนมัติจะมีสมาร์ทของโปรแกรมเมอร์ผู้เชี่ยวชาญและดูลิงก์ระหว่างเจตนาและรหัสที่ CPU กำลังทำงานอยู่หรือไม่? นอกจากนี้ JIT ทั้งหมดที่คุณทำคือเสียเวลาเมื่อเทียบกับโปรแกรมที่รวบรวมไว้แล้ว

Garbage Collectionเป็นเครื่องมือที่จัดสรรคืนทรัพยากรที่โปรแกรมเมอร์จะลืมที่จะจัดสรรคืนอย่างมีประสิทธิภาพมากขึ้นหรือน้อยลง

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

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

สถิติรันไทม์ นี่เกินความรู้ของฉัน แต่ฉันสงสัยว่าผู้เชี่ยวชาญใน C มีความรู้ในการทำนายสาขามากเกินพอที่จะทำให้การเพิ่มประสิทธิภาพอัตโนมัติดีกว่า -

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

JVM เป็นเลเยอร์ของสิ่งที่เป็นนามธรรมซึ่งแสดงถึงทั้งสิ่งที่ดีหลายอย่างซึ่งอยู่ด้านบนและยังหมายความว่าโซลูชันโดยรวมนั้นช้ากว่าด้วยการออกแบบ

Adj] ทั้งหมด, รวมทั้งหมด, รวมทั้งสิ้น:

Java ไม่สามารถเข้าถึงความเร็วของ C / C ++ เนื่องจากวิธีการทำงานใน JVM ที่มีการป้องกันคุณสมบัติและเครื่องมือมากมาย

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

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

และโดยสรุปฉันอยากจะบอกว่าคนส่วนใหญ่ไม่ให้สองเซ็นต์เกี่ยวกับเรื่องนี้ว่าในท้ายที่สุดการเพิ่มประสิทธิภาพเป็นกีฬาที่สงวนไว้สำหรับผู้โชคดีเพียงไม่กี่คนเท่านั้นและยกเว้นในกรณีที่ประสิทธิภาพเป็นกังวลจริงๆ (iE ที่การคูณฮาร์ดแวร์ด้วย 10 จะไม่ช่วยคุณ - หรือแสดงอย่างน้อยสองสามล้าน) ผู้จัดการส่วนใหญ่จะชอบแอพที่ไม่ได้เพิ่มประสิทธิภาพและฮาร์ดแวร์มากมาย


อีกครั้ง การรวบรวมขยะไม่เพียงเป็น "เครื่องมือที่จัดสรรคืน" GC สามารถย่อขนาดโครงสร้างของคุณ GC สามารถจัดการข้อมูลอ้างอิงที่อ่อนแอของคุณและช่วยให้คุณรักษาสมดุลการแคชด้วยวิธีนี้ Multistage GC ทำให้การจัดสรรฮีปมี ราคาถูกกว่าเทอะทะช้าnewหรือmalloc()ใหญ่มาก โดยทั่วไปจะเร็วกว่าการจัดการหน่วยความจำด้วยตนเองมากเนื่องจากคุณจะไม่สามารถย้ายวัตถุด้วยตนเอง ดังนั้นการใช้เหตุผลของคุณทั้งหมดจึงผิดและลำเอียงอย่างชัดเจน ความรู้ของคุณเกี่ยวกับอัลกอริธึม GC และวิธีเพิ่มประสิทธิภาพ JIT นั้น จำกัด เกินไป
SK-logic

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

ขอบคุณสำหรับความคิดเห็นของคุณ SK-logic แต่ในขณะที่คุณระบุ GC สามารถทำได้เร็วกว่าโดยทั่วไปเรากำลังพูดถึงสิ่งที่จะเร็วที่สุดในบางกรณีและดูเหมือนว่าคนส่วนใหญ่ยอมรับว่า GC สามารถทำได้ ทำโปรแกรมเมอร์สามารถทำและดียิ่งขึ้น แน่นอนคุณสามารถย้ายวัตถุด้วยตนเองเมื่อคุณเข้าถึงหน่วยความจำโดยตรง .. lol ความรู้เกี่ยวกับ JVM internals ของฉันมี จำกัด อย่างแน่นอนและฉันคาดหวังว่า Java-heads จะแสดงแสงให้ฉันไม่เพียงแค่บอกฉันแบบสุ่มอึเกี่ยวกับ GC ความสามารถในการทำสิ่งที่ไม่สามารถทำได้ด้วยตนเอง (ฮ่า ๆ ... แม้แต่ GC ต้อง ใช้คำสั่ง CPU;))
Morg

Konrad ฉันเห็นด้วยว่าฉันดูถูกตัวเพิ่มประสิทธิภาพสมัยใหม่ส่วนใหญ่ดูไม่ดี ... อย่างไรก็ตามฉันคิดว่ามันน่าสนใจที่คุณจะพิจารณาโค้ดที่ปรับปรุงด้วยมือว่าด้อยกว่าโค้ดเพิ่มประสิทธิภาพอัตโนมัติ คุณคาดหวังอะไรกับผู้แปลที่เห็นว่ามนุษย์ไม่สามารถทำได้?
Morg

1
ถูกต้อง ผลักดันอย่างต่อเนื่อง -1 นั่นจะไม่เปลี่ยนความจริงที่ว่า C ++ เร็วกว่า Java ฉันอาจไม่ทราบมากเกี่ยวกับคอมไพเลอร์สมัยใหม่ แต่ก็ไม่ได้สร้างความแตกต่างให้กับประเด็นหลักซึ่งถูกต้องและขัดแย้งกับคำตอบที่ได้รับการโหวตมากที่สุดที่นี่ เหตุใด C ++ จะให้ความสำคัญกับ nVidia อีกครั้งสำหรับ GPU ของพวกเขาสำหรับ HPC ทำไมเกมอื่น ๆ ถึงเขียนด้วยภาษา C ++ ทำไมโปรแกรม DB ทุกตัวถึงเขียนด้วยภาษา C?
Morg

-4

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


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