การใช้คำหลักสุดท้ายใน Java ปรับปรุงประสิทธิภาพหรือไม่


347

ใน Java เราเห็นสถานที่มากมายที่finalสามารถใช้คำหลักได้ แต่การใช้คำหลักนั้นผิดปกติ

ตัวอย่างเช่น:

String str = "abc";
System.out.println(str);

ในกรณีข้างต้นstrอาจเป็นได้finalแต่นี่เป็นเรื่องปกติที่เหลือ

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

การใช้คำหลักสุดท้ายในกรณีเหล่านี้ใด ๆ หรือทั้งหมดปรับปรุงประสิทธิภาพหรือไม่? ถ้าเป็นเช่นนั้นได้อย่างไร กรุณาอธิบาย. หากการใช้งานที่เหมาะสมมีความfinalสำคัญอย่างยิ่งต่อการปฏิบัติงานจริงๆแล้วโปรแกรมเมอร์ Java ควรพัฒนานิสัยอย่างไรให้ใช้ประโยชน์จากคำหลักได้ดีที่สุด


ฉันไม่คิดว่าเพื่อน, วิธีการเยี่ยงอย่าง (แคชเว็บไซต์โทรและ ... ) เป็นปัญหาในภาษาแบบไดนามิกไม่ได้อยู่ในภาษาประเภทคงที่
Jahan

ถ้าฉันเรียกใช้เครื่องมือ PMD ของฉัน (plugin to eclipse) ที่ใช้สำหรับตรวจสอบจุดประสงค์ก็ขอแนะนำให้ทำการเปลี่ยนแปลงตัวแปรในกรณีที่แสดงข้างต้น แต่ฉันไม่เข้าใจแนวคิดของมัน ประสิทธิภาพเป็นที่นิยมอย่างมาก ??
Abhishek Jain

5
ฉันคิดว่านี่เป็นคำถามสอบทั่วไป ผมจำได้ว่าครั้งสุดท้ายที่จะมีอิทธิพลต่อประสิทธิภาพการเรียน IIRC สุดท้ายสามารถเพิ่มประสิทธิภาพโดย JRE ในทางใดทางหนึ่งเพราะพวกเขาไม่สามารถ subclassed
Kawu

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

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

คำตอบ:


285

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

(แน่นอนว่านี่เป็นการสมมติว่าคุณกำลังใช้ HotSpot - แต่มันก็เป็น JVM ที่พบได้บ่อยที่สุดดังนั้น ... )

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

แก้ไข: ตามที่ได้รับการกล่าวถึงเขตข้อมูลสุดท้ายมันก็คุ้มค่าที่จะนำเสนอว่าพวกเขามักจะเป็นความคิดที่ดีในแง่ของการออกแบบที่ชัดเจน พวกเขายังเปลี่ยนพฤติกรรมการรับประกันในแง่ของการมองเห็นข้ามเธรด: หลังจากตัวสร้างเสร็จสมบูรณ์ฟิลด์สุดท้ายใด ๆ ที่รับประกันว่าจะสามารถมองเห็นได้ในหัวข้ออื่น ๆ ทันที นี่อาจเป็นสิ่งที่ใช้บ่อยที่สุดfinalในประสบการณ์ของฉันถึงแม้ว่าในฐานะผู้สนับสนุนของ "การออกแบบของ Josh Bloch สำหรับการสืบทอดหรือห้าม" กฎง่ายๆ แต่ฉันควรใช้finalบ่อยขึ้นสำหรับคลาส ...


1
@Abhishek: โดยเฉพาะเกี่ยวกับอะไร จุดที่สำคัญที่สุดคือจุดสุดท้าย - คุณแทบไม่ควรกังวลเกี่ยวกับเรื่องนี้
Jon Skeet

9
@Abishek: finalแนะนำโดยทั่วไปเพราะมันทำให้โค้ดง่ายต่อการเข้าใจและช่วยหาข้อผิดพลาด (เพราะมันทำให้ความตั้งใจของโปรแกรมเมอร์ชัดเจน) PMD อาจแนะนำให้ใช้finalเนื่องจากปัญหาสไตล์เหล่านี้ไม่ใช่เหตุผลด้านประสิทธิภาพ
sleske

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

5
นี่ฉันจะพูดมีผลบังคับใช้ Java, พิมพ์ครั้งที่ 2 รายการที่ 15 Immutable classes are easier to design, implement, and use than mutable classes. They are less prone to error and are more secure.ลดความผันแปร: นอกจากVSAn immutable object can be in exactly one state, the state in which it was created. Mutable objects, on the other hand, can have arbitrarily complex state spaces.จากประสบการณ์ส่วนตัวของฉันการใช้คำหลักfinalควรเน้นจุดประสงค์ของนักพัฒนาเพื่อโน้มตัวไปสู่การเปลี่ยนแปลงไม่ได้และไม่ควรใช้รหัส "ปรับให้เหมาะสม" ฉันขอแนะนำให้คุณอ่านบทนี้น่าทึ่ง!
Louis F.

2
คำตอบอื่น ๆ แสดงให้เห็นว่าการใช้finalคำหลักกับตัวแปรสามารถลดจำนวน bytecode ซึ่งอาจส่งผลต่อประสิทธิภาพ
Julien Kronegg

86

คำตอบสั้น ๆ : ไม่ต้องกังวลกับมัน!

คำตอบยาว:

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

public class FinalTest {

    public static final int N_ITERATIONS = 1000000;

    public static String testFinal() {
        final String a = "a";
        final String b = "b";
        return a + b;
    }

    public static String testNonFinal() {
        String a = "a";
        String b = "b";
        return a + b;
    }

    public static void main(String[] args) {
        long tStart, tElapsed;

        tStart = System.currentTimeMillis();
        for (int i = 0; i < N_ITERATIONS; i++)
            testFinal();
        tElapsed = System.currentTimeMillis() - tStart;
        System.out.println("Method with finals took " + tElapsed + " ms");

        tStart = System.currentTimeMillis();
        for (int i = 0; i < N_ITERATIONS; i++)
            testNonFinal();
        tElapsed = System.currentTimeMillis() - tStart;
        System.out.println("Method without finals took " + tElapsed + " ms");

    }

}

ผลลัพธ์?

Method with finals took 5 ms
Method without finals took 273 ms

ทดสอบกับ Java Hotspot VM 1.7.0_45-b18

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

ผลประโยชน์จากการรวบรวมเวลาฉันไม่สามารถหาหลักฐานใด ๆ ที่การใช้คำหลักfinalนั้นมีผลกระทบต่อประสิทธิภาพที่วัดได้


2
ฉันเขียนรหัสของคุณอีกครั้งเพื่อทดสอบทั้งสองกรณี 100 ครั้ง ในที่สุดเวลาเฉลี่ยของรอบชิงชนะเลิศคือ 0 ms และ 9 ms สำหรับ non final การเพิ่มจำนวนการวนซ้ำเป็น 10M ตั้งค่าเฉลี่ยเป็น 0 ms และ 75 ms วิธีที่ดีที่สุดสำหรับผู้ที่ไม่ใช่คนสุดท้ายคือ 0 ms บางทีมันอาจเป็นผลการตรวจจับของ VM ที่ถูกทิ้งไปหรือเปล่า? ฉันไม่รู้ แต่ไม่ว่าการใช้งานขั้นสุดท้ายจะทำให้เกิดความแตกต่างอย่างมีนัยสำคัญ
Casper Færgemand

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

16
ไม่มีการทดสอบที่ไม่สมบูรณ์ข้อควรพิจารณาถึงการวอร์มอัพ การทดสอบที่สองคือช้ากว่าไม่เร็วกว่า หากไม่มีการอุ่นเครื่องการทดสอบครั้งที่สองก็จะเป็นแบบช้า
rustyx

6
ใน testFinal () ทุกครั้งที่ส่งคืนวัตถุเดียวกันจากกลุ่มสตริเพราะ resust ของสตริงสุดท้ายและการเรียงตัวอักษรสตริงสตริงสุดท้ายจะถูกประเมินในเวลารวบรวม testNonFinal () ทุกครั้งที่ส่งคืนวัตถุใหม่นั่นจะอธิบายความแตกต่างของความเร็ว
anber

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

62

ใช่มันสามารถ นี่คือตัวอย่างที่ขั้นตอนสุดท้ายสามารถเพิ่มประสิทธิภาพได้:

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

พิจารณาสิ่งต่อไปนี้:

public class ConditionalCompile {

  private final static boolean doSomething= false;

    if (doSomething) {
       // do first part. 
    }

    if (doSomething) {
     // do second part. 
    }

    if (doSomething) {     
      // do third part. 
    }

    if (doSomething) {
    // do finalization part. 
    }
}

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

public class ConditionalCompile {

  private final static boolean doSomething= false;

    if (false){
       // do first part. 
    }

    if (false){
     // do second part. 
    }

    if (false){
      // do third part. 
    }

    if (false){
    // do finalization part. 

    }
}

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

public class ConditionalCompile {


  private final static boolean doSomething= false;

  public static void someMethodBetter( ) {

    // do first part. 

    // do second part. 

    // do third part. 

    // do finalization part. 

  }
}

จึงลดรหัสที่มากเกินไปหรือการตรวจสอบตามเงื่อนไขที่ไม่จำเป็น

แก้ไข: เป็นตัวอย่างมาลองโค้ดต่อไปนี้:

public class Test {
    public static final void main(String[] args) {
        boolean x = false;
        if (x) {
            System.out.println("x");
        }
        final boolean y = false;
        if (y) {
            System.out.println("y");
        }
        if (false) {
            System.out.println("z");
        }
    }
}

เมื่อรวบรวมรหัสนี้ด้วย Java 8 และ decompiling กับjavap -c Test.classเราได้รับ:

public class Test {
  public Test();
    Code:
       0: aload_0
       1: invokespecial #8                  // Method java/lang/Object."<init>":()V
       4: return

  public static final void main(java.lang.String[]);
    Code:
       0: iconst_0
       1: istore_1
       2: iload_1
       3: ifeq          14
       6: getstatic     #16                 // Field java/lang/System.out:Ljava/io/PrintStream;
       9: ldc           #22                 // String x
      11: invokevirtual #24                 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
      14: iconst_0
      15: istore_2
      16: return
}

xเราสามารถทราบว่ารหัสเรียบเรียงมีเพียงตัวแปรไม่ใช่ครั้งสุดท้าย สิ่งนี้นำเสนอตัวแปรสุดท้ายที่มีผลกระทบต่อการแสดงอย่างน้อยก็สำหรับกรณีง่าย ๆ นี้


1
@ ŁukaszLechฉันได้เรียนรู้สิ่งนี้จากหนังสือ Oreilly: Hardcore Java ในบทที่เกี่ยวกับคำหลักสุดท้าย
mel3kings

15
การพูดคุยเกี่ยวกับการปรับให้เหมาะสมในเวลารวบรวมหมายถึงนักพัฒนารู้ค่า VALUE ของตัวแปรบูลีนสุดท้าย ณ เวลารวบรวมสิ่งที่เป็นจุดรวมของการเขียนถ้าบล็อกในสถานที่แรกแล้วสำหรับสถานการณ์นี้ที่ IF- เงื่อนไขไม่จำเป็น ความรู้สึกใด ๆ ในความคิดของฉันแม้ว่านี่จะช่วยเพิ่มประสิทธิภาพ แต่นี่เป็นรหัสที่ไม่ถูกต้องในตอนแรกและสามารถปรับให้เหมาะสมโดยนักพัฒนาเองแทนที่จะส่งผ่านความรับผิดชอบต่อคอมไพเลอร์และคำถามส่วนใหญ่ตั้งใจถามเกี่ยวกับการปรับปรุงประสิทธิภาพในรหัสปกติ ทำให้รู้สึกโปรแกรม
Bhavesh Agarwal

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

37

ตาม IBM - มันไม่ได้สำหรับชั้นเรียนหรือวิธีการ

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


1
... และตาม IBM ก็มีไว้สำหรับฟิลด์ : ibm.com/developerworks/java/library/j-jtp1029/… - และยังได้รับการเลื่อนขั้นเป็นแนวทางปฏิบัติที่ดีที่สุด
Philzen

2
บทความ "04223" มาจากปี 2003 ปัจจุบันอายุสิบเจ็ดปีแล้ว นั่นคือ ... Java 1.4?
dmatej

16

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

สำหรับการอ้างอิงนี้ได้รับการทดสอบกับjavacรุ่น8, 9และ10และ

สมมติว่าวิธีนี้:

public static int test() {
    /* final */ Object left = new Object();
    Object right = new Object();

    return left.hashCode() + right.hashCode();
}

การคอมไพล์รหัสนี้ตามที่เป็นอยู่จะสร้างรหัสไบต์ที่แน่นอนเหมือนกับที่finalเคยมีอยู่ (final Object left = new Object(); )

แต่อันนี้:

public static int test() {
    /* final */ int left = 11;
    int right = 12;
    return left + right;
}

ผลิต:

   0: bipush        11
   2: istore_0
   3: bipush        12
   5: istore_1
   6: iload_0
   7: iload_1
   8: iadd
   9: ireturn

ปล่อยfinalให้เป็นปัจจุบันผลิต:

   0: bipush        12
   2: istore_1
   3: bipush        11
   5: iload_1
   6: iadd
   7: ireturn

รหัสค่อนข้างอธิบายตนเองในกรณีที่มีการรวบรวมเวลาคงที่มันจะถูกโหลดโดยตรงไปยังตัวถูกดำเนินการสแต็ก (จะไม่ถูกเก็บไว้ในอาร์เรย์ตัวแปรท้องถิ่นเช่นตัวอย่างก่อนหน้านี้ผ่าน bipush 12; istore_0; iload_0 ) - ซึ่งการเรียงลำดับของความรู้สึกที่ทำให้ เนื่องจากไม่มีใครสามารถเปลี่ยนได้

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

ฉันรู้สึกประหลาดใจที่เห็นการเพิ่มประสิทธิภาพดังกล่าวพิจารณาว่าคนตัวเล็กjavacทำอย่างไร ในฐานะที่เราควรจะใช้เสมอfinal? ฉันจะไม่เขียนบทJMHทดสอบ (ซึ่งฉันต้องการในตอนแรก) ฉันมั่นใจว่าความแตกต่างอยู่ในลำดับns(ถ้าเป็นไปได้ที่จะถูกจับเลย) ที่เดียวที่อาจเป็นปัญหาคือเมื่อเมธอดไม่สามารถถูกแทรกได้เนื่องจากขนาดของมัน (และการประกาศfinalจะทำให้ขนาดนั้นเล็กลงเป็นสองสามไบต์)

มีอีกสองfinalที่ต้องแก้ไข ครั้งแรกคือเมื่อวิธีการคือfinal(จากJITมุมมอง) วิธีดังกล่าวคือmonomorphic - และเหล่านี้เป็นคนที่รักมากที่สุดโดยJVMคนโดย

จากนั้นมีfinalตัวแปรอินสแตนซ์ (ที่ต้องตั้งค่าในตัวสร้างทุกตัว); เหล่านี้เป็นสิ่งสำคัญที่พวกเขาจะรับประกันการอ้างอิงการตีพิมพ์อย่างถูกต้องแตะต้องบิตที่นี่JLSและยังระบุว่าโดย


ฉันรวบรวมรหัสโดยใช้ Java 8 (JDK 1.8.0_162) พร้อมตัวเลือกการแก้ปัญหา ( javac -g FinalTest.java), ถอดรหัสรหัสโดยใช้javap -c FinalTest.classและไม่ได้ผลลัพธ์เดียวกัน (ด้วยfinal int left=12ฉันได้รับbipush 11; istore_0; bipush 12; istore_1; bipush 11; iload_1; iadd; ireturn) ใช่แล้วบิทโคดที่สร้างขึ้นนั้นขึ้นอยู่กับหลาย ๆ ปัจจัยและเป็นการยากที่จะบอกว่าการมีfinalหรือไม่มีผลต่อประสิทธิภาพ แต่เนื่องจากรหัสไบต์มีความแตกต่างกันจึงอาจมีความแตกต่างด้านประสิทธิภาพ
Julien Kronegg


13

คุณกำลังถามเกี่ยวกับกรณีที่แตกต่างกันสองกรณี (อย่างน้อย) จริง:

  1. final สำหรับตัวแปรท้องถิ่น
  2. final สำหรับวิธีการ / ชั้นเรียน

Jon Skeet ได้ตอบแล้ว 2) ประมาณ 1):

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

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

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

แก้ไข:

ที่จริงตามคำตอบของ Timo Westkämper final สามารถปรับปรุงประสิทธิภาพสำหรับฟิลด์คลาสในบางกรณี ฉันยืนแก้ไขแล้ว


ฉันไม่คิดว่าคอมไพเลอร์สามารถตรวจสอบจำนวนครั้งที่ตัวแปรโลคัลถูกกำหนดได้อย่างถูกต้องแล้ว if-then-else structs ที่มีการกำหนดจำนวนมากเป็นอย่างไร
gyorgyabraham

1
@gyabraham: คอมไพเลอร์ตรวจสอบกรณีเหล่านี้แล้วถ้าคุณประกาศตัวแปรโลคอลเป็นfinalเพื่อให้แน่ใจว่าคุณไม่ได้กำหนดมันสองครั้ง เท่าที่ผมสามารถมองเห็นตรรกะเดียวกันการตรวจสอบสามารถ (และน่าจะเป็น) finalที่ใช้สำหรับการตรวจสอบไม่ว่าจะเป็นตัวแปรที่อาจจะ
sleske

ความแปรปรวนขั้นสุดท้ายของตัวแปรท้องถิ่นไม่ได้แสดงไว้ใน bytecode ดังนั้น JVM จึงไม่ทราบด้วยซ้ำว่าเป็นสิ่งสุดท้าย
Steve Kuo

1
@SteveKuo: แม้ว่ามันจะไม่ได้แสดงในโหมดไบต์ก็อาจช่วยjavacเพิ่มประสิทธิภาพที่ดีขึ้น แต่นี่เป็นเพียงการเก็งกำไร
sleske

1
คอมไพเลอร์สามารถค้นหาว่าตัวแปรโลคัลได้รับการกำหนดเพียงครั้งเดียว แต่ในทางปฏิบัติมันไม่ได้ (นอกเหนือจากการตรวจสอบข้อผิดพลาด) ในทางกลับกันหากfinalตัวแปรเป็นประเภทดั้งเดิมหรือประเภทStringและได้รับมอบหมายทันทีกับค่าคงที่รวบรวมเวลาเช่นในตัวอย่างคำถามคำถามรวบรวมจะต้องอินไลน์มันเป็นตัวแปรที่เป็นค่าคงที่เวลารวบรวมต่อข้อกำหนด แต่สำหรับกรณีการใช้งานส่วนใหญ่รหัสอาจมีลักษณะที่แตกต่างกัน แต่ก็ยังไม่ได้สร้างความแตกต่างไม่ว่าจะเป็นค่าคงที่ inline หรืออ่านจากตัวแปรท้องถิ่นประสิทธิภาพฉลาด
Holger

6

หมายเหตุ: ไม่ใช่ผู้เชี่ยวชาญเกี่ยวกับจาวา

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


1

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


1

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

public class ShaderInput {

    private /* final */ float[] input;
    private /* final */ int[] strides;


    public ShaderInput()
    {
        this.input = new float[10];
        this.strides = new int[] { 0, 4, 8 };
    }


    public ShaderInput x(int stride, float val)
    {
        input[strides[stride] + 0] = val;
        return this;
    }

    // more stuff ...

และนี่คือวิธีที่ฉันใช้ในการทดสอบประสิทธิภาพของทางเลือกต่าง ๆ ซึ่งเป็นคลาส ShaderInput:

public static void test4()
{
    int arraySize = 10;
    float[] fb = new float[arraySize];
    for (int i = 0; i < arraySize; i++) {
        fb[i] = random.nextFloat();
    }
    int times = 1000000000;
    for (int i = 0; i < 10; ++i) {
        floatVectorTest(times, fb);
        arrayCopyTest(times, fb);
        shaderInputTest(times, fb);
        directFloatArrayTest(times, fb);
        System.out.println();
        System.gc();
    }
}

หลังจากการทำซ้ำครั้งที่ 3 ด้วยการอุ่นเครื่อง VM ฉันได้รับตัวเลขเหล่านี้อย่างต่อเนื่องโดยไม่ต้องใช้คำสำคัญสุดท้าย:

Simple array copy took   : 02.64
System.arrayCopy took    : 03.20
ShaderInput took         : 00.77
Unsafe float array took  : 05.47

ด้วยคำหลักสุดท้าย:

Simple array copy took   : 02.66
System.arrayCopy took    : 03.20
ShaderInput took         : 02.59
Unsafe float array took  : 06.24

บันทึกตัวเลขสำหรับการทดสอบ ShaderInput

มันไม่สำคัญหรอกว่าฉันจะทำไร่เป็นแบบสาธารณะหรือส่วนตัว

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

นอกจากนี้ System.arrayCopy จริง ๆ แล้วค่อนข้างช้าสำหรับอาร์เรย์ขนาดเล็กกว่าเพียงแค่การคัดลอกองค์ประกอบจากอาร์เรย์หนึ่งไปยังอีกในห่วงสำหรับ และการใช้ sun.misc.Unsafe (เช่นเดียวกับ java.nio.FloatBuffer โดยตรงซึ่งไม่ได้แสดงไว้ที่นี่) จะดำเนินการ abysmally


1
คุณลืมที่จะทำให้พารามิเตอร์สุดท้าย <pre> <code> public ShaderInput x (ก้าวย่างสุดท้ายของ int, val float สุดท้าย) {อินพุต [strides [stride] + 0] = val; ส่งคืนสิ่งนี้ } </code> </pre> จากประสบการณ์ของฉันการทำตัวแปรหรือฟิลด์สุดท้ายอาจเพิ่มประสิทธิภาพได้แน่นอน
Anticro

1
โอ้และทำให้คนอื่นจบด้วยเช่นกัน: <pre> <code> อาร์เรย์ int สุดท้ายขนาด = 10; Final float [] fb = โฟลตใหม่ [arraySize]; สำหรับ (int i = 0; i <arraySize; i ++) {fb [i] = random.nextFloat (); } int ครั้งสุดท้าย = 1000000000 สำหรับ (int i = 0; i <10; ++ i) {floatVectorTest (เท่า, fb); arrayCopyTest (เท่า, fb); shaderInputTest (เท่า, fb); directFloatArrayTest (ครั้ง, fb); System.out.println (); System.gc (); } </code> </pre>
Anticro

1

Final (อย่างน้อยที่สุดสำหรับตัวแปรและพารามิเตอร์ของสมาชิก) นั้นมากกว่าสำหรับมนุษย์ดังนั้นมันจึงเป็นเครื่องจักร

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

อีกกรณีหนึ่ง - ฉันได้แปลงรหัสจำนวนมากเพื่อใช้คำอธิบายประกอบ @ NonNull / @ Nullable (คุณสามารถพูดได้ว่าพารามิเตอร์ method ต้องไม่เป็นโมฆะดังนั้น IDE สามารถเตือนคุณทุกที่ที่คุณผ่านตัวแปรที่ไม่ได้ติดแท็ก @ NonNull - ทุกสิ่งกระจายไปในระดับไร้สาระ) มันง่ายกว่ามากในการพิสูจน์ว่าตัวแปรหรือพารามิเตอร์ของสมาชิกไม่สามารถเป็นโมฆะเมื่อติดแท็กครั้งสุดท้ายเนื่องจากคุณรู้ว่ามันไม่ได้ถูกกำหนดใหม่อีกเลย

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

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


0

ดังที่กล่าวไว้ในที่อื่น ๆ 'ขั้นสุดท้าย' สำหรับตัวแปรท้องถิ่นและในระดับที่น้อยกว่าเล็กน้อยสำหรับตัวแปรสมาชิกนั้นเป็นเรื่องของสไตล์มากกว่า

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

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

(ฉันใช้สุดท้ายกับตัวแปรสมาชิก)


-3

สมาชิกที่ประกาศให้ทราบครั้งสุดท้ายจะมีให้ตลอดโปรแกรมเพราะไม่เหมือนกับสมาชิกที่ไม่ใช่สมาชิกสุดท้ายหากสมาชิกเหล่านี้ไม่ได้ใช้ในโปรแกรมพวกเขาจะยังไม่ได้รับการดูแลโดย Garbage Collector ดังนั้นอาจทำให้เกิดปัญหาประสิทธิภาพเนื่องจากการจัดการหน่วยความจำไม่ดี


2
เรื่องไร้สาระแบบนี้คืออะไร? คุณมีแหล่งที่มาว่าทำไมคุณคิดว่าเป็นกรณีนี้หรือไม่?
J. Doe

-4

final คำหลักสามารถใช้ในห้าวิธีใน Java

  1. คลาสถือเป็นที่สิ้นสุด
  2. ตัวแปรอ้างอิงถือเป็นที่สิ้นสุด
  3. ตัวแปรท้องถิ่นถือเป็นที่สุด
  4. วิธีการถือเป็นที่สิ้นสุด

คลาสเป็นขั้นสุดท้าย: คลาสเป็นขั้นตอนสุดท้ายหมายความว่าเราไม่สามารถขยายหรือสืบทอดได้หมายความว่าไม่สามารถรับมรดกได้

ในทำนองเดียวกัน - วัตถุเป็นที่สิ้นสุด: บางครั้งเราไม่ได้แก้ไขสถานะภายในของวัตถุดังนั้นในกรณีเช่นนี้เราสามารถระบุวัตถุที่เป็นวัตถุสุดท้ายวัตถุ Object สุดท้ายหมายถึงไม่ตัวแปรยังสุดท้าย

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


2
โดย "object is final" คุณหมายถึง "object is immutable"
gyorgyabraham

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