ผู้ประกอบการที่เหลืออยู่ใน int ทำให้ java.util.Objects.requireNonNull?


12

ฉันพยายามทำให้ได้ประสิทธิภาพมากที่สุดเท่าที่จะเป็นไปได้จากวิธีการภายในบางอย่าง

รหัส Java คือ:

List<DirectoryTaxonomyWriter> writers = Lists.newArrayList();
private final int taxos = 4;

[...]

@Override
public int getParent(final int globalOrdinal) throws IOException {
    final int bin = globalOrdinal % this.taxos;
    final int ordinalInBin = globalOrdinal / this.taxos;
    return this.writers.get(bin).getParent(ordinalInBin) * this.taxos + bin; //global parent
}

ในผู้สร้างโปรไฟล์ของฉันฉันเห็นว่ามีการใช้ CPU 1% java.util.Objects.requireNonNullแต่ฉันไม่ได้เรียกมันว่า เมื่อตรวจสอบรหัสไบต์ฉันเห็นสิ่งนี้:

 public getParent(I)I throws java/io/IOException 
   L0
    LINENUMBER 70 L0
    ILOAD 1
    ALOAD 0
    INVOKESTATIC java/util/Objects.requireNonNull (Ljava/lang/Object;)Ljava/lang/Object;
    POP
    BIPUSH 8
    IREM
    ISTORE 2

ดังนั้นคอมไพเลอร์สร้างเช็ค (ไร้ประโยชน์) นี้ ฉันทำงานกับสิ่งพื้นฐานซึ่งไม่สามารถอยู่ได้nullดังนั้นทำไมคอมไพเลอร์สร้างบรรทัดนี้ มันเป็นข้อบกพร่องหรือไม่? หรือพฤติกรรม 'ปกติ'

(ฉันอาจทำงานกับ bitmask แต่ฉันแค่อยากรู้อยากเห็น)

[อัปเดต]

  1. ผู้ประกอบการดูเหมือนจะไม่มีส่วนเกี่ยวข้องกับมัน (ดูคำตอบด้านล่าง)

  2. การใช้คอมไพเลอร์ eclipse (เวอร์ชั่น 4.10) ฉันได้รับผลลัพธ์ที่สมเหตุสมผลมากขึ้น:

    public getParent (I) ฉันโยน java / io / IOException 
       L0
        LINENUMBER 77 L0
        ILOAD 1
        ICONST_4
        IREM
        ตอนที่ 2
       L1
        LINENUMBER 78 L

นั่นคือเหตุผลที่มากกว่า


@ Lino แน่นอน แต่นั่นไม่เกี่ยวข้องกับสาย 70 ด้วยสาเหตุINVOKESTATIC
RobAu

คุณใช้คอมไพเลอร์อะไร? ปกติjavacจะไม่สร้างสิ่งนี้
apangin

คุณใช้คอมไพเลอร์อะไร? รุ่น Java, Openjdk / Oracle / etc? แก้ไข: whops, @apangin เร็วขึ้นขอโทษ
lugiorgi

1
มันรวบรวมจาก Intellij 2019.3 กับ java 11 openjdk version "11.0.6" 2020-01-14บน Ubuntu 64 บิต
RobAu

คำตอบ:


3

ทำไมจะไม่ล่ะ?

ทะลึ่ง

class C {
    private final int taxos = 4;

    public int test() {
        final int a = 7;
        final int b = this.taxos;
        return a % b;
    }
}

โทรเหมือนc.test()ที่cมีการประกาศว่าC จะต้องโยนเมื่อเป็นc nullวิธีการของคุณเทียบเท่า

    public int test() {
        return 3; // `7 % 4`
    }

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

รหัสไบต์

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

ประสิทธิภาพการทำงาน

ในผู้สร้างโปรไฟล์ของฉันฉันเห็นว่ามีการใช้ CPU 1% java.util.Objects.requireNonNull

ฉันจะตำหนิผู้สร้างโปรไฟล์ก่อน การทำโปรไฟล์ Java นั้นค่อนข้างยากและคุณไม่สามารถคาดหวังผลลัพธ์ที่สมบูรณ์แบบได้

คุณอาจจะลองทำให้วิธีคงที่ คุณก็ควรอ่านบทความนี้เกี่ยวกับการตรวจสอบ null


1
ขอบคุณ @ maaartinus สำหรับคำตอบที่ลึกซึ้งของคุณ ฉันจะอ่านบทความที่เชื่อมโยงของคุณอย่างแน่นอน
RobAu

1
“ด้วยการทดสอบการเป็นไม่คงที่การตรวจสอบจะต้องทำ” จริงๆแล้วไม่มีเหตุผลที่จะทดสอบว่าคือไม่ใช่this nullดังที่คุณพูดด้วยตนเองการโทรเช่นนั้นc.test()จะต้องล้มเหลวเมื่อcใดnullและจะต้องล้มเหลวทันทีแทนที่จะเข้าสู่วิธีการ ดังนั้นภายในtest(), thisไม่สามารถnull(มิฉะนั้นจะมีข้อผิดพลาด JVM) ดังนั้นไม่จำเป็นต้องตรวจสอบ การแก้ไขที่เกิดขึ้นจริงควรจะเปลี่ยนสนามtaxosจะstaticเป็นมีจุดใดในหน่วยความจำสำรองในทุกกรณีสำหรับค่าคงที่เวลารวบรวม จากนั้นไม่ว่าtest()จะstaticเป็นเรื่องที่ไม่เกี่ยวข้อง
Holger

2

ดูเหมือนว่าคำถามของฉันจะ 'ผิด' เพราะไม่มีอะไรเกี่ยวข้องกับตัวดำเนินการ แต่เป็นตัวของตัวเอง ยังไม่รู้ว่าทำไม ..

   public int test() {
        final int a = 7;
        final int b = this.taxos;
        return a % b;
    }

ซึ่งกลายเป็น:

  public test()I
   L0
    LINENUMBER 51 L0
    BIPUSH 7
    ISTORE 1
   L1
    LINENUMBER 52 L1
    ALOAD 0
    INVOKESTATIC java/util/Objects.requireNonNull (Ljava/lang/Object;)Ljava/lang/Object;
    POP
    ICONST_4
    ISTORE 2
   L2
    LINENUMBER 53 L2
    BIPUSH 7
    ILOAD 2
    IREM
    IRETURN

1
ผู้แปลอาจกลัวthisการอ้างอิงนั้นจริงnullหรือ สิ่งนี้จะเป็นไปได้ไหม
atalantus

1
ไม่ว่ามันจะไม่เข้าท่าเว้นแต่ว่าคอมไพเลอร์จะคอมไพล์ฟิลด์เป็นIntegerอย่างใดและนี่เป็นผลของการออโตบอท
RobAu

1
ไม่มีALOAD 0การอ้างอิงthis? ดังนั้นจึงสมเหตุสมผล (ไม่จริง) ว่าผู้แปลเพิ่ม nullcheck
Lino

1
ดังนั้นคอมไพเลอร์จึงเพิ่มการตรวจสอบค่าว่างthisหรือไม่ เยี่ยมมาก: /
RobAu

1
ฉันจะพยายามสร้างโค้ดให้น้อยที่สุดด้วยบรรทัดคำสั่งjavacเพื่อตรวจสอบในวันพรุ่งนี้ และถ้านั่นแสดงพฤติกรรมนี้ฉันคิดว่ามันอาจจะเป็น javac-bug?
RobAu

2

ประการแรกนี่เป็นตัวอย่างที่จำลองได้น้อยที่สุดของพฤติกรรมนี้:

/**
 * OS:              Windows 10 64x
 * javac version:   13.0.1
 */
public class Test {
    private final int bar = 5;

    /**
     * public int foo();
     *   Code:
     *     0: iconst_5
     *     1: ireturn
     */
    public int foo() {
        return bar;
    }

    /**
     * public int foo2();
     *   Code:
     *     0: aload_0
     *     1: invokestatic  #13     // Method java/util/Objects.requireNonNull:(Ljava/lang/Object;)Ljava/lang/Object;
     *     4: pop
     *     5: iconst_5
     *     6: ireturn
     */
    public int foo2() {
        return this.bar;
    }
}

พฤติกรรมเป็นเพราะวิธีการเรียบเรียง Java เพิ่มประสิทธิภาพคงที่รวบรวมเวลา

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

เมื่อเปลี่ยนbarเป็นค่าคงที่เวลาที่ไม่คอมไพล์ (โดยการลบfinalคำหลักหรือไม่เริ่มต้นภายในการประกาศ แต่ภายใน Constructor) คุณจะได้รับ:

/**
 * OS:              Windows 10 64x
 * javac version:   13.0.1
 */
public class Test2 {
    private int bar = 5;

    /**
     * public int foo();
     *   Code:
     *     0: aload_0
     *     1: getfield      #7
     *     4: ireturn
     */
    public int foo() {
        return bar;
    }

    /**
     * public int foo2();
     *   Code:
     *     0: aload_0
     *     1: getfield      #7
     *     4: ireturn
     */
    public int foo2() {
        return this.bar;
    }
}

ที่aload_0ผลักดันการอ้างอิงของthisบนตัวถูกดำเนินการกองเพื่อรับbarสนามของวัตถุนี้

นี่คอมไพเลอร์จะเพียงพอที่ฉลาดเพื่อแจ้งให้ทราบว่าaload_0(คนthisอ้างอิงในกรณีของฟังก์ชั่นสมาชิก) nullสามารถมีเหตุผลไม่ได้

ตอนนี้กรณีของคุณเพิ่มประสิทธิภาพคอมไพเลอร์หายไปจริงหรือ

ดูคำตอบ @maaartinus

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