2017-07-11 55 views
16

अगर मैं जंग में इन मील के पत्थरों चलाएँ:जावा में जंग की तुलना में जंग में लॉगरिदम धीमा क्यों है?

#[bench] 
fn bench_rnd(b: &mut Bencher) { 
    let mut rng = rand::weak_rng(); 
    b.iter(|| rng.gen_range::<f64>(2.0, 100.0)); 
} 

#[bench] 
fn bench_ln(b: &mut Bencher) { 
    let mut rng = rand::weak_rng(); 
    b.iter(|| rng.gen_range::<f64>(2.0, 100.0).ln()); 
} 

परिणाम है:

प्रति ln कॉल
test tests::bench_ln    ... bench:  121 ns/iter (+/- 2) 
test tests::bench_rnd   ... bench:   6 ns/iter (+/- 0) 

121-6 = 115 एनएस।

लेकिन जावा में एक ही बेंचमार्क:

@State(Scope.Benchmark) 
public static class Rnd { 
    final double x = ThreadLocalRandom.current().nextDouble(2, 100); 
} 

@Benchmark 
public double testLog(Rnd rnd) { 
    return Math.log(rnd.x); 
} 

मुझे देता है:

Benchmark Mode Cnt Score Error Units 
Main.testLog avgt 20 31,555 ± 0,234 ns/op 

लॉग है ~ 3.7 गुना धीमी जंग में (115/31) जावा की तुलना में।

जब मैं hypotenuse कार्यान्वयन (hypot) का परीक्षण करता हूं, तो जंग में कार्यान्वयन जावा की तुलना में 15.8 गुना तेज है।

क्या मैंने खराब मानक लिखा है या यह एक प्रदर्शन मुद्दा है? टिप्पणी में पूछे गये सवालों के

जवाब:

  1. "," मेरे देश में एक दशमलव विभाजक है।

  2. मैं cargo bench का उपयोग कर जंग का बेंचमार्क चलाता हूं जो हमेशा रिलीज मोड में चलता है।

  3. जावा बेंचमार्क फ्रेमवर्क (जेएमएच) प्रत्येक कॉल के लिए एक नई वस्तु बनाता है, भले ही यह static वर्ग और final चर हो। यदि मैं परीक्षण विधि में एक यादृच्छिक निर्माण जोड़ता हूं, तो मुझे 43 एनएस/सेशन मिलता है।

+1

क्या जावा बेंचमार्क आधार के रूप में उपयोग करने में बुरा नहीं है? मेरा मतलब है कि जावा अच्छा है लेकिन कुछ मामलों में, यह बहुत अच्छा है – Wietlol

+3

आप लॉग फ़ंक्शन से अधिक यादृच्छिक संख्या जनरेटर को बेंचमार्क कर रहे हैं। साथ ही, मेरा मानना ​​है कि जंग सिर्फ सिस्टम गणित पुस्तकालय का उपयोग करता है, इसलिए 'लॉग' के लिए एक शुद्ध कॉल सी में क्या होना चाहिए (जावा के बारे में कोई विचार नहीं)। –

+2

क्या आप 'RUSTFLAGS =' - Ctarget-cpu = मूल 'कार्गो बेंच' का उपयोग करके परीक्षण को फिर से शुरू कर सकते हैं? – kennytm

उत्तर

7

जवाब given by @kennytm था:

export RUSTFLAGS='-Ctarget-cpu=native' 

सुधार समस्या। उसके बाद, परिणाम हैं:

test tests::bench_ln    ... bench:   43 ns/iter (+/- 3) 
test tests::bench_rnd    ... bench:   5 ns/iter (+/- 0) 

मुझे लगता है कि (± 0.234) 38 (± 3) 31.555 करने के लिए पर्याप्त करीब है।

+0

यह अभी भी जावा के कोड से धीमा है, आश्चर्य की बात है। – Boiethios

6

मैं स्पष्टीकरण के दूसरे आधे हिस्से को प्रदान करने जा रहा हूं क्योंकि मुझे जंग नहीं पता है। Math.log@HotSpotIntrinsicCandidate के साथ एनोटेट किया गया है जिसका अर्थ यह है कि इसे इस तरह के ऑपरेशन के लिए मूल सीपीयू निर्देश द्वारा प्रतिस्थापित किया जाएगा: Integer.bitCount सोचें जो या तो बहुत सी स्थानांतरण या सीधे सीपीयू निर्देश का उपयोग करेगा जो कि बहुत तेज है।

इस तरह एक बहुत ही साधारण प्रोग्राम के बाद:

public static void main(String[] args) { 
    System.out.println(mathLn(20_000)); 
} 

private static long mathLn(int x) { 
    long result = 0L; 
    for (int i = 0; i < x; ++i) { 
     result = result + ln(i); 
    } 
    return result; 
} 

private static final long ln(int x) { 
    return (long) Math.log(x); 
} 

और साथ इसे चलाने:

java -XX:+UnlockDiagnosticVMOptions 
     -XX:+PrintInlining 
     -XX:+PrintIntrinsics 
     -XX:CICompilerCount=2 
     -XX:+PrintCompilation 
     package/Classname 

यह लाइनों का एक बहुत उत्पन्न होगा, लेकिन उनमें से एक है:

@ 2 java.lang.Math::log (5 bytes) intrinsic 

इस कोड को बहुत तेज़ बनाते हैं।

मुझे वास्तव में पता नहीं है कि जंग में कब और कैसे होता है ...

+7

चूंकि जंग स्थिर रूप से (या एओटी, यदि आप चाहें) संकलित है, तो इसे संकलित करने के लिए एक मंच को जानना होगा। डिफ़ॉल्ट रूप से, यह रूढ़िवादी होगा (उदाहरण के लिए, 32-बिट x86 कोड 686 प्रोसेसर को लक्षित कर सकता है)। '-Cargetarget-cpu = native' ध्वज संकलक को उस मशीन को लक्षित करने के लिए कहता है जो संकलक चल रहा है; यह संकलक को उपलब्ध निर्देशों के पूर्ण सेट का उपयोग करने की अनुमति देता है (जैसे आपका 'popcnt' उदाहरण)। – Shepmaster