Hãy xem xét hai đoạn mã sau trên một mảng có độ dài 2:
boolean isOK(int i) {
for (int j = 0; j < filters.length; ++j) {
if (!filters[j].isOK(i)) {
return false;
}
}
return true;
}
và
boolean isOK(int i) {
return filters[0].isOK(i) && filters[1].isOK(i);
}
Tôi cho rằng hiệu suất của hai tác phẩm này sẽ tương tự nhau sau khi khởi động đủ.
Tôi đã kiểm tra điều này bằng cách sử dụng khung điểm chuẩn vi mô JMH như được mô tả, ví dụ ở đây và ở đây và nhận thấy rằng đoạn mã thứ hai nhanh hơn 10%.
Câu hỏi: tại sao Java không tối ưu hóa đoạn mã đầu tiên của tôi bằng cách sử dụng kỹ thuật hủy đăng ký vòng lặp cơ bản?
Cụ thể, tôi muốn hiểu những điều sau:
- Tôi có thể dễ dàng tạo mã phù hợp cho các trường hợp 2 bộ lọc và vẫn có thể hoạt động trong trường hợp có một số bộ lọc khác (hãy tưởng tượng một trình xây dựng đơn giản) :
return (filters.length) == 2 ? new FilterChain2(filters) : new FilterChain1(filters)
. JITC có thể làm như vậy và nếu không, tại sao? - JITC có thể phát hiện ra rằng ' bộ lọc.length == 2 ' là trường hợp thường xuyên nhất và tạo ra mã tối ưu cho trường hợp này sau khi khởi động không? Điều này sẽ gần như tối ưu như phiên bản không được kiểm soát thủ công.
- JITC có thể phát hiện ra rằng một cá thể cụ thể được sử dụng rất thường xuyên và sau đó tạo mã cho trường hợp cụ thể này (mà nó biết rằng số lượng bộ lọc luôn luôn là 2)?
Cập nhật: đã có câu trả lời rằng JITC chỉ hoạt động ở cấp độ lớp. OK đã nhận nó.
Lý tưởng nhất, tôi muốn nhận được câu trả lời từ một người có hiểu biết sâu sắc về cách thức hoạt động của JITC.
Chi tiết điểm chuẩn chạy:
- Đã thử trên các phiên bản mới nhất của Java 8 OpenJDK và Oracle HotSpot, kết quả tương tự
- Các cờ Java đã sử dụng: -Xmx4g -Xms4g -server -Xbatch -XX: CICompilerCount = 2 (cũng có kết quả tương tự mà không có các cờ ưa thích)
- Nhân tiện, tôi nhận được tỷ lệ thời gian chạy tương tự nếu tôi chỉ chạy nó vài tỷ lần trong một vòng lặp (không thông qua JMH), tức là đoạn mã thứ hai luôn nhanh hơn rõ ràng
Sản lượng chuẩn điển hình:
Benchmark (filterIndex) Chế độ Cnt Điểm Lỗi Units
LoopUnrollingBenchmark.runBenchmark 0 avgt 400 44,202 ± 0,224 ns / op
LoopUnrollingBenchmark.runBenchmark 1 avgt 400 38,347 ± 0,063 ns / op
(Dòng đầu tiên tương ứng với đoạn đầu tiên, dòng thứ hai - với dòng thứ hai.
Hoàn thành mã điểm chuẩn:
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)
: không chắc chắn nó giúp (xem javadoc).
final
, nhưng JIT không thấy rằng tất cả các phiên bản của lớp sẽ có một mảng có độ dài 2. Để thấy rằng, nó sẽ phải đi sâu vào createLeafFilters()
Phương pháp và phân tích mã đủ sâu để biết rằng mảng sẽ luôn dài 2. Tại sao bạn tin rằng trình tối ưu hóa JIT sẽ đi sâu vào mã của bạn?