ความแตกต่างอย่างมากในความเร็วของวิธีการคงที่และไม่คงที่เท่ากัน


86

ในรหัสนี้เมื่อฉันสร้าง Object ในmainเมธอดแล้วเรียกว่า object method: ff.twentyDivCount(i)(ทำงานใน 16010 ms) มันทำงานได้เร็วกว่าการเรียกโดยใช้คำอธิบายประกอบนี้: twentyDivCount(i)(ทำงานใน 59516 ms) แน่นอนเมื่อฉันเรียกใช้โดยไม่สร้างวัตถุฉันทำให้เมธอดคงที่ดังนั้นจึงสามารถเรียกใช้ใน main ได้

public class ProblemFive {

    // Counts the number of numbers that the entry is evenly divisible by, as max is 20
    int twentyDivCount(int a) {    // Change to static int.... when using it directly
        int count = 0;
        for (int i = 1; i<21; i++) {

            if (a % i == 0) {
                count++;
            }
        }
        return count;
    }

    public static void main(String[] args) {
        long startT = System.currentTimeMillis();;
        int start = 500000000;
        int result = start;

        ProblemFive ff = new ProblemFive();

        for (int i = start; i > 0; i--) {

            int temp = ff.twentyDivCount(i); // Faster way
                       // twentyDivCount(i) - slower

            if (temp == 20) {
                result = i;
                System.out.println(result);
            }
        }

        System.out.println(result);

        long end = System.currentTimeMillis();;
        System.out.println((end - startT) + " ms");
    }
}

แก้ไข: จนถึงตอนนี้ดูเหมือนว่าเครื่องจักรที่แตกต่างกันจะให้ผลลัพธ์ที่แตกต่างกัน แต่การใช้ JRE 1.8.


4
คุณใช้เกณฑ์มาตรฐานของคุณอย่างไร ฉันพนันได้เลยว่านี่เป็นสิ่งประดิษฐ์ของ JVM ที่ไม่มีเวลาเพียงพอในการปรับแต่งโค้ดให้เหมาะสม
Patrick Collins

2
ดูเหมือนว่าจะถึงเวลาเพียงพอแล้วที่ JVM จะรวบรวมและดำเนินการ OSR สำหรับวิธีการหลักตามที่+PrintCompilation +PrintInliningแสดงไว้
Tagir Valeev

1
ฉันได้ลองใช้ข้อมูลโค้ดแล้ว แต่ฉันไม่ได้รับความแตกต่างของเวลาตามที่ Stabbz กล่าว 56282ms (ใช้อินสแตนซ์) 54551ms (เป็นวิธีการแบบคงที่)
Don Chakkappan

1
@PatrickCollins ห้าวินาทีต้องพอเพียง ฉันเขียนใหม่เล็กน้อยเพื่อให้คุณสามารถวัดทั้งสองอย่างได้ (หนึ่ง JVM เริ่มต้นต่อตัวแปร) ฉันรู้ว่าในฐานะที่เป็นเกณฑ์มาตรฐานก็ยังมีข้อบกพร่อง แต่ก็น่าเชื่อเพียงพอ: 1457 ms STATIC vs 5312 ms NON_STATIC
maaartinus

1
ยังไม่ได้ตรวจสอบคำถามโดยละเอียด แต่อาจเกี่ยวข้อง: shipilev.net/blog/2015/black-magic-method-dispatch (บางที Aleksey Shipilëvสามารถให้ความกระจ่างแก่เราได้ที่นี่)
Marco13

คำตอบ:


72

การใช้ JRE 1.8.0_45 ฉันได้ผลลัพธ์ที่คล้ายกัน

ตรวจสอบ:

  1. การรัน java ด้วย-XX:+UnlockDiagnosticVMOptions -XX:+PrintCompilation -XX:+PrintInliningอ็อพชัน VM แสดงให้เห็นว่าทั้งสองวิธีได้รับการคอมไพล์และอินไลน์
  2. การดูแอสเซมบลีที่สร้างขึ้นสำหรับวิธีการต่างๆนั้นไม่ได้แสดงความแตกต่างอย่างมีนัยสำคัญ
  3. อย่างไรก็ตามเมื่ออินไลน์แล้วแอสเซมบลีที่สร้างขึ้นภายในmainจะแตกต่างกันมากโดยวิธีการอินสแตนซ์ได้รับการปรับให้เหมาะสมมากขึ้นโดยเฉพาะอย่างยิ่งในแง่ของการคลายการวนซ้ำ

จากนั้นฉันทำการทดสอบของคุณอีกครั้ง แต่มีการตั้งค่าการคลายการวนซ้ำที่แตกต่างกันเพื่อยืนยันข้อสงสัยข้างต้น ฉันรันโค้ดของคุณด้วย:

  • -XX:LoopUnrollLimit=0 และทั้งสองวิธีทำงานช้า (คล้ายกับวิธีการแบบคงที่ที่มีตัวเลือกเริ่มต้น)
  • -XX:LoopUnrollLimit=100 และทั้งสองวิธีทำงานได้อย่างรวดเร็ว (คล้ายกับวิธีการอินสแตนซ์ที่มีตัวเลือกเริ่มต้น)

จากข้อสรุปดูเหมือนว่าด้วยการตั้งค่าเริ่มต้น JIT ของฮอตสปอต 1.8.0_45จะไม่สามารถคลายการวนซ้ำได้เมื่อเมธอดนั้นคงที่ (แม้ว่าฉันจะไม่แน่ใจว่าทำไมมันถึงทำงานแบบนั้น) JVM อื่น ๆ อาจให้ผลลัพธ์ที่แตกต่างกัน


ระหว่าง 52 ถึง 71 พฤติกรรมดั้งเดิมจะถูกเรียกคืน (อย่างน้อยก็ในเครื่องของฉันคำตอบของฉัน) ดูเหมือนว่าเวอร์ชันคงที่มีขนาดใหญ่กว่า 20 หน่วย แต่ทำไม? มันแปลก ๆ.
maaartinus

3
@maaartinus ฉันไม่แน่ใจด้วยซ้ำว่าตัวเลขนั้นแสดงถึงอะไร - เอกสารค่อนข้างหลีกเลี่ยง: " Unroll loop body with server compiler intermediate representation node นับน้อยกว่าค่านี้ขีด จำกัด ที่คอมไพเลอร์เซิร์ฟเวอร์ใช้คือฟังก์ชันของค่านี้ ไม่ใช่ค่าจริงค่าเริ่มต้นจะแตกต่างกันไปตามแพลตฟอร์มที่ JVM กำลังทำงานอยู่ "...
assylias

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

33

เป็นเพียงการคาดเดาที่ยังไม่ผ่านการพิสูจน์โดยอาศัยคำตอบของ assylias

JVM ใช้เกณฑ์สำหรับการคลายการวนซ้ำซึ่งก็คือ 70 ไม่ว่าด้วยเหตุผลใดก็ตามการเรียกแบบคงที่จะใหญ่กว่าเล็กน้อยและไม่ได้รับการยกเลิก

อัปเดตผลลัพธ์

  • ด้วยLoopUnrollLimit52 ด้านล่างทั้งสองเวอร์ชันจะช้า
  • ระหว่าง 52 ถึง 71 เฉพาะเวอร์ชันคงที่เท่านั้นที่ช้า
  • สูงกว่า 71 ทั้งสองเวอร์ชันเร็ว

นี่เป็นเรื่องแปลกเพราะฉันเดาว่าการโทรแบบคงที่นั้นใหญ่กว่าเล็กน้อยในการเป็นตัวแทนภายในและ OP ก็เป็นกรณีที่แปลก แต่ความแตกต่างน่าจะประมาณ 20 ซึ่งไม่สมเหตุสมผล

 

-XX:LoopUnrollLimit=51
5400 ms NON_STATIC
5310 ms STATIC
-XX:LoopUnrollLimit=52
1456 ms NON_STATIC
5305 ms STATIC
-XX:LoopUnrollLimit=71
1459 ms NON_STATIC
5309 ms STATIC
-XX:LoopUnrollLimit=72
1457 ms NON_STATIC
1488 ms STATIC

สำหรับผู้ที่ต้องการทดลองเวอร์ชันของฉันอาจมีประโยชน์


เป็นเวลา '1456 ms' หรือไม่ถ้าเป็นเช่นนั้นทำไมคุณถึงบอกว่าคงที่ช้า
Tony

@ โทนี่ฉันสับสนNON_STATICและSTATICแต่ข้อสรุปของฉันถูกต้อง แก้ไขแล้วขอบคุณ
maaartinus

0

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

ทำไมถึงทำเช่นนั้น? มันยากที่จะพูด อาจจะทำในสิ่งที่ถูกต้องถ้านี่เป็นแอปพลิเคชั่นขนาดใหญ่ ...


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

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

@DraganBozanovic: จะหยุด "ไม่จำเป็นในการดีบักอย่างเต็มที่" เมื่อทำให้เกิดปัญหาจริงในโค้ดจริง
tmyklebu

0

ฉันเพิ่งปรับแต่งการทดสอบเล็กน้อยและได้ผลลัพธ์ดังต่อไปนี้:

เอาท์พุต:

Dynamic Test:
465585120
232792560
232792560
51350 ms
Static Test:
465585120
232792560
232792560
52062 ms

บันทึก

ในขณะที่ฉันทดสอบแยกกันฉันใช้เวลาประมาณ 52 วินาทีสำหรับไดนามิกและ ~ 200 วินาทีสำหรับแบบคงที่

นี่คือโปรแกรม:

public class ProblemFive {

    // Counts the number of numbers that the entry is evenly divisible by, as max is 20
    int twentyDivCount(int a) {  // Change to static int.... when using it directly
        int count = 0;
        for (int i = 1; i<21; i++) {

            if (a % i == 0) {
                count++;
            }
        }
        return count;
    }

    static int twentyDivCount2(int a) {
         int count = 0;
         for (int i = 1; i<21; i++) {

             if (a % i == 0) {
                 count++;
             }
         }
         return count;
    }

    public static void main(String[] args) {
        System.out.println("Dynamic Test: " );
        dynamicTest();
        System.out.println("Static Test: " );
        staticTest();
    }

    private static void staticTest() {
        long startT = System.currentTimeMillis();;
        int start = 500000000;
        int result = start;

        for (int i = start; i > 0; i--) {

            int temp = twentyDivCount2(i);

            if (temp == 20) {
                result = i;
                System.out.println(result);
            }
        }

        System.out.println(result);

        long end = System.currentTimeMillis();;
        System.out.println((end - startT) + " ms");
    }

    private static void dynamicTest() {
        long startT = System.currentTimeMillis();;
        int start = 500000000;
        int result = start;

        ProblemFive ff = new ProblemFive();

        for (int i = start; i > 0; i--) {

            int temp = ff.twentyDivCount(i); // Faster way

            if (temp == 20) {
                result = i;
                System.out.println(result);
            }
        }

        System.out.println(result);

        long end = System.currentTimeMillis();;
        System.out.println((end - startT) + " ms");
    }
}

ฉันยังเปลี่ยนลำดับของการทดสอบเป็น:

public static void main(String[] args) {
    System.out.println("Static Test: " );
    staticTest();
    System.out.println("Dynamic Test: " );
    dynamicTest();
}

และฉันได้รับสิ่งนี้:

Static Test:
465585120
232792560
232792560
188945 ms
Dynamic Test:
465585120
232792560
232792560
50106 ms

อย่างที่คุณเห็นถ้าไดนามิกถูกเรียกก่อนแบบคงที่ความเร็วคงที่จะลดลงอย่างมาก

ตามเกณฑ์มาตรฐานนี้:

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

หลักการง่ายๆ:

Java: เมื่อใดควรใช้วิธีการแบบคงที่


"คุณต้องปฏิบัติตามกฎง่ายๆในการใช้วิธีการคงที่และไดนามิก" กฎง่ายๆนี้คืออะไร? และคุณอ้างจากใคร / อะไร
weston

@weston ขอโทษฉันไม่ได้เพิ่มลิงค์ที่ฉันคิด :) thx
nafas

0

โปรดลอง:

public class ProblemFive {
    public static ProblemFive PROBLEM_FIVE = new ProblemFive();

    public static void main(String[] args) {
        long startT = System.currentTimeMillis();
        int start = 500000000;
        int result = start;


        for (int i = start; i > 0; i--) {
            int temp = PROBLEM_FIVE.twentyDivCount(i); // faster way
            // twentyDivCount(i) - slower

            if (temp == 20) {
                result = i;
                System.out.println(result);
                System.out.println((System.currentTimeMillis() - startT) + " ms");
            }
        }

        System.out.println(result);

        long end = System.currentTimeMillis();
        System.out.println((end - startT) + " ms");
    }

    int twentyDivCount(int a) {  // change to static int.... when using it directly
        int count = 0;
        for (int i = 1; i < 21; i++) {

            if (a % i == 0) {
                count++;
            }
        }
        return count;
    }
}

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