ทำลายการเพิ่มประสิทธิภาพของ JIT พร้อมการสะท้อนกลับ


9

เมื่อเล่นซอกับการทดสอบหน่วยสำหรับคลาสซิงเกิลตันที่เกิดขึ้นพร้อมกันสูงฉันสะดุดกับพฤติกรรมแปลก ๆ ต่อไปนี้ (ทดสอบบน JDK 1.8.0_162):

private static class SingletonClass {
    static final SingletonClass INSTANCE = new SingletonClass(0);
    final int value;

    static SingletonClass getInstance() {
        return INSTANCE;
    }

    SingletonClass(int value) {
        this.value = value;
    }
}

public static void main(String[] args) throws NoSuchFieldException, IllegalAccessException {

    System.out.println(SingletonClass.getInstance().value); // 0

    // Change the instance to a new one with value 1
    setSingletonInstance(new SingletonClass(1));
    System.out.println(SingletonClass.getInstance().value); // 1

    // Call getInstance() enough times to trigger JIT optimizations
    for(int i=0;i<100_000;++i){
        SingletonClass.getInstance();
    }

    System.out.println(SingletonClass.getInstance().value); // 1

    setSingletonInstance(new SingletonClass(2));
    System.out.println(SingletonClass.INSTANCE.value); // 2
    System.out.println(SingletonClass.getInstance().value); // 1 (2 expected)
}

private static void setSingletonInstance(SingletonClass newInstance) throws NoSuchFieldException, IllegalAccessException {
    // Get the INSTANCE field and make it accessible
    Field field = SingletonClass.class.getDeclaredField("INSTANCE");
    field.setAccessible(true);

    // Remove the final modifier
    Field modifiersField = Field.class.getDeclaredField("modifiers");
    modifiersField.setAccessible(true);
    modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL);

    // Set new value
    field.set(null, newInstance);
}

2 บรรทัดสุดท้ายของเมธอด main () ไม่เห็นด้วยกับค่า INSTANCE - ฉันเดาว่า JIT กำจัดวิธีการอย่างสมบูรณ์เนื่องจากฟิลด์เป็นแบบสแตติกสุดท้าย การลบคำหลักสุดท้ายทำให้รหัสถูกต้องค่าผลลัพธ์

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


1
Singleton เป็นคลาสที่มีอินสแตนซ์เดียวเท่านั้นที่สามารถมีอยู่ได้ ดังนั้นคุณไม่มีซิงเกิลคุณแค่มีคลาสที่มีstatic finalฟิลด์ นอกจากนั้นมันไม่สำคัญว่าแฮ็คการสะท้อนนี้จะแตกเนื่องจาก JIT หรือการทำงานพร้อมกัน
Holger

@ Holger การแฮ็คนี้เกิดขึ้นในการทดสอบหน่วยเป็นเพียงการลองจำลองเดี่ยวสำหรับการทดสอบหลายกรณีของชั้นเรียนที่ใช้มัน ฉันไม่เห็นว่าการเกิดขึ้นพร้อมกันนั้นเกิดขึ้นได้อย่างไร (ไม่มีในรหัสด้านบน) และฉันอยากจะรู้ว่าเกิดอะไรขึ้น
Kelm

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

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

คำตอบ:


7

จดคำถามของคุณอย่างแท้จริง“ …การสันนิษฐานของฉันถูกต้องในการเพิ่มประสิทธิภาพ JIT นั้นจะโทษหรือไม่ ” คำตอบคือใช่มีโอกาสมากที่การเพิ่มประสิทธิภาพ JIT จะรับผิดชอบต่อพฤติกรรมนี้ในตัวอย่างเฉพาะนี้

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

แม้ว่า JMM และเครื่องมือเพิ่มประสิทธิภาพจะแยกกันได้ยากที่นี่

คำถามของคุณ“ …ถูก จำกัด เฉพาะเขตข้อมูลสุดท้ายเท่านั้นหรือไม่ ” เป็นการยากกว่าที่จะตอบเพราะแน่นอนว่าการเพิ่มประสิทธิภาพไม่ได้ จำกัด อยู่แค่ในstatic finalสาขา แต่พฤติกรรมของเช่นไม่คงที่finalฟิลด์ที่นั้นไม่เหมือนกันและมีความแตกต่างระหว่างทฤษฎีและการปฏิบัติเช่นกัน

สำหรับfinalฟิลด์ที่ไม่คงที่อนุญาตให้ทำการแก้ไขผ่าน Reflection ในบางสถานการณ์ นี่เป็นการบ่งชี้โดยข้อเท็จจริงที่setAccessible(true)เพียงพอที่จะทำการแก้ไขดังกล่าวโดยไม่แฮ็กเข้าไปในFieldอินสแตนซ์เพื่อเปลี่ยนเขตmodifiersข้อมูลภายใน

สเปคบอกว่า:

17.5.3 การปรับเปลี่ยนfinalเขตข้อมูลในภายหลัง

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

...

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

ตัวอย่างที่ 17.5.3-1 การเพิ่มประสิทธิภาพเชิงรุกของfinalฟิลด์
class A {
    final int x;
    A() { 
        x = 1; 
    } 

    int f() { 
        return d(this,this); 
    } 

    int d(A a1, A a2) { 
        int i = a1.x; 
        g(a1); 
        int j = a2.x; 
        return j - i; 
    }

    static void g(A a) { 
        // uses reflection to change a.x to 2 
    } 
}

ในdวิธีการคอมไพเลอร์ได้รับอนุญาตให้เรียงลำดับการอ่านxและการเรียกเพื่อgอิสระ ดังนั้นnew A().f()สามารถกลับ-1, หรือ01

ในทางปฏิบัติการกำหนดสถานที่ที่เหมาะสมที่เพิ่มประสิทธิภาพในเชิงรุกที่เป็นไปได้โดยไม่ทำลายสถานการณ์ทางกฎหมายที่อธิบายข้างต้นจะเปิดประเด็นดังนั้นถ้า-XX:+TrustFinalNonStaticFieldsได้รับการระบุ HotSpot JVM จะไม่เพิ่มประสิทธิภาพไม่คงที่finalเขตเช่นเดียวกับstatic finalสาขา

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


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