เหตุใดจึงช้ากว่า int ใน x64 Java นาน


92

ฉันใช้ Windows 8.1 x64 พร้อมอัปเดต Java 7 45 x64 (ไม่ได้ติดตั้ง Java 32 บิต) บนแท็บเล็ต Surface Pro 2

โค้ดด้านล่างใช้เวลา 1688ms เมื่อประเภทของ i ยาวและ 109ms เมื่อฉันเป็น int เหตุใดลำดับความยาว (ประเภท 64 บิต) จึงช้ากว่า int บนแพลตฟอร์ม 64 บิตที่มี JVM 64 บิต

การคาดเดาเพียงอย่างเดียวของฉันคือ CPU ใช้เวลาในการเพิ่มจำนวนเต็ม 64 บิตนานกว่า 32 บิตหนึ่ง แต่ดูเหมือนจะไม่น่าเป็นไปได้ ฉันสงสัยว่า Haswell ไม่ได้ใช้ตัวเติมแบบกระเพื่อม

ฉันกำลังเรียกใช้สิ่งนี้ใน Eclipse Kepler SR1, btw

public class Main {

    private static long i = Integer.MAX_VALUE;

    public static void main(String[] args) {    
        System.out.println("Starting the loop");
        long startTime = System.currentTimeMillis();
        while(!decrementAndCheck()){
        }
        long endTime = System.currentTimeMillis();
        System.out.println("Finished the loop in " + (endTime - startTime) + "ms");
    }

    private static boolean decrementAndCheck() {
        return --i < 0;
    }

}

แก้ไข: นี่คือผลลัพธ์จากโค้ด C ++ ที่เทียบเท่าซึ่งรวบรวมโดย VS 2013 (ด้านล่าง) ระบบเดียวกัน ยาว: 72265ms int: 74656ms ผลลัพธ์เหล่านั้นอยู่ในโหมดดีบัก 32 บิต

ในโหมดปล่อย 64 บิต: ยาว: 875ms ยาวยาว: 906ms int: 1047ms

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

#include "stdafx.h"
#include "iostream"
#include "windows.h"
#include "limits.h"

long long i = INT_MAX;

using namespace std;


boolean decrementAndCheck() {
return --i < 0;
}


int _tmain(int argc, _TCHAR* argv[])
{


cout << "Starting the loop" << endl;

unsigned long startTime = GetTickCount64();
while (!decrementAndCheck()){
}
unsigned long endTime = GetTickCount64();

cout << "Finished the loop in " << (endTime - startTime) << "ms" << endl;



}

แก้ไข: เพิ่งลองอีกครั้งใน Java 8 RTM ไม่มีการเปลี่ยนแปลงที่สำคัญ


8
ผู้ต้องสงสัยที่เป็นไปได้มากที่สุดคือการตั้งค่าของคุณไม่ใช่ CPU หรือส่วนต่างๆของ JVM คุณสามารถทำซ้ำการวัดนี้ได้อย่างน่าเชื่อถือหรือไม่? ไม่ทำวนซ้ำไม่อุ่น JIT ใช้currentTimeMillis()รันโค้ดที่สามารถปรับให้เหมาะสมได้อย่างสมบูรณ์และอื่น ๆ ทำให้เกิดผลลัพธ์ที่ไม่น่าเชื่อถือ

1
ฉันกำลังเปรียบเทียบเมื่อไม่นานมานี้ฉันต้องใช้ a longเป็นตัวนับลูปเนื่องจากคอมไพเลอร์ JIT ได้ปรับลูปเอาต์ให้เหมาะสมเมื่อฉันใช้intไฟล์. หนึ่งจะต้องดูที่การถอดรหัสเครื่องที่สร้างขึ้น
แซม

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

7
ความคิดเห็นทั้งหมดที่กล่าวโทษ OP เนื่องจากความล้มเหลวในการเขียนไมโครเบนช์มาร์ก Java ที่เหมาะสมนั้นขี้เกียจอย่างไม่อาจบรรยายได้ นี่คือสิ่งที่ง่ายมากที่จะคิดออกว่าคุณเพียงแค่ดูและดูว่า JVM ทำอะไรกับโค้ด
tmyklebu

2
@maaartinus: การปฏิบัติที่ได้รับการยอมรับนั้นเป็นที่ยอมรับเพราะเป็นการปฏิบัติตามรายการของข้อผิดพลาดที่ทราบ ในกรณีของ Java Benchmarks ที่เหมาะสมคุณต้องตรวจสอบให้แน่ใจว่าคุณกำลังวัดโค้ดที่ปรับให้เหมาะสมอย่างเหมาะสมไม่ใช่การแทนที่บนสแต็กและคุณต้องการให้แน่ใจว่าการวัดของคุณสะอาดเมื่อสิ้นสุด OP พบปัญหาที่แตกต่างไปจากเดิมอย่างสิ้นเชิงและเกณฑ์มาตรฐานที่เขาให้ไว้แสดงให้เห็นอย่างเพียงพอ และตามที่ระบุไว้การเปลี่ยนรหัสนี้เป็น Java Benchmark ที่เหมาะสมไม่ได้ทำให้ความแปลกประหลาดหายไป และการอ่านรหัสประกอบไม่ใช่เรื่องยาก
tmyklebu

คำตอบ:


82

JVM ของฉันทำสิ่งนี้ตรงไปตรงมากับวงในเมื่อคุณใช้longs:

0x00007fdd859dbb80: test   %eax,0x5f7847a(%rip)  /* fun JVM hack */
0x00007fdd859dbb86: dec    %r11                  /* i-- */
0x00007fdd859dbb89: mov    %r11,0x258(%r10)      /* store i to memory */
0x00007fdd859dbb90: test   %r11,%r11             /* unnecessary test */
0x00007fdd859dbb93: jge    0x00007fdd859dbb80    /* go back to the loop top */

มันโกงยากเมื่อคุณใช้ints; ก่อนอื่นมีความสับสนบางอย่างที่ฉันไม่อ้างว่าเข้าใจ แต่ดูเหมือนการตั้งค่าสำหรับลูปที่ไม่มีการควบคุม:

0x00007f3dc290b5a1: mov    %r11d,%r9d
0x00007f3dc290b5a4: dec    %r9d
0x00007f3dc290b5a7: mov    %r9d,0x258(%r10)
0x00007f3dc290b5ae: test   %r9d,%r9d
0x00007f3dc290b5b1: jl     0x00007f3dc290b662
0x00007f3dc290b5b7: add    $0xfffffffffffffffe,%r11d
0x00007f3dc290b5bb: mov    %r9d,%ecx
0x00007f3dc290b5be: dec    %ecx              
0x00007f3dc290b5c0: mov    %ecx,0x258(%r10)   
0x00007f3dc290b5c7: cmp    %r11d,%ecx
0x00007f3dc290b5ca: jle    0x00007f3dc290b5d1
0x00007f3dc290b5cc: mov    %ecx,%r9d
0x00007f3dc290b5cf: jmp    0x00007f3dc290b5bb
0x00007f3dc290b5d1: and    $0xfffffffffffffffe,%r9d
0x00007f3dc290b5d5: mov    %r9d,%r8d
0x00007f3dc290b5d8: neg    %r8d
0x00007f3dc290b5db: sar    $0x1f,%r8d
0x00007f3dc290b5df: shr    $0x1f,%r8d
0x00007f3dc290b5e3: sub    %r9d,%r8d
0x00007f3dc290b5e6: sar    %r8d
0x00007f3dc290b5e9: neg    %r8d
0x00007f3dc290b5ec: and    $0xfffffffffffffffe,%r8d
0x00007f3dc290b5f0: shl    %r8d
0x00007f3dc290b5f3: mov    %r8d,%r11d
0x00007f3dc290b5f6: neg    %r11d
0x00007f3dc290b5f9: sar    $0x1f,%r11d
0x00007f3dc290b5fd: shr    $0x1e,%r11d
0x00007f3dc290b601: sub    %r8d,%r11d
0x00007f3dc290b604: sar    $0x2,%r11d
0x00007f3dc290b608: neg    %r11d
0x00007f3dc290b60b: and    $0xfffffffffffffffe,%r11d
0x00007f3dc290b60f: shl    $0x2,%r11d
0x00007f3dc290b613: mov    %r11d,%r9d
0x00007f3dc290b616: neg    %r9d
0x00007f3dc290b619: sar    $0x1f,%r9d
0x00007f3dc290b61d: shr    $0x1d,%r9d
0x00007f3dc290b621: sub    %r11d,%r9d
0x00007f3dc290b624: sar    $0x3,%r9d
0x00007f3dc290b628: neg    %r9d
0x00007f3dc290b62b: and    $0xfffffffffffffffe,%r9d
0x00007f3dc290b62f: shl    $0x3,%r9d
0x00007f3dc290b633: mov    %ecx,%r11d
0x00007f3dc290b636: sub    %r9d,%r11d
0x00007f3dc290b639: cmp    %r11d,%ecx
0x00007f3dc290b63c: jle    0x00007f3dc290b64f
0x00007f3dc290b63e: xchg   %ax,%ax /* OK, fine; I know what a nop looks like */

จากนั้นลูปที่ไม่ได้รับการควบคุมเอง:

0x00007f3dc290b640: add    $0xfffffffffffffff0,%ecx
0x00007f3dc290b643: mov    %ecx,0x258(%r10)
0x00007f3dc290b64a: cmp    %r11d,%ecx
0x00007f3dc290b64d: jg     0x00007f3dc290b640

จากนั้นรหัสการฉีกขาดสำหรับลูปที่ไม่มีการควบคุมตัวเองเป็นการทดสอบและลูปตรง:

0x00007f3dc290b64f: cmp    $0xffffffffffffffff,%ecx
0x00007f3dc290b652: jle    0x00007f3dc290b662
0x00007f3dc290b654: dec    %ecx
0x00007f3dc290b656: mov    %ecx,0x258(%r10)
0x00007f3dc290b65d: cmp    $0xffffffffffffffff,%ecx
0x00007f3dc290b660: jg     0x00007f3dc290b654

ดังนั้นมันจึงเร็วขึ้น 16 เท่าสำหรับ ints เนื่องจาก JIT คลายการintวนซ้ำ 16 ครั้ง แต่ไม่ได้คลายการlongวนซ้ำเลย

เพื่อความสมบูรณ์นี่คือรหัสที่ฉันลองใช้จริง:

public class foo136 {
  private static int i = Integer.MAX_VALUE;
  public static void main(String[] args) {
    System.out.println("Starting the loop");
    for (int foo = 0; foo < 100; foo++)
      doit();
  }

  static void doit() {
    i = Integer.MAX_VALUE;
    long startTime = System.currentTimeMillis();
    while(!decrementAndCheck()){
    }
    long endTime = System.currentTimeMillis();
    System.out.println("Finished the loop in " + (endTime - startTime) + "ms");
  }

  private static boolean decrementAndCheck() {
    return --i < 0;
  }
}

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


9
ตกลงดังนั้น net-net ไม่ใช่ว่าlongเวอร์ชันช้ากว่า แต่เป็นintเวอร์ชันที่เร็วกว่า ที่สมเหตุสมผล ดูเหมือนว่าจะไม่ได้ใช้ความพยายามมากนักในการทำให้ JIT ปรับlongนิพจน์ให้เหมาะสมที่สุด
Hot Licks

1
... ยกโทษให้ฉันไม่รู้ แต่ "funrolled" คืออะไร? ฉันไม่สามารถแม้แต่จะใช้ Google ได้อย่างถูกต้องและนั่นทำให้นี่เป็นครั้งแรกที่ฉันต้องถามใครสักคนว่าคำศัพท์บนอินเทอร์เน็ตมีความหมายอย่างไร
BrianH

1
@BrianDHall gccใช้-fเป็นสวิตช์บรรทัดคำสั่งสำหรับ "ธง" และการเพิ่มประสิทธิภาพเปิดอยู่โดยกล่าวว่าunroll-loops -funroll-loopsฉันแค่ใช้ "unroll" เพื่ออธิบายการเพิ่มประสิทธิภาพ
chrylis -cautiouslyoptimistic-

4
@BRPocock: คอมไพเลอร์ Java ไม่สามารถทำได้ แต่ JIT สามารถทำได้
tmyklebu

1
เพื่อความชัดเจนมันไม่ได้ "funroll" มันคลายการควบคุมและแปลงลูปที่ไม่มีการควบคุมi-=16ซึ่งแน่นอนว่าเร็วกว่า 16 เท่า
Aleksandr Dubinsky

22

สแต็ก JVM ถูกกำหนดเป็นคำซึ่งมีขนาดเป็นรายละเอียดการนำไปใช้งาน แต่ต้องกว้างอย่างน้อย 32 บิต ตัวดำเนินการ JVM อาจใช้คำ 64 บิต แต่ bytecode ไม่สามารถพึ่งพาสิ่งนี้ได้ดังนั้นการดำเนินการกับlongหรือdoubleค่าจึงต้องได้รับการจัดการด้วยความระมัดระวังเป็นพิเศษ โดยเฉพาะอย่างยิ่งJVM ที่คำแนะนำสาขาจำนวนเต็มintกำหนดไว้ในตรงประเภท

ในกรณีของรหัสของคุณการถอดชิ้นส่วนเป็นคำแนะนำ นี่คือ bytecode สำหรับintเวอร์ชันที่รวบรวมโดย Oracle JDK 7:

private static boolean decrementAndCheck();
  Code:
     0: getstatic     #14  // Field i:I
     3: iconst_1      
     4: isub          
     5: dup           
     6: putstatic     #14  // Field i:I
     9: ifge          16
    12: iconst_1      
    13: goto          17
    16: iconst_0      
    17: ireturn       

โปรดทราบว่า JVM จะโหลดค่าคงที่i(0) ของคุณลบหนึ่ง (3-4) ทำซ้ำค่าบนสแต็ก (5) และดันกลับเข้าไปในตัวแปร (6) จากนั้นทำการเปรียบเทียบกับศูนย์และส่งกลับ

เวอร์ชันที่มีlongนั้นซับซ้อนกว่าเล็กน้อย:

private static boolean decrementAndCheck();
  Code:
     0: getstatic     #14  // Field i:J
     3: lconst_1      
     4: lsub          
     5: dup2          
     6: putstatic     #14  // Field i:J
     9: lconst_0      
    10: lcmp          
    11: ifge          18
    14: iconst_1      
    15: goto          19
    18: iconst_0      
    19: ireturn       

ขั้นแรกเมื่อ JVM ซ้ำค่าใหม่บนสแต็ก (5) จะต้องทำซ้ำสองคำซ้อนกัน ในกรณีของคุณเป็นไปได้ค่อนข้างมากที่จะไม่แพงไปกว่าการทำซ้ำเนื่องจาก JVM มีอิสระที่จะใช้คำ 64 บิตหากสะดวก อย่างไรก็ตามคุณจะสังเกตเห็นว่าตรรกะของสาขานั้นยาวกว่าที่นี่ JVM ที่ไม่ได้มีการเรียนการสอนที่จะเปรียบเทียบlongด้วยศูนย์ดังนั้นจะมีการผลักดันอย่างต่อเนื่อง0Lบนสแต็ค (9) ทำทั่วไปlongเปรียบเทียบ (10) และจากนั้นสาขากับค่าของที่คำนวณ

นี่คือสองสถานการณ์ที่เป็นไปได้:

  • JVM เป็นไปตามเส้นทาง bytecode ทุกประการ ในกรณีนี้มันทำงานได้มากขึ้นในlongเวอร์ชันผลักดันและเพิ่มค่าพิเศษหลายค่าและสิ่งเหล่านี้อยู่ในสแต็กที่มีการจัดการเสมือนไม่ใช่สแต็ก CPU ที่ใช้ฮาร์ดแวร์ช่วยจริง ในกรณีนี้คุณจะยังคงเห็นความแตกต่างของประสิทธิภาพอย่างมีนัยสำคัญหลังจากการวอร์ม
  • JVM ตระหนักดีว่าสามารถเพิ่มประสิทธิภาพโค้ดนี้ได้ ในกรณีนี้ต้องใช้เวลาเพิ่มเพื่อเพิ่มประสิทธิภาพตรรกะการผลัก / เปรียบเทียบที่ไม่จำเป็นในทางปฏิบัติ หากเป็นกรณีนี้คุณจะเห็นความแตกต่างของประสิทธิภาพน้อยมากหลังจากวอร์มอัพ

ฉันแนะนำให้คุณเขียน microbenchmark ที่ถูกต้องเพื่อกำจัดผลกระทบของการเริ่มต้น JIT และลองใช้เงื่อนไขสุดท้ายที่ไม่ใช่ศูนย์เพื่อบังคับให้ JVM ทำการเปรียบเทียบแบบเดียวกันกับintที่ทำกับไฟล์long.


1
@ Katona ไม่จำเป็น โดยเฉพาะอย่างยิ่ง Client และ Server HotSpot JVM เป็นการใช้งานที่แตกต่างกันโดยสิ้นเชิงและ Ilya ไม่ได้ระบุว่าเลือกเซิร์ฟเวอร์ (โดยปกติไคลเอ็นต์จะเป็นค่าเริ่มต้น 32 บิต)
ริลิส

1
@tmyklebu ปัญหาคือเกณฑ์มาตรฐานกำลังวัดหลายสิ่งพร้อมกัน การใช้เงื่อนไขเทอร์มินัลที่ไม่ใช่ศูนย์จะช่วยลดจำนวนตัวแปร
chrylis -cautiouslyoptimistic-

1
@tmyklebu ประเด็นคือ OP มีจุดประสงค์เพื่อเปรียบเทียบความเร็วของการเพิ่มขึ้นการลดลงและการเปรียบเทียบระหว่าง ints กับ longs แต่ (สมมติว่าคำตอบนี้ถูกต้อง) พวกเขาวัดเฉพาะการเปรียบเทียบและเทียบกับ 0 เท่านั้นซึ่งเป็นกรณีพิเศษ หากไม่มีสิ่งอื่นใดจะทำให้เกณฑ์มาตรฐานดั้งเดิมทำให้เข้าใจผิด - ดูเหมือนว่าจะใช้มาตรการสามกรณีทั่วไปเมื่อในความเป็นจริงมาตรการหนึ่งกรณีเฉพาะ
yshavit

1
@tmyklebu อย่าเข้าใจฉันผิดฉันโหวตคำถามคำตอบนี้และคำตอบของคุณ แต่ฉันไม่เห็นด้วยกับคำพูดของคุณที่ว่า @chrylis กำลังปรับเกณฑ์มาตรฐานเพื่อหยุดวัดความแตกต่างที่พยายามวัด OP สามารถแก้ไขฉันได้หากฉันทำผิด แต่ดูเหมือนว่าพวกเขากำลังพยายามวัดเพียงอย่างเดียว / เป็นหลัก== 0ซึ่งดูเหมือนจะเป็นส่วนใหญ่ที่ไม่ได้สัดส่วนของผลลัพธ์มาตรฐาน สำหรับฉันแล้วดูเหมือนว่า OP จะพยายามวัดช่วงการดำเนินการที่กว้างขึ้นและคำตอบนี้ชี้ให้เห็นว่าเกณฑ์มาตรฐานนั้นเอียงไปทางหนึ่งในการดำเนินการเหล่านั้นอย่างมาก
yshavit

2
@tmyklebu ไม่เลย. ฉันทุกคนเข้าใจถึงสาเหตุที่แท้จริง แต่เมื่อระบุว่าสาเหตุหลักที่สำคัญประการหนึ่งก็คือเกณฑ์มาตรฐานนั้นเบ้การเปลี่ยนเกณฑ์มาตรฐานเพื่อลบความเบ้นั้นไม่ถูกต้องรวมถึงการขุดคุ้ยและทำความเข้าใจเพิ่มเติมเกี่ยวกับการเอียงนั้น (เช่นสามารถทำให้มีประสิทธิภาพมากขึ้น bytecode เพื่อให้สามารถคลายลูปได้ง่ายขึ้น ฯลฯ ) นั่นเป็นเหตุผลที่ฉันให้คะแนนทั้งคำตอบนี้ (ซึ่งระบุความเบ้) และของคุณ (ซึ่งเจาะลึกลงไปในรายละเอียดเพิ่มเติม)
yshavit

8

หน่วยพื้นฐานของข้อมูลใน Java Virtual Machine คือ word การเลือกขนาดคำที่เหมาะสมจะเหลืออยู่ในการนำ JVM ไปใช้ การติดตั้ง JVM ควรเลือกขนาดคำขั้นต่ำ 32 บิต สามารถเลือกขนาดคำที่สูงขึ้นเพื่อเพิ่มประสิทธิภาพ ไม่มีข้อ จำกัด ใด ๆ ที่ JVM 64 บิตควรเลือกคำ 64 บิตเท่านั้น

สถาปัตยกรรมต้นแบบไม่ได้กำหนดว่าขนาดของคำควรจะเท่ากัน JVM อ่าน / เขียนข้อมูลทีละคำ นี่คือสาเหตุที่อาจใช้เวลานานกว่าสำหรับไฟล์ยาวกว่าint

ที่นี่คุณสามารถหาข้อมูลเพิ่มเติมในหัวข้อเดียวกัน


4

ฉันเพิ่งเขียนเกณฑ์มาตรฐานโดยใช้คาลิปเปอร์คาลิเปอร์

ผลค่อนข้างสอดคล้องกับรหัสเดิมที่: ~ 12 เท่าเพิ่มความเร็วในการใช้งานมากกว่าint longดูเหมือนว่าการวนซ้ำที่ยกเลิกการรายงานโดย tmyklebuหรือสิ่งที่คล้ายกันมากกำลังเกิดขึ้น

timeIntDecrements         195,266,845.000
timeLongDecrements      2,321,447,978.000

นี่คือรหัสของฉัน โปรดทราบว่ามันใช้สแนปชอตที่สร้างขึ้นcaliperใหม่เนื่องจากฉันไม่สามารถหาวิธีเขียนโค้ดเทียบกับรุ่นเบต้าที่มีอยู่ได้

package test;

import com.google.caliper.Benchmark;
import com.google.caliper.Param;

public final class App {

    @Param({""+1}) int number;

    private static class IntTest {
        public static int v;
        public static void reset() {
            v = Integer.MAX_VALUE;
        }
        public static boolean decrementAndCheck() {
            return --v < 0;
        }
    }

    private static class LongTest {
        public static long v;
        public static void reset() {
            v = Integer.MAX_VALUE;
        }
        public static boolean decrementAndCheck() {
            return --v < 0;
        }
    }

    @Benchmark
    int timeLongDecrements(int reps) {
        int k=0;
        for (int i=0; i<reps; i++) {
            LongTest.reset();
            while (!LongTest.decrementAndCheck()) { k++; }
        }
        return (int)LongTest.v | k;
    }    

    @Benchmark
    int timeIntDecrements(int reps) {
        int k=0;
        for (int i=0; i<reps; i++) {
            IntTest.reset();
            while (!IntTest.decrementAndCheck()) { k++; }
        }
        return IntTest.v | k;
    }
}

1

สำหรับบันทึกเวอร์ชันนี้ทำการ "วอร์มอัพ" แบบคร่าวๆ:

public class LongSpeed {

    private static long i = Integer.MAX_VALUE;
    private static int j = Integer.MAX_VALUE;

    public static void main(String[] args) {

        for (int x = 0; x < 10; x++) {
            runLong();
            runWord();
        }
    }

    private static void runLong() {
        System.out.println("Starting the long loop");
        i = Integer.MAX_VALUE;
        long startTime = System.currentTimeMillis();
        while(!decrementAndCheckI()){

        }
        long endTime = System.currentTimeMillis();

        System.out.println("Finished the long loop in " + (endTime - startTime) + "ms");
    }

    private static void runWord() {
        System.out.println("Starting the word loop");
        j = Integer.MAX_VALUE;
        long startTime = System.currentTimeMillis();
        while(!decrementAndCheckJ()){

        }
        long endTime = System.currentTimeMillis();

        System.out.println("Finished the word loop in " + (endTime - startTime) + "ms");
    }

    private static boolean decrementAndCheckI() {
        return --i < 0;
    }

    private static boolean decrementAndCheckJ() {
        return --j < 0;
    }

}

เวลาโดยรวมดีขึ้นประมาณ 30% แต่อัตราส่วนระหว่างทั้งสองยังคงเท่าเดิม


@TedHopp - ฉันลองเปลี่ยนขีด จำกัด วงในของฉันแล้วและมันก็ยังคงไม่เปลี่ยนแปลง
Hot Licks

@ Techrocket9: ฉันได้รับหมายเลขที่คล้ายกัน ( intเร็วกว่า 20 เท่า) ด้วยรหัสนี้
tmyklebu

1

สำหรับบันทึก:

ถ้าฉันใช้

boolean decrementAndCheckLong() {
    lo = lo - 1l;
    return lo < -1l;
}

(เปลี่ยน "l--" เป็น "l = l - 1l") ประสิทธิภาพที่ยาวนานดีขึ้น ~ 50%


1

อาจเกิดจากการตรวจสอบ JVM เพื่อหาจุดปลอดภัยเมื่อใช้งานเป็นเวลานาน (ลูปที่ไม่ได้นับ) และไม่ได้ทำเพื่อ int (นับลูป)

ข้อมูลอ้างอิงบางส่วน: https://stackoverflow.com/a/62557768/14624235

https://stackoverflow.com/a/58726530/14624235

http://psy-lob-saw.blogspot.com/2016/02/wait-for-it-counteduncounted-loops.html


0

ฉันไม่มีเครื่อง 64 บิตให้ทดสอบ แต่ความแตกต่างที่ค่อนข้างมากแสดงให้เห็นว่ามีรหัสไบต์ที่ยาวกว่าเล็กน้อยในที่ทำงาน

ฉันเห็นเวลาที่ใกล้กันมากสำหรับ long / int (4400 vs 4800ms) บน 32 บิต 1.7.0_45 ของฉัน

นี่เป็นเพียงการคาดเดาแต่ฉันสงสัยอย่างยิ่งว่านั่นเป็นผลของโทษปรับความจำไม่ตรงแนว เพื่อยืนยัน / ปฏิเสธข้อสงสัยให้ลองเพิ่ม public static int dummy = 0; ก่อนการประกาศของ i. ซึ่งจะผลักฉันลง 4 ไบต์ในเลย์เอาต์หน่วยความจำและอาจทำให้จัดตำแหน่งอย่างเหมาะสมเพื่อประสิทธิภาพที่ดีขึ้น ยืนยันไม่ได้เป็นสาเหตุของปัญหา

แก้ไข: เหตุผลเบื้องหลังนี้คือ VM อาจไม่จัดลำดับฟิลด์ใหม่ในยามว่างโดยเพิ่มช่องว่างภายในเพื่อการจัดตำแหน่งที่เหมาะสมที่สุดเนื่องจากอาจรบกวน JNI (ไม่ใช่กรณี).


VM ที่แน่นอนจะได้รับอนุญาตให้สาขาการสั่งซื้อและเพิ่มช่องว่างภายใน
Hot Licks

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