लंबाई 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);
}
मुझे लगता है कि इन दो टुकड़ों का प्रदर्शन पर्याप्त गर्मजोशी के बाद समान होना चाहिए।
मैंने जेएमएच माइक्रो-बेंचमार्किंग फ्रेमवर्क का उपयोग करके यह जाँच की है जैसे कि यहाँ और यहाँ वर्णित है और देखा कि दूसरा स्निपेट 10% से अधिक तेज़ है।
प्रश्न: जावा ने बुनियादी लूप अन्रॉलिंग तकनीक का उपयोग करके मेरे पहले स्निपेट को क्यों नहीं अनुकूलित किया है?
विशेष रूप से, मैं निम्नलिखित समझना चाहूंगा:
- मैं आसानी से एक कोड का उत्पादन कर सकता हूं जो 2 फिल्टर के मामलों के लिए इष्टतम है और अभी भी फिल्टर की एक और संख्या के मामले में काम कर सकता है (एक साधारण बिल्डर की कल्पना करें:)
return (filters.length) == 2 ? new FilterChain2(filters) : new FilterChain1(filters)
। क्या JITC भी ऐसा कर सकता है और यदि नहीं, तो क्यों? - क्या JITC यह पता लगा सकता है कि ' filter.length == 2 ' सबसे लगातार मामला है और कोड का उत्पादन करता है जो कुछ वार्म-अप के बाद इस मामले के लिए इष्टतम है? यह मैन्युअल रूप से अनियंत्रित संस्करण के रूप में लगभग इष्टतम होना चाहिए।
- क्या JITC यह पता लगा सकता है कि किसी विशेष उदाहरण का उपयोग बहुत बार किया जाता है और फिर इस विशिष्ट उदाहरण के लिए एक कोड का उत्पादन किया जाता है (जिसके लिए यह जानता है कि फ़िल्टर की संख्या हमेशा 2 है)?
अपडेट: एक जवाब मिला कि जेआईटीसी केवल एक वर्ग स्तर पर काम करता है। ठीक मिल गया।
आदर्श रूप से, मैं जेआईटीसी के काम करने की गहरी समझ के साथ किसी से उत्तर प्राप्त करना चाहूंगा।
बेंचमार्क रन विवरण:
- जावा 8 ओपनजेडके और ओरेकल हॉटस्पॉट के नवीनतम संस्करणों की कोशिश की, परिणाम समान हैं
- जावा झंडे का इस्तेमाल किया: -Xmx4g -Xms4g -server -Xbatch -XX: CICompilerCount = 2 (फैंसी झंडे के बिना भी इसी तरह के परिणाम मिले)
- वैसे, मुझे समान रन समय अनुपात मिलता है अगर मैं बस इसे कई बिलियन बार (जेएमएच के माध्यम से नहीं) चलाता हूं, तो दूसरा स्निपेट हमेशा स्पष्ट रूप से तेज होता है
विशिष्ट बेंचमार्क आउटपुट:
बेंचमार्क (filterIndex) मोड Cnt स्कोर त्रुटि इकाइयाँ
LoopUnrollingBenchmark.runBenchmark 0 avgt 400 44.202 24 0.224 ns / op
LoopUnrollingBenchmark.runBenchmark 1 avgt 400 38.347 op 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)
: निश्चित नहीं है कि यह मदद करता है (देखें जवादोक)।
final
, लेकिन जेआईटी यह नहीं देखता है कि कक्षा के सभी उदाहरणों को लंबाई की एक सरणी मिल जाएगी। 2. यह देखने के लिए, उसे गोता लगाना होगा createLeafFilters()
विधि और कोड को गहराई से जानने के लिए विश्लेषण करें कि सरणी हमेशा 2 लंबी होगी। आप क्यों मानते हैं कि जेआईटी ऑप्टिमाइज़र आपके कोड में गहरा होगा?