ลองพิจารณาตัวอย่างของโค้ดสองชุดต่อไปนี้ในอาร์เรย์ที่มีความยาว 2:
boolean isOK(int i) {
for (int j = 0; j < filters.length; ++j) {
if (!filters[j].isOK(i)) {
return false;
}
}
return true;
}
และ
boolean isOK(int i) {
return filters[0].isOK(i) && filters[1].isOK(i);
}
ฉันคิดว่าประสิทธิภาพของสองชิ้นนี้ควรจะคล้ายกันหลังจากการวอร์มอัพเพียงพอ
ฉันได้ตรวจสอบสิ่งนี้โดยใช้กรอบการเปรียบเทียบไมโคร JMH ตามที่อธิบายไว้เช่นที่นี่และที่นี่และสังเกตว่าตัวอย่างที่สองนั้นเร็วกว่า 10%
คำถาม: ทำไม Java ถึงไม่ปรับข้อมูลโค้ดแรกของฉันให้ดีขึ้นโดยใช้เทคนิคการปลดลูปพื้นฐาน
โดยเฉพาะฉันต้องการทำความเข้าใจสิ่งต่อไปนี้:
- ฉันสามารถผลิตรหัสที่เป็นที่เหมาะสมสำหรับกรณีของ 2 ตัวกรองและยังคงสามารถทำงานในกรณีของหมายเลขอื่นของตัวกรอง (จินตนาการสร้างง่าย)
return (filters.length) == 2 ? new FilterChain2(filters) : new FilterChain1(filters)
A: JITC สามารถทำสิ่งเดียวกันได้หรือไม่ถ้าไม่ทำไม - JITC สามารถตรวจจับได้หรือไม่ว่า ' filters.length == 2 ' เป็นกรณีที่พบบ่อยที่สุดและสร้างรหัสที่เหมาะสมที่สุดสำหรับกรณีนี้หลังจากทำการอุ่นเครื่องหรือไม่ นี่น่าจะเหมาะสมเกือบเท่ากับเวอร์ชันที่ไม่ได้ควบคุมด้วยตนเอง
- JITC สามารถตรวจพบว่ามีการใช้งานอินสแตนซ์ที่เฉพาะเจาะจงบ่อยครั้งมากแล้วสร้างรหัสสำหรับอินสแตนซ์ที่เฉพาะเจาะจงนี้ (ซึ่งทราบว่าจำนวนตัวกรองเป็น 2 เสมอ)?
อัปเดต:รับคำตอบที่ JITC ใช้งานได้เฉพาะในระดับชั้นเรียนเท่านั้น ตกลงเข้าใจแล้ว
เป็นการดีที่ฉันต้องการได้รับคำตอบจากคนที่มีความเข้าใจอย่างลึกซึ้งว่า JITC ทำงานอย่างไร
รายละเอียดการเรียกใช้เกณฑ์มาตรฐาน:
- ทดลองใช้ Java 8 OpenJDK และ Oracle HotSpot เวอร์ชันล่าสุดผลลัพธ์จะคล้ายกัน
- ธง Java ที่ใช้: -Xmx4g -Xms4g -server -Xbatch -XX: CICompilerCount = 2 (รับผลลัพธ์ที่คล้ายกันโดยไม่มีแฟล็กแฟนซีเช่นกัน)
- โดยวิธีการที่ฉันได้รับอัตราส่วนเวลาที่คล้ายกันถ้าฉันเพียงแค่เรียกใช้มันหลายพันล้านครั้งในวง (ไม่ผ่าน JMH) นั่นคือตัวอย่างที่สองจะเร็วขึ้นอย่างชัดเจนเสมอ
เอาต์พุตมาตรฐานทั่วไป:
เกณฑ์มาตรฐาน (filterIndex) โหมด Cnt คะแนนข้อผิดพลาดหน่วย
LoopUnrollingBenchmark.runBenchmark 0 avgt 400 44.202 ± 0.224 ns / op
LoopUnrollingBenchmark.runBenchmark 1 avgt 400 38.347 ± 0.063 ns / op
(บรรทัดแรกสอดคล้องกับตัวอย่างแรกบรรทัดที่สอง - ถึงที่สอง
รหัสมาตรฐานที่สมบูรณ์:
public class LoopUnrollingBenchmark {
@State(Scope.Benchmark)
public static class BenchmarkData {
public Filter[] filters;
@Param({"0", "1"})
public int filterIndex;
public int num;
@Setup(Level.Invocation) //similar ratio with Level.TRIAL
public void setUp() {
filters = new Filter[]{new FilterChain1(), new FilterChain2()};
num = new Random().nextInt();
}
}
@Benchmark
@Fork(warmups = 5, value = 20)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public int runBenchmark(BenchmarkData data) {
Filter filter = data.filters[data.filterIndex];
int sum = 0;
int num = data.num;
if (filter.isOK(num)) {
++sum;
}
if (filter.isOK(num + 1)) {
++sum;
}
if (filter.isOK(num - 1)) {
++sum;
}
if (filter.isOK(num * 2)) {
++sum;
}
if (filter.isOK(num * 3)) {
++sum;
}
if (filter.isOK(num * 5)) {
++sum;
}
return sum;
}
interface Filter {
boolean isOK(int i);
}
static class Filter1 implements Filter {
@Override
public boolean isOK(int i) {
return i % 3 == 1;
}
}
static class Filter2 implements Filter {
@Override
public boolean isOK(int i) {
return i % 7 == 3;
}
}
static class FilterChain1 implements Filter {
final Filter[] filters = createLeafFilters();
@Override
public boolean isOK(int i) {
for (int j = 0; j < filters.length; ++j) {
if (!filters[j].isOK(i)) {
return false;
}
}
return true;
}
}
static class FilterChain2 implements Filter {
final Filter[] filters = createLeafFilters();
@Override
public boolean isOK(int i) {
return filters[0].isOK(i) && filters[1].isOK(i);
}
}
private static Filter[] createLeafFilters() {
Filter[] filters = new Filter[2];
filters[0] = new Filter1();
filters[1] = new Filter2();
return filters;
}
public static void main(String[] args) throws Exception {
org.openjdk.jmh.Main.main(args);
}
}
@Setup(Level.Invocation)
: ไม่แน่ใจว่ามันช่วยได้ (ดู javadoc)
final
แต่ JIT ไม่เห็นว่าอินสแตนซ์ทั้งหมดของคลาสจะได้รับอาร์เรย์ที่มีความยาว 2 เพื่อดูว่าจะต้องดำดิ่งลงในcreateLeafFilters()
วิธีการและวิเคราะห์รหัสลึกพอที่จะเรียนรู้ว่าอาเรย์จะยาว 2 เสมอ ทำไมคุณถึงเชื่อว่าเครื่องมือเพิ่มประสิทธิภาพของ JIT จะดำลงไปในโค้ดของคุณ