java - यह जावा कोड समान C#कोड से 6 गुना अधिक तेज क्यों है?




execution-time (5)

मेरे पास प्रोजेक्ट यूलर समस्या 5 के लिए कुछ अलग समाधान हैं, लेकिन इस विशेष कार्यान्वयन में दो भाषाओं / प्लेटफार्मों के बीच निष्पादन का समय अंतर है। मैंने संकलक झंडे के साथ कोई अनुकूलन नहीं किया, बस सादे javac (कमांडलाइन के माध्यम से) और csc (विज़ुअल स्टूडियो के माध्यम से)।

यहाँ जावा कोड है। यह 55ms में समाप्त होता है।

public class Problem005b
{
    public static void main(String[] args)
    {
        long begin = System.currentTimeMillis();
        int i = 20;
        while (true)
        {
            if (
                    (i % 19 == 0) &&
                    (i % 18 == 0) &&
                    (i % 17 == 0) &&
                    (i % 16 == 0) &&
                    (i % 15 == 0) &&
                    (i % 14 == 0) &&
                    (i % 13 == 0) &&
                    (i % 12 == 0) &&
                    (i % 11 == 0)
                )
            {
                break;
            }
            i += 20;
        }
        long end = System.currentTimeMillis();
        System.out.println(i);
        System.out.println(end-begin + "ms");
    }
}

यहाँ समान C # कोड है। यह 320ms में खत्म होता है

using System;

namespace ProjectEuler05
{
    class Problem005
    {
        static void Main(String[] args)
        {
            DateTime begin = DateTime.Now;
            int i = 20;
            while (true)
            {
                if (
                        (i % 19 == 0) &&
                        (i % 18 == 0) &&
                        (i % 17 == 0) &&
                        (i % 16 == 0) &&
                        (i % 15 == 0) &&
                        (i % 14 == 0) &&
                        (i % 13 == 0) &&
                        (i % 12 == 0) &&
                        (i % 11 == 0)
                    )
                    {
                        break;
                    }
                i += 20;
            }
            DateTime end = DateTime.Now;
            TimeSpan elapsed = end - begin;
            Console.WriteLine(i);
            Console.WriteLine(elapsed.TotalMilliseconds + "ms");
        }
    }
}

  1. कोड निष्पादन के लिए, आपको StopWatch क्लास का उपयोग करना चाहिए।
  2. इसके अलावा, आपको जेआईटी, रनटाइम आदि के लिए भी ध्यान रखना होगा, इसलिए परीक्षण को पर्याप्त मात्रा में (जैसे 10,000, 100,000 बार) चलने दें और किसी प्रकार का औसत प्राप्त करें। कोड को कई बार चलाना महत्वपूर्ण है, कि प्रोग्राम को। तो अपनी माप प्राप्त करने के लिए मुख्य विधि में एक विधि और लूप लिखें।
  3. असेंबली से सभी डिबगिंग सामान को हटा दें और कोड को एक रिलीज बिल्ड में अकेले चलने दें

(ओपी से स्थानांतरित)

X86 से anycpu तक के लक्ष्य को बदलने से औसत निष्पादन समय 282ms से नीचे 84 रन प्रति रन हो गया है। शायद मैं इसे एक दूसरे धागे में विभाजित कर दूं?

अद्यतन करें:
नीचे Femaref के लिए धन्यवाद जिन्होंने कुछ परीक्षण समस्याओं को इंगित किया , और वास्तव में, उनके सुझावों का पालन करने के बाद, समय कम है, यह दर्शाता है कि VM सेटअप समय जावा में महत्वपूर्ण था, लेकिन शायद C # में नहीं। C # में, यह डीबग प्रतीक थे जो महत्वपूर्ण थे।

मैंने प्रत्येक लूप को 10,000 बार चलाने के लिए अपने कोड को अपडेट किया, और अंत में केवल औसत एमएस आउटपुट किया। मेरे द्वारा किए गए एकमात्र महत्वपूर्ण परिवर्तन C # संस्करण में था, जहाँ मैंने अधिक रिज़ॉल्यूशन के लिए [StopWatch वर्ग] [3] पर स्विच किया। मैं मिलीसेकंड के साथ फंस गया क्योंकि यह काफी अच्छा है।

परिणाम:
परीक्षण परिवर्तन यह नहीं समझाते हैं कि जावा # (अभी भी) C # की तुलना में अधिक तेज़ क्यों है। सी # प्रदर्शन बेहतर था, लेकिन डिबग प्रतीकों को हटाकर इसे पूरी तरह से समझाया जा सकता है। यदि आप [माइक टू] [4] पढ़ते हैं और इस ओपी से जुड़ी टिप्पणियों पर मैं एक्सचेंज करता हूं, तो आप देखेंगे कि मुझे डिबग से रिलीज तक स्विच करके सिर्फ सी # कोड के पांच रनों में ~ 280 मी औसत मिला।

नंबर:

  • अनमॉडिफाइड जावा कोड के 10,000 काउंट लूप ने मुझे औसतन 45ms (55ms से नीचे) दिया
  • स्टॉपवॉच क्लास का उपयोग करके C # कोड के 10,000 काउंट लूप ने मुझे 282ms (320ms से नीचे) का औसत दिया

यह सब अस्पष्टीकृत अंतर को छोड़ देता है। वास्तव में, अंतर खराब हो गया। जावा ~ 5.8x तेज से ~ 6.2x तेज हो गया।


कुछ अनुकूलन संभव हैं। शायद जावा JIT उन्हें प्रदर्शन कर रही है और सीएलआर नहीं है।

अनुकूलन # 1:

(x % a == 0) && (x % b == 0) && ... && (x % z == 0)

के बराबर है

(x % lcm(a, b, ... z) == 0)

तो आपके उदाहरण में तुलना श्रृंखला को प्रतिस्थापित किया जा सकता है

if (i % 232792560 == 0) break;

(लेकिन निश्चित रूप से यदि आप पहले से ही एलसीएम की गणना कर चुके हैं, तो पहली जगह में कार्यक्रम चलाने का कोई मतलब नहीं है!)

अनुकूलन # 2 :

यह भी समतुल्य है:

if (i % (14549535 * 16)) == 0 break;

या

if ((i % 16 == 0) && (i % 14549535 == 0)) break;

पहले डिवीजन को मास्क से बदला जा सकता है और शून्य के खिलाफ तुलना की जा सकती है:

if (((i & 15) == 0) && (i % 14549535 == 0)) break;

दूसरे भाग को मॉड्यूलर व्युत्क्रम द्वारा गुणा से बदला जा सकता है:

final long LCM = 14549535;
final long INV_LCM = 8384559098224769503L; // == 14549535**-1 mod 2**64
final long MAX_QUOTIENT = Long.MAX_VALUE / LCM;
// ...
if (((i & 15) == 0) &&
    (0 <= (i>>4) * INV_LCM) &&
    ((i>>4) * INV_LCM < MAX_QUOTIENT)) {
    break;
}

यह कुछ संभावना नहीं है कि जेआईटी इसे नियोजित कर रहा है, लेकिन यह उतना दूर नहीं है जितना आप सोच सकते हैं - कुछ सी कंपाइलर इस तरह से सूचक घटाव को लागू करते हैं।


जावा में मैं System.nanoTime () का उपयोग करेगा। कोई भी परीक्षण जो 2 सेकंड से कम समय लेता है, उसे अधिक समय तक चलाया जाना चाहिए। यह ध्यान देने योग्य है कि जावा अक्षम कोड या कोड का अनुकूलन करने में बहुत अच्छा है जो कुछ भी नहीं करता है। यदि आप कोड को अनुकूलित करते हैं तो एक और दिलचस्प परीक्षा होगी।

आप एक समाधान प्राप्त करने की कोशिश कर रहे हैं जिसे आप लूप का उपयोग किए बिना निर्धारित कर सकते हैं। यानी एक समस्या जो बेहतर तरीके से की जाएगी।

आप 11 से 20 के कारकों का उत्पाद चाहते हैं, जो 2,2,2,3,3,5,7,11,13,17,19 हैं। इन्हें एक साथ गुणा करें और आपके पास इसका उत्तर है।


शायद क्योंकि DateTime वस्तुओं का निर्माण System.currentTimeMillis तुलना में बहुत अधिक महंगा है।





execution-time