2015-09-05 6 views
8

मैं इस तरह एक विधि लिखने के लिए कोशिश कर रहा हूँ:यह जेएमएच बेंचमार्क मशीनों पर असंगत है - क्यों?

static boolean fitsInDouble(long x) { 
    // return true if x can be represented 
    // as a numerically-equivalent double 
} 

और मैं सबसे कुशल कार्यान्वयन खोजने की कोशिश कर रहा हूँ। मैं एक पर बस गया, लेकिन फिर एक सहकर्मी ने बेंचमार्क चलाया और विभिन्न सापेक्ष परिणाम प्राप्त किए। मेरे लिए सबसे तेज़ कार्यान्वयन उसके लिए सबसे तेज़ नहीं है।

क्या इन मानकों में कुछ गड़बड़ है?

package rnd; 

import org.openjdk.jmh.annotations.Benchmark; 
import org.openjdk.jmh.annotations.BenchmarkMode; 
import org.openjdk.jmh.annotations.Fork; 
import org.openjdk.jmh.annotations.Measurement; 
import org.openjdk.jmh.annotations.Mode; 
import org.openjdk.jmh.annotations.OutputTimeUnit; 
import org.openjdk.jmh.annotations.Scope; 
import org.openjdk.jmh.annotations.State; 
import org.openjdk.jmh.annotations.Warmup; 
import org.openjdk.jmh.infra.Blackhole; 
import org.openjdk.jmh.runner.Runner; 
import org.openjdk.jmh.runner.options.Options; 
import org.openjdk.jmh.runner.options.OptionsBuilder; 

import java.math.BigDecimal; 
import java.util.concurrent.TimeUnit; 

@State(Scope.Thread) 
@BenchmarkMode(Mode.AverageTime) 
@OutputTimeUnit(TimeUnit.NANOSECONDS) 
@Fork(1) 
@Measurement(iterations = 5) 
@Warmup(iterations = 5) 
public class Benchmarks { 

    public static void main(String[] args) throws Exception { 
    Options options = new OptionsBuilder() 
     .include(Benchmarks.class.getName()) 
     .build(); 
    new Runner(options).run(); 
    } 

    @Benchmark 
    public void bigDecimal(Blackhole bh) { 
    for (long x : NUMBERS) bh.consume(bigDecimal(x)); 
    } 

    @Benchmark 
    public void cast(Blackhole bh) { 
    for (long x : NUMBERS) bh.consume(cast(x)); 
    } 

    @Benchmark 
    public void zeros(Blackhole bh) { 
    for (long x : NUMBERS) bh.consume(zeros(x)); 
    } 

    public static boolean bigDecimal(long x) { 
    BigDecimal a = new BigDecimal(x); 
    BigDecimal b = new BigDecimal((double) x); 
    return a.compareTo(b) == 0; 
    } 

    public static boolean cast(long x) { 
    return x == (long) (double) x 
     && x != Long.MAX_VALUE; 
    } 

    public static boolean zeros(long x) { 
    long a = Math.abs(x); 
    int z = Long.numberOfLeadingZeros(a); 
    return z > 10 || Long.numberOfTrailingZeros(a) > 10 - z; 
    } 

    private static final long[] NUMBERS = { 
     0, 
     1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 
     -1, -2, -3, -4, -5, -6, -7, -8, -9, -10, 
     123, 456, 789, 
     -123, -456, -789, 
     101112, 131415, 161718, 
     -101112, -131415, -161718, 
     11L, 
     222L, 
     3333L, 
     44444L, 
     555555L, 
     6666666L, 
     77777777L, 
     888888888L, 
     9999999999L, 
     1111L, 
     22222L, 
     333333L, 
     4444444L, 
     55555555L, 
     666666666L, 
     7777777777L, 
     88888888888L, 
     999999999999L, 
     11111111, 
     222222222, 
     3333333333L, 
     44444444444L, 
     555555555555L, 
     6666666666666L, 
     77777777777777L, 
     888888888888888L, 
     9999999999999999L, 
     Long.MAX_VALUE, 
     Long.MAX_VALUE - 1, 
     Long.MIN_VALUE, 
     Long.MIN_VALUE + 1, 
     (1L << 53), 
     (1l << 53) + 1, 
     (1l << 53) + 2, 
     (1l << 60), 
     (1l << 60) + 1, 
     (1l << 60) + 8, 
     (1l << 60) + 32, 
     (1l << 60) + 64, 
     (1l << 60) + 128, 
     (1l << 60) + 256, 
     (-1L << 53), 
     (-1L << 53) - 1, 
     (-1L << 53) - 2, 
     (-1l << 60), 
     (-1l << 60) - 1, 
     (-1l << 60) - 8, 
     (-1l << 60) - 32, 
     (-1l << 60) - 64, 
     (-1l << 60) - 128, 
     (-1l << 60) - 256 
    }; 
} 

हमारे वातावरण में छोटे अंतर हैं।

मुझे: Windows 10, JDK 1.8.0_45, "शून्य" है सबसे तेजी से

उसे: विंडोज 7, JDK 1.8.0_20, "डाली" सबसे तेज

हमारे परिणाम आत्म-संगत कर रहे हैं चलाने के लिए, चाहे आईडीई में या कमांड लाइन से चल रहा हो। हम जेएमएच 1.10.5 का उपयोग कर रहे हैं।

यहां क्या हो रहा है? बेंचमार्क अविश्वसनीय लगता है और मुझे नहीं पता कि इसे कैसे ठीक किया जाए।

+6

मैं बेंचमार्क की गुणवत्ता का न्याय करने के लिए सक्षम नहीं हूं, लेकिन आपके प्रश्न का एक बहुत ही सरल जवाब है: परिणाम अलग हैं क्योंकि पर्यावरण अलग है: वही JVM नहीं, एक ही ओएस नहीं, शायद नहीं वही हार्डवेयर इससे परिणाम अलग-अलग होने के तीन अच्छे कारण बनते हैं। –

+0

@JBNizet अगर यह एक जेडीके मामूली संस्करण या विंडोज संस्करण या प्रोसेसर-विशिष्ट चीज बन जाता है, जो निराशाजनक होगा, लेकिन कम से कम मुझे इसका जवाब होगा। मैं जानना चाहता हूं कि वास्तव में क्या कारण है। अंतिम लक्ष्य इस विधि को पुस्तकालय में रखना है। यदि परिणाम पर्यावरण में किसी चीज़ पर निर्भर हैं, तो मैं समझना चाहता हूं कि * अधिक संभावना * तेज होने के लिए कौन सा है। अभी यह एक सिक्का फ्लिप की तरह दिखता है। मुझे आशा है कि मैंने बेंचमार्क कोड में एक बेवकूफ त्रुटि की है। –

+0

मेरे पास 1.8.0_45 और 1.8.0_20 दोनों स्थापित हैं। दोनों जेवीएम 'कास्ट' परीक्षण पर बेहतर परिणाम दिखाते हैं, हालांकि अंतर बहुत अधिक नहीं है (जैसे 380 एनएस बनाम 390 एनएस)। आगे की जांच के लिए कृपया दोनों मशीनों के लिए पूर्ण प्रति-पुनरावृत्ति लॉग प्रदान करें। –

उत्तर

2

जेबी निजेट नोट्स के रूप में, आप यह नहीं मान सकते कि एक प्रोग्राम कई जेवीएम और/या ऑपरेटिंग सिस्टम में भी ऐसा करेगा, यदि आपके पास अलग-अलग मशीनें हैं। अंत में

public static boolean zeroes2(final long x) { 
    final long a = Math.abs(x); 
    return (a >>> Long.numberOfTrailingZeros(a)) < (1L << 53); 
} 

, यदि आप वास्तव में अत्यंत प्रदर्शन की जरूरत है, या तो मशीनों पर परीक्षण करने और एक है कि सबसे अच्छा प्रदर्शन लेने के लिए के बेतरतीब नमूने का चयन (जब तक:

वैसे, आप numberOfLeadingZeroes(a) जरूरत नहीं ऐसी मशीनें हैं जहां यह काफी खराब होती है, हालांकि आपके कोड नमूने के साथ, यह काफी संभावना नहीं है), या सभी विधियों को जोड़ें और एक अंशांकन प्रोग्राम बनाएं जो सभी संस्करणों को बेंचमार्क करता है और जिस मशीन पर चल रहा है उसके लिए सबसे तेज़ है।

संपादित करें: जेवियर राज्यों के रूप में, कई असली दुनिया जैसे वर्कलोड के साथ बेंचमार्क करना सुनिश्चित करें।

2

सीपीयू मॉडल समय के साथ विकसित होते हैं। यदि का परिचालन प्रदर्शन बदलता है, या शाखा भविष्यवाणी में कुछ नया सुधार हुआ है, तो आपके पास लगातार अंतर है।

आप एक विकल्प एक विशिष्ट डेटा सेट पर एक नकली लाभ दिया है कि (जैसे अगले मामले में, जहां वास्तविक जीवन में यह सक्षम नहीं हो सकते लगता है कि करने में सक्षम होने) अभी नहीं करना चाहते हैं, तो आप अनियमित/शफ़ल सकता अपने डेटा सेट और इसे लंबा बनाते हैं। बस अगर आप कोशिश करना चाहते हैं, यद्यपि यह शायद व्यर्थ हो सकता है (और संदिग्ध, यदि आपको सटीक उसी डेटा को निष्पादित करने की आवश्यकता है)।

final int times = 10; 
List<Long> listShuffle = new ArrayList<>(NUMBERS.length * times); 
for (long l : NUMBERS) { 
    for (int i = 0; i < times; i++) { 
     listShuffle.add(l); 
    } 
} 

// Shake and serve chilled 
Collections.shuffle(listShuffle); 

पी.डी. # 1 इसके अलावा, अगर आप सिंथेटिक डेटा बजाय वास्तविक डेटा के नमूनों को खिलाने के कर सकते हैं, कि और अधिक सार्थक जानकारी देने के कर सकते हैं। उस मामले में बहुत अधिक CPU अनुमान लगाना वास्तव में अच्छा हो सकता है।

6

मैं उसी वातावरण पर एक ही मशीन पर भी विभिन्न परिणामों को पुन: पेश कर सकता हूं: कभी-कभी cast थोड़ा तेज होता है, कभी-कभी zeros है।

# JMH 1.10.5 (released 9 days ago) 
# VM invoker: C:\Program Files\Java\jdk1.8.0_40\jre\bin\java.exe 
# VM options: -Didea.launcher.port=7540 -Didea.launcher.bin.path=C:\Program Files (x86)\IDEA 14.1.3\bin -Dfile.encoding=UTF-8 
# Warmup: 3 iterations, 1 s each 
# Measurement: 5 iterations, 1 s each 
# Timeout: 10 min per iteration 
# Threads: 1 thread, will synchronize iterations 
# Benchmark mode: Average time, time/op 
# Benchmark: bench.LongDouble.cast 

# Run progress: 0,00% complete, ETA 00:01:20 
# Fork: 1 of 5 
# Warmup Iteration 1: 513,793 ns/op 
# Warmup Iteration 2: 416,508 ns/op 
# Warmup Iteration 3: 402,110 ns/op 
Iteration 1: 402,535 ns/op 
Iteration 2: 403,999 ns/op 
Iteration 3: 404,871 ns/op 
Iteration 4: 404,845 ns/op 
Iteration 5: 401,705 ns/op 

# Run progress: 10,00% complete, ETA 00:01:16 
# Fork: 2 of 5 
# Warmup Iteration 1: 421,552 ns/op 
# Warmup Iteration 2: 418,925 ns/op 
# Warmup Iteration 3: 421,813 ns/op 
Iteration 1: 420,978 ns/op 
Iteration 2: 422,940 ns/op 
Iteration 3: 422,009 ns/op 
Iteration 4: 423,011 ns/op 
Iteration 5: 422,406 ns/op 

# Run progress: 20,00% complete, ETA 00:01:07 
# Fork: 3 of 5 
# Warmup Iteration 1: 414,057 ns/op 
# Warmup Iteration 2: 410,364 ns/op 
# Warmup Iteration 3: 402,330 ns/op 
Iteration 1: 402,776 ns/op 
Iteration 2: 404,764 ns/op 
Iteration 3: 400,346 ns/op 
Iteration 4: 403,227 ns/op 
Iteration 5: 403,350 ns/op 

# Run progress: 30,00% complete, ETA 00:00:58 
# Fork: 4 of 5 
# Warmup Iteration 1: 422,161 ns/op 
# Warmup Iteration 2: 419,118 ns/op 
# Warmup Iteration 3: 402,990 ns/op 
Iteration 1: 401,592 ns/op 
Iteration 2: 402,999 ns/op 
Iteration 3: 403,035 ns/op 
Iteration 4: 402,625 ns/op 
Iteration 5: 403,396 ns/op 

# Run progress: 40,00% complete, ETA 00:00:50 
# Fork: 5 of 5 
# Warmup Iteration 1: 422,621 ns/op 
# Warmup Iteration 2: 419,596 ns/op 
# Warmup Iteration 3: 403,047 ns/op 
Iteration 1: 403,438 ns/op 
Iteration 2: 405,066 ns/op 
Iteration 3: 403,271 ns/op 
Iteration 4: 403,021 ns/op 
Iteration 5: 402,162 ns/op 


Result "cast": 
    406,975 ?(99.9%) 5,906 ns/op [Average] 
    (min, avg, max) = (400,346, 406,975, 423,011), stdev = 7,884 
    CI (99.9%): [401,069, 412,881] (assumes normal distribution) 


# JMH 1.9.3 (released 114 days ago, please consider updating!) 
# VM invoker: C:\Program Files\Java\jdk1.8.0_40\jre\bin\java.exe 
# VM options: -Didea.launcher.port=7540 -Didea.launcher.bin.path=C:\Program Files (x86)\IDEA 14.1.3\bin -Dfile.encoding=UTF-8 
# Warmup: 3 iterations, 1 s each 
# Measurement: 5 iterations, 1 s each 
# Timeout: 10 min per iteration 
# Threads: 1 thread, will synchronize iterations 
# Benchmark mode: Average time, time/op 
# Benchmark: bench.LongDouble.zeros 

# Run progress: 50,00% complete, ETA 00:00:41 
# Fork: 1 of 5 
# Warmup Iteration 1: 439,529 ns/op 
# Warmup Iteration 2: 437,752 ns/op 
# Warmup Iteration 3: 390,530 ns/op 
Iteration 1: 389,394 ns/op 
Iteration 2: 391,453 ns/op 
Iteration 3: 390,446 ns/op 
Iteration 4: 390,822 ns/op 
Iteration 5: 389,850 ns/op 

# Run progress: 60,00% complete, ETA 00:00:33 
# Fork: 2 of 5 
# Warmup Iteration 1: 438,252 ns/op 
# Warmup Iteration 2: 437,446 ns/op 
# Warmup Iteration 3: 448,328 ns/op 
Iteration 1: 389,979 ns/op 
Iteration 2: 392,741 ns/op 
Iteration 3: 390,575 ns/op 
Iteration 4: 390,492 ns/op 
Iteration 5: 390,000 ns/op 

# Run progress: 70,00% complete, ETA 00:00:25 
# Fork: 3 of 5 
# Warmup Iteration 1: 447,939 ns/op 
# Warmup Iteration 2: 444,489 ns/op 
# Warmup Iteration 3: 414,433 ns/op 
Iteration 1: 417,409 ns/op 
Iteration 2: 413,518 ns/op 
Iteration 3: 413,388 ns/op 
Iteration 4: 414,040 ns/op 
Iteration 5: 415,935 ns/op 

# Run progress: 80,00% complete, ETA 00:00:16 
# Fork: 4 of 5 
# Warmup Iteration 1: 439,012 ns/op 
# Warmup Iteration 2: 437,345 ns/op 
# Warmup Iteration 3: 388,208 ns/op 
Iteration 1: 395,647 ns/op 
Iteration 2: 389,221 ns/op 
Iteration 3: 387,539 ns/op 
Iteration 4: 388,524 ns/op 
Iteration 5: 387,623 ns/op 

# Run progress: 90,00% complete, ETA 00:00:08 
# Fork: 5 of 5 
# Warmup Iteration 1: 446,116 ns/op 
# Warmup Iteration 2: 446,622 ns/op 
# Warmup Iteration 3: 409,116 ns/op 
Iteration 1: 409,761 ns/op 
Iteration 2: 410,146 ns/op 
Iteration 3: 410,060 ns/op 
Iteration 4: 409,370 ns/op 
Iteration 5: 411,114 ns/op 


Result "zeros": 
    399,162 ?(99.9%) 8,487 ns/op [Average] 
    (min, avg, max) = (387,539, 399,162, 417,409), stdev = 11,330 
    CI (99.9%): [390,675, 407,649] (assumes normal distribution) 


# Run complete. Total time: 00:01:23 

Benchmark   Mode Cnt Score Error Units 
LongDouble.cast avgt 25 406,975 ± 5,906 ns/op 
LongDouble.zeros avgt 25 399,162 ± 8,487 ns/op 

कुछ विश्लेषण के बाद मुझे पता चला कि समस्या बेंचमार्क में नहीं बल्कि जेएमएच में है। perfasm प्रोफाइलर Blackhole.consume विधि की ओर इशारा किया:

public final void consume(boolean bool) { 
    boolean bool1 = this.bool1; // volatile read 
    boolean bool2 = this.bool2; 
    if (bool == bool1 & bool == bool2) { 
     // SHOULD NEVER HAPPEN 
     nullBait.bool1 = bool; // implicit null pointer exception 
    } 
} 

दिलचस्प हिस्सा कैसे bool1 और bool2 प्रारंभ कर रहे हैं:

Random r = new Random(System.nanoTime()); 
... 
bool1 = r.nextBoolean(); bool2 = !bool1; 

हाँ, वे हर बार यादृच्छिक कर रहे हैं! जैसा कि आप जानते हैं, जेआईटी कंपाइलर रन-टाइम निष्पादन प्रोफ़ाइल पर निर्भर करता है, इसलिए जेनरेट कोड bool1 और bool2 के प्रारंभिक मानों के आधार पर थोड़ा भिन्न होता है, खासतौर पर, आधा मामलों में यह शाखा को लिया जाता है, और बाकी हिस्सों में नहीं लिया जाता है। यही वह जगह है जहां से अंतर आता है।

लेखकों ने दोष की पुष्टि करने के मामले में सुझाए गए फिक्स के साथ जेएमएच के खिलाफ the report सबमिट किया है।

+4

यह एक कारण है कि आप आम तौर पर एक ही फोर्क पर भरोसा नहीं कर सकते हैं: यह केवल रन-टू-रन भिन्नता मुद्दों के पूरे स्लैब को याद करता है, न केवल बेंचमार्क, बेंचमार्क दोहन के कारण, बल्कि रनटाइम पर्यावरण, ओएस, और हार्डवेयर। डिफ़ॉल्ट जेएमएच सेटिंग्स 10 कांटेदार करते हैं; यदि आप ओपी की तरह 1 फोर्क तक "ट्यून" करते हैं, तो आपको परिणामों का सामना करना पड़ता है। –

+0

ओह साफ! जब मैं एकाधिक कांटे (woops) का उपयोग करता हूं, तो मुझे लगता है कि रन-टू-रन भिन्नता है। 2 9 0ns और 270ns के बीच "कास्ट" shuffles, और "शून्य" 250ns और 240ns के बीच shuffles। तो मुझे लगता है कि अभी भी यह मुद्दा है जहां "तेज़ है?" इस पर निर्भर करता है ... कुछ पर्यावरण-विशिष्ट जो मुझे समझ में नहीं आता है। लेकिन यहां मेरी खोज शायद मेरे मूल प्रश्न के उत्तर से अधिक दिलचस्प/उपयोगी है। –

+1

@ माइकल हिक्ससन, यह विशेष बग जेएमएच में तय है। क्या आपने अद्यतन संस्करण के साथ परिणामों को पुन: पेश करने का प्रयास किया है? –

संबंधित मुद्दे