Uzunluk 2 dizisinde aşağıdaki iki kod parçacığını düşünün:
boolean isOK(int i) {
for (int j = 0; j < filters.length; ++j) {
if (!filters[j].isOK(i)) {
return false;
}
}
return true;
}
ve
boolean isOK(int i) {
return filters[0].isOK(i) && filters[1].isOK(i);
}
Yeterli ısınma sonrasında bu iki parçanın performansının benzer olması gerektiğini varsayabilirim.
Bunu burada ve burada tarif edildiği gibi JMH mikro-karşılaştırma çerçevesi kullanarak kontrol ettim ve ikinci snippet'in% 10'dan daha hızlı olduğunu gözlemledim.
Soru: Java neden ilk snippet'i temel döngü açma tekniğini kullanarak optimize etmedi?
Özellikle, aşağıdakileri anlamak istiyorum:
- Kolayca (basit bir oluşturucu hayal) filtrelerin başka bir numaraya durumunda çalışabilir hala 2 filtrelerin durumlar için en uygunudur ve bir kod üretebilir:
return (filters.length) == 2 ? new FilterChain2(filters) : new FilterChain1(filters)
. JITC de aynısını yapabilir ve değilse, neden? - JITC, 'filters.length == 2 ' nin en sık karşılaşılan durum olduğunu tespit edebilir ve bir miktar ısınmadan sonra bu durum için en uygun kodu üretebilir mi? Bu, manuel olarak açılmış versiyon kadar neredeyse optimal olmalıdır.
- JITC belirli bir örneğin çok sık kullanıldığını algılayabilir ve daha sonra bu belirli örnek için bir kod üretebilir (bunun için filtre sayısının her zaman 2 olduğunu bildiği)?
Güncelleme: JITC'nin sadece sınıf düzeyinde çalıştığı bir cevap aldı. Tamam anladım.
İdeal olarak, JITC'nin nasıl çalıştığını derinlemesine anlayan birinden cevap almak istiyorum.
Benchmark çalıştırma detayları:
- Java 8 OpenJDK ve Oracle HotSpot'un en son sürümlerinde denenen sonuçlar benzer
- Kullanılan Java bayrakları: -Xmx4g -Xms4g -server -Xbatch -XX: CICompilerCount = 2 (süslü bayraklar olmadan da benzer sonuçlar aldı)
- Bu arada, sadece bir döngüde birkaç milyar kez çalıştırırsam (JMH aracılığıyla değil) benzer çalışma süresi oranı elde ederim, yani ikinci snippet her zaman açıkça daha hızlıdır
Tipik kıyaslama çıktısı:
Karşılaştırma (filterIndex) Modu Cnt Puanı Hata Birimleri
DöngüKontrolBenchmark.runBenchmark 0 ortalama 400 44.202 ± 0.224 ns / op
LoopUnrollingBenchmark.runBenchmark 1 ortalama 400 38.347 ± 0.063 ns / op
(İlk satır birinci pasaja, ikinci satıra - ikinci satıra karşılık gelir.
Eksiksiz karşılaştırma kodu:
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)
: yardımcı olduğuna emin değilim (bkz. javadoc).
final
görebilir, ancak JIT , sınıfın tüm örneklerinin bir uzunluk 2 dizisi alacağını görmez. Bunu görmek için, createLeafFilters()
ve dizinin her zaman 2 uzun olacağını öğrenecek kadar derin kod analiz edin. Neden JIT optimizatörünün kodunuzun derinliklerine dalacağına inanıyorsunuz?