प्रिंटलाइन () को अक्सर एक साथ तार को जोड़ने और एक बार कॉल करने से कितना बुरा है?


23

मुझे पता है कि कंसोल को आउटपुट एक महंगा ऑपरेशन है। कोड पठनीयता के हित में कभी-कभी एक तर्क के रूप में पाठ की एक लंबी स्ट्रिंग होने के बजाय, दो बार टेक्स्ट को आउटपुट करने के लिए फ़ंक्शन को कॉल करना अच्छा होता है।

उदाहरण के लिए यह कितना कम कुशल है

System.out.println("Good morning.");
System.out.println("Please enter your name");

बनाम

System.out.println("Good morning.\nPlease enter your name");

उदाहरण में अंतर केवल एक कॉल का है, println()लेकिन अगर यह अधिक है तो क्या होगा?

संबंधित नोट पर, मुद्रण पाठ से संबंधित कथन स्रोत कोड को देखने के दौरान अजीब लग सकते हैं यदि पाठ मुद्रित करने के लिए लंबा है। यह मानकर कि पाठ को कमतर नहीं किया जा सकता है, क्या किया जा सकता है? क्या यह ऐसा मामला होना चाहिए जहां कई println()कॉल किए जाएं? किसी ने एक बार मुझसे कहा था कि कोड की एक पंक्ति 80 से अधिक अक्षर (IIRC) नहीं होनी चाहिए, इसलिए आप क्या करेंगे

System.out.println("Good morning everyone. I am here today to present you with a very, very lengthy sentence in order to prove a point about how it looks strange amongst other code.");

क्या सी / सी ++ जैसी भाषाओं के लिए भी यही सच है क्योंकि हर बार डेटा को आउटपुट स्ट्रीम में लिखे जाने के बाद सिस्टम कॉल करना होगा और प्रक्रिया को कर्नेल मोड में जाना चाहिए (जो बहुत महंगा है)?


हालांकि यह बहुत कम कोड है, मुझे कहना होगा कि मैं एक ही बात सोच रहा हूं। एक बार और सभी के लिए इसका उत्तर निर्धारित करना अच्छा होगा
साइमन फोर्सबर्ग

@ सिमोनएंड्रॉफ्सबर्ग मुझे यकीन नहीं है कि यह जावा पर लागू होता है क्योंकि यह एक आभासी मशीन पर चलता है, लेकिन निचले स्तर की भाषाओं में जैसे कि सी / सी ++ मैं कल्पना करता हूं कि यह हर बार महंगा होगा क्योंकि कुछ आउटपुट स्ट्रीम को लिखता है, एक सिस्टम कॉल बनाया जाना चाहिए।

वहाँ यह भी विचार करने के लिए है: stackoverflow.com/questions/21947452/…
hjk

1
मेरा कहना है कि मुझे यहाँ की बात दिखाई नहीं दे रही है। टर्मिनल के माध्यम से एक उपयोगकर्ता के साथ बातचीत करते समय, मैं किसी भी प्रदर्शन के मुद्दों की कल्पना नहीं कर सकता क्योंकि आमतौर पर प्रिंट करने के लिए इतना नहीं है। और जीयूआई या वेबएप वाले एप्लिकेशन को लॉग-फाइल (आमतौर पर फ्रेमवर्क का उपयोग करके) लिखना चाहिए।
एंडी

1
यदि आप सुप्रभात कह रहे हैं, तो आप इसे दिन में एक या दो बार करते हैं। अनुकूलन एक चिंता का विषय नहीं है। अगर इसकी कुछ और है, तो आपको यह जानने के लिए प्रोफाइल बनाना होगा कि क्या इसकी समस्या है। जब तक आप लॉगिंग पर काम करते हैं, कोड तब तक अनुपयोगी हो जाता है जब तक कि आप बहु-पंक्ति बफर का निर्माण नहीं करते हैं और एक कॉल में पाठ को डंप करते हैं।
मटनज़

जवाबों:


29

यहां दो 'ताकतें' हैं, तनाव में: प्रदर्शन बनाम पठनीयता।

आइए तीसरी समस्या से पहले निपटें, हालांकि लंबी लाइनें:

System.out.println("Good morning everyone. I am here today to present you with a very, very lengthy sentence in order to prove a point about how it looks strange amongst other code.");

इसे लागू करने और पठनीयता रखने का सबसे अच्छा तरीका है, स्ट्रिंग का उपयोग करना:

System.out.println("Good morning everyone. I am here today to present you "
                 + "with a very, very lengthy sentence in order to prove a "
                 + "point about how it looks strange amongst other code.");

स्ट्रिंग-निरंतर कॉन्सेप्टन संकलन समय पर होगा, और प्रदर्शन पर इसका कोई प्रभाव नहीं पड़ेगा। लाइनें पठनीय हैं, और आप बस आगे बढ़ सकते हैं।

अब, के बारे में:

System.out.println("Good morning.");
System.out.println("Please enter your name");

बनाम

System.out.println("Good morning.\nPlease enter your name");

दूसरा विकल्प काफी तेज है। मैं उपवास के बारे में 2X का सुझाव दूंगा .... क्यों?

क्योंकि काम का 90% (त्रुटि के एक विस्तृत मार्जिन के साथ) वर्णों को आउटपुट में डंप करने से संबंधित नहीं है, लेकिन इसे लिखने के लिए आउटपुट को सुरक्षित करने के लिए ओवरहेड की आवश्यकता है।

तुल्यकालन

System.outएक है PrintStream। सभी जावा कार्यान्वयन, जिनके बारे में मुझे पता है, आंतरिक रूप से PrintStream सिंक्रनाइज़ करते हैं: GrepCode पर कोड देखें!

आपके कोड के लिए इसका क्या अर्थ है?

इसका मतलब है कि हर बार जब आप कॉल System.out.println(...)करते हैं तो आप अपने मेमोरी मॉडल को सिंक्रनाइज़ कर रहे हैं, आप जाँच कर रहे हैं और लॉक की प्रतीक्षा कर रहे हैं। System.out को कॉल करने वाले किसी अन्य थ्रेड को भी लॉक कर दिया जाएगा।

एकल-थ्रेडेड अनुप्रयोगों में System.out.println()अक्सर आपके सिस्टम के IO प्रदर्शन द्वारा सीमित किया जाता है, आप कितनी तेजी से फाइल करने के लिए लिख सकते हैं। बहुपरत अनुप्रयोगों में, लॉकिंग IO की तुलना में अधिक समस्या हो सकती है।

फ्लशिंग

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

कुछ संख्या

मैंने निम्नलिखित छोटे परीक्षण को एक साथ रखा:

public class ConsolePerf {

    public static void main(String[] args) {
        for (int i = 0; i < 100; i++) {
            benchmark("Warm " + i);
        }
        benchmark("real");
    }

    private static void benchmark(String string) {
        benchString(string + "short", "This is a short String");
        benchString(string + "long", "This is a long String with a number of newlines\n"
                  + "in it, that should simulate\n"
                  + "printing some long sentences and log\n"
                  + "messages.");

    }

    private static final int REPS = 1000;

    private static void benchString(String name, String value) {
        long time = System.nanoTime();
        for (int i = 0; i < REPS; i++) {
            System.out.println(value);
        }
        double ms = (System.nanoTime() - time) / 1000000.0;
        System.err.printf("%s run in%n    %12.3fms%n    %12.3f lines per ms%n    %12.3f chars per ms%n",
                name, ms, REPS/ms, REPS * (value.length() + 1) / ms);

    }


}

कोड अपेक्षाकृत सरल है, यह बार-बार या तो शॉर्ट, या आउटपुट के लिए एक लंबी स्ट्रिंग प्रिंट करता है। लंबे स्ट्रिंग में कई नए संस्करण हैं। यह मापता है कि प्रत्येक के 1000 पुनरावृत्तियों को मुद्रित करने में कितना समय लगता है।

अगर मैं यूनिक्स (लिनक्स) पर इसे चलाने के आदेश-शीघ्र, और पुन: निर्देशित STDOUTकरने के लिए /dev/null, और करने के लिए वास्तविक परिणामों को मुद्रित STDERR, मैं निम्नलिखित कर सकते हैं:

java -cp . ConsolePerf > /dev/null 2> ../errlog

आउटपुट (इरलॉग में) ऐसा दिखता है:

Warm 0short run in
           7.264ms
         137.667 lines per ms
        3166.345 chars per ms
Warm 0long run in
           1.661ms
         602.051 lines per ms
       74654.317 chars per ms
Warm 1short run in
           1.615ms
         619.327 lines per ms
       14244.511 chars per ms
Warm 1long run in
           2.524ms
         396.238 lines per ms
       49133.487 chars per ms
.......
Warm 99short run in
           1.159ms
         862.569 lines per ms
       19839.079 chars per ms
Warm 99long run in
           1.213ms
         824.393 lines per ms
      102224.706 chars per ms
realshort run in
           1.204ms
         830.520 lines per ms
       19101.959 chars per ms
reallong run in
           1.215ms
         823.160 lines per ms
      102071.811 chars per ms

इसका क्या मतलब है? मुझे आखिरी 'श्लोक' दोहराने दें:

realshort run in
           1.204ms
         830.520 lines per ms
       19101.959 chars per ms
reallong run in
           1.215ms
         823.160 lines per ms
      102071.811 chars per ms

इसका मतलब है कि, सभी इरादों और उद्देश्यों के लिए, भले ही 'लंबी' लाइन लगभग 5-गुना लंबी हो, और इसमें कई नए अंक हों, लेकिन यह छोटी लाइन के रूप में लंबे समय तक आउटपुट के रूप में लेता है।

लंबे समय के लिए वर्ण-प्रति-सेकंड की संख्या 5 गुना है, और बीता हुआ समय लगभग एक ही है .....

दूसरे शब्दों में, आपका प्रदर्शन आपके पास प्रिंट करने वालों की संख्या के सापेक्ष होता है, न कि वे जो प्रिंट करते हैं।

अद्यतन: क्या होता है यदि आप / dev / null के बजाय किसी फ़ाइल पर रीडायरेक्ट करते हैं?

realshort run in
           2.592ms
         385.815 lines per ms
        8873.755 chars per ms
reallong run in
           2.686ms
         372.306 lines per ms
       46165.955 chars per ms

यह पूरी तरह से धीमी है, लेकिन अनुपात उसी के बारे में हैं ...।


कुछ प्रदर्शन संख्याएँ जोड़ी गईं।
रॉल्फ

आपको उस समस्या पर भी विचार करना "\n"होगा जो सही लाइन टर्मिनेटर नहीं हो सकती है। printlnस्वचालित रूप से सही वर्ण (रेखाओं) के साथ रेखा को समाप्त कर देगा, लेकिन \nसीधे अपनी स्ट्रिंग में चिपकाने से समस्या हो सकती है। यदि आप इसे सही तरीके से करना चाहते हैं, तो आपको स्ट्रिंग फॉर्मेटिंग या line.separatorसिस्टम प्रॉपर्टी का उपयोग करने की आवश्यकता हो सकती है । printlnज्यादा साफ है।
user2357112

3
यह निश्चित रूप से +1 के लिए सभी महान विश्लेषण है, लेकिन मैं तर्क दूंगा कि एक बार जब आप आउटपुट कंसोल के लिए प्रतिबद्ध होते हैं, तो ये मामूली प्रदर्शन अंतर खिड़की से बाहर उड़ जाते हैं। यदि आपके प्रोग्राम का एल्गोरिथ्म परिणाम (आउटपुट के इस छोटे स्तर पर) को आउटपुट करने की तुलना में तेजी से चलता है, तो आप प्रत्येक चरित्र को एक-एक करके प्रिंट कर सकते हैं और अंतर को नोटिस नहीं कर सकते हैं।
डेविड हरकेंस

मेरा मानना ​​है कि यह जावा और सी / सी ++ के बीच अंतर है कि आउटपुट सिंक्रनाइज़ किया गया है। मैं यह कहता हूं क्योंकि मुझे याद है कि एक multithreaded प्रोग्राम लिखना और गार्बल्ड आउटपुट के साथ समस्याएँ होना यदि विभिन्न थ्रेड्स कंसोल पर लिखने के लिए लिखने का प्रयास करते हैं। क्या कोई इसे सत्यापित कर सकता है?

6
यह भी याद रखना महत्वपूर्ण है कि उपयोगकर्ता इनपुट पर प्रतीक्षा करने वाले फ़ंक्शन के ठीक बगल में कोई भी गति मायने नहीं रखती है।
vmrob

2

मुझे नहीं लगता कि printlnएस का एक गुच्छा एक डिजाइन मुद्दा है। जिस तरह से मैं यह देखता हूं कि यह स्पष्ट रूप से स्थिर कोड विश्लेषक के साथ किया जा सकता है अगर यह वास्तव में एक समस्या है।

लेकिन यह कोई समस्या नहीं है क्योंकि ज्यादातर लोग इस तरह से IOs नहीं करते हैं। जब उन्हें वास्तव में बहुत सारे IOs करने की आवश्यकता होती है, तो वे बफर वाले (BufferedReader, BufferedWriter, आदि ..) का उपयोग करते हैं। जब इनपुट बफ़र किया जाता है, तो आप देखेंगे कि प्रदर्शन समान है, कि आपको चिंता करने की कोई आवश्यकता नहीं है printlnया कुछ का गुच्छा println

तो मूल प्रश्न का उत्तर देने के लिए। मैं कहूंगा, बुरा नहीं यदि आप printlnकुछ चीजों को प्रिंट करने के लिए उपयोग करते हैं जैसा कि अधिकांश लोग उपयोग printlnकरेंगे।


1

सी और सी ++ जैसी उच्च स्तरीय भाषाओं में, यह जावा की तुलना में कम समस्या है।

सबसे पहले, C और C ++ कंपाइल-टाइम स्ट्रिंग कॉन्फैटिनेशन को परिभाषित करते हैं, इसलिए आप ऐसा कुछ कर सकते हैं:

std::cout << "Good morning everyone. I am here today to present you with a very, "
    "very lengthy sentence in order to prove a point about how it looks strange "
    "amongst other code.";

ऐसे मामले में, स्ट्रिंग को संक्षिप्त करना केवल एक अनुकूलन नहीं है जो आप बहुत अधिक कर सकते हैं, आमतौर पर (आदि) बनाने के लिए संकलक पर निर्भर करते हैं। इसके बजाय, यह सीधे सी और सी ++ मानकों (अनुवाद के चरण 6: "आसन्न स्ट्रिंग शाब्दिक टोकन के लिए आवश्यक है।")।

हालांकि यह संकलक और कार्यान्वयन में थोड़ी अतिरिक्त जटिलता की कीमत पर है, सी और सी ++ प्रोग्रामर से उत्पादन उत्पादन की जटिलता को छिपाने के लिए थोड़ा अधिक करते हैं। जावा बहुत कुछ असेंबली लैंग्वेज की तरह है - प्रत्येक कॉल System.out.printlnकंसोल को डेटा लिखने के लिए अंतर्निहित ऑपरेटिंग को कॉल करने के लिए बहुत अधिक सीधे अनुवाद करता है। यदि आप दक्षता में सुधार के लिए बफरिंग चाहते हैं, तो उसे अलग से प्रदान करना होगा।

इसका मतलब है, उदाहरण के लिए, कि C ++ में, पिछले उदाहरण को फिर से लिखना, कुछ इस तरह से:

std::cout << "Good morning everyone. I am here today to present you with a very, ";
std::cout << "very lengthy sentence in order to prove a point about how it looks ";       
std::cout << "strange amongst other code.";

... आम तौर पर 1 दक्षता पर लगभग कोई प्रभाव नहीं पड़ता है। प्रत्येक का उपयोग coutबस डेटा को एक बफर में जमा करेगा। जब बफर भर जाता है तो यह बफर अंतर्निहित धारा में प्रवाहित हो जाएगा, या कोड ने उपयोग से इनपुट पढ़ने की कोशिश की (जैसे कि std::cin)।

iostreams के पास एक sync_with_stdioसंपत्ति भी है जो यह निर्धारित करती है कि iostreams से आउटपुट C- शैली इनपुट (जैसे, getchar) के साथ सिंक्रनाइज़ है या नहीं । डिफ़ॉल्ट रूप sync_with_stdioसे सही पर सेट किया जाता है, इसलिए, उदाहरण के लिए, यदि आप लिखते हैं std::cout, तो पढ़ें के माध्यम से getchar, आपके द्वारा लिखा गया डेटा coutजब getcharकहा जाता है, तब फ्लश हो जाएगा । आप इसे sync_with_stdioअक्षम करने के लिए झूठ बोल सकते हैं (आमतौर पर प्रदर्शन में सुधार के लिए)।

sync_with_stdioधागे के बीच तुल्यकालन की एक डिग्री को भी नियंत्रित करता है। यदि सिंक्रनाइज़ेशन चालू है (डिफ़ॉल्ट) कई थ्रेड्स से एक iostream लिखने के परिणामस्वरूप थ्रेड्स से डेटा को इंटरलीव किया जा सकता है, लेकिन किसी भी दौड़ की स्थिति को रोकता है। IOW, आपका प्रोग्राम रन करेगा और आउटपुट तैयार करेगा, लेकिन यदि एक बार में एक से अधिक थ्रेड स्ट्रीम पर लिखते हैं, तो विभिन्न थ्रेड्स से डेटा का मनमाना इंटरमीक्सिंग आमतौर पर आउटपुट को बहुत बेकार कर देगा।

यदि आप सिंक्रोनाइज़ेशन को बंद कर देते हैं , तो कई थ्रेड्स से सिंक्रोनाइज़िंग एक्सेस पूरी तरह से आपकी जिम्मेदारी बन जाती है। समवर्ती कई धागों से लिखता है / कर सकता है एक डेटा दौड़, जो कोड अपरिभाषित व्यवहार है मतलब है।

सारांश

C ++ सुरक्षा के साथ गति को संतुलित करने के प्रयास में चूक करता है। परिणाम एकल-थ्रेडेड कोड के लिए काफी सफल है, लेकिन बहु-थ्रेडेड कोड के लिए कम है। बहु-कोडित कोड को आमतौर पर यह सुनिश्चित करने की आवश्यकता होती है कि उपयोगी उत्पादन के लिए एक बार में केवल एक धागा एक स्ट्रीम को लिखता है।


1. यह एक धारा के लिए बफरिंग को बंद करना संभव है, लेकिन वास्तव में ऐसा करना बहुत ही असामान्य है, और जब / यदि कोई ऐसा करता है, तो यह संभवतः काफी विशिष्ट कारण के लिए है, जैसे कि यह सुनिश्चित करना कि प्रदर्शन पर प्रभाव के बावजूद सभी आउटपुट तुरंत पकड़े जाते हैं। । किसी भी मामले में, यह केवल तभी होता है जब कोड स्पष्ट रूप से करता है।


13
" सी और सी ++ जैसी उच्च स्तरीय भाषाओं में, यह जावा की तुलना में कम समस्या है। " - क्या? C और C ++ जावा की तुलना में निचले स्तर की भाषाएं हैं। इसके अलावा, आप अपने लाइन टर्मिनेटर को भूल गए।
user2357112

1
जावा के निचले स्तर की भाषा होने के लिए मैं उद्देश्य के आधार पर इंगित करता हूं। निश्चित नहीं कि आप किस लाइन टर्मिनेटर के बारे में बात कर रहे हैं।
जेरी कॉफिन

2
जावा कंपाइल-टाइम कॉन्टेनेशन भी करता है। उदाहरण के लिए, "2^31 - 1 = " + Integer.MAX_VALUEएकल इंटर्न स्ट्रिंग (जेएलएस सेक 3.10.5 और 15.28 ) के रूप में संग्रहीत किया जाता है ।
२००:०००

2
@ 200_success: संकलित समय पर स्ट्रिंग कॉन्सेप्टेशन करने वाला जावा 815.18.1 तक नीचे आता है: "स्ट्रिंग ऑब्जेक्ट नए बनाए गए हैं (.512.5) जब तक कि अभिव्यक्ति संकलन-समय स्थिर अभिव्यक्ति (§15.28) नहीं है।" ऐसा लगता है कि अनुमति देने की आवश्यकता नहीं है, लेकिन संकलन समय पर किया जाना चाहिए। यानी, यदि परिणाम संकलन-समय स्थिरांक नहीं हैं, तो परिणाम को नया बनाया जाना चाहिए, लेकिन यदि वे समय स्थिरांक हैं तो किसी भी दिशा में कोई आवश्यकता नहीं होती है। संकलन-समयावधि की आवश्यकता के लिए, आपको इसके (निहित) "यदि" वास्तव में अर्थ के रूप में "यदि और केवल यदि" पढ़ना होगा।
जेरी कॉफिन

2
@ घोषी: संसाधनों के साथ प्रयास करना भी RAII के समान नहीं है। RAII संसाधनों को प्रबंधित करने के लिए वर्ग की अनुमति देता है, लेकिन संसाधनों के साथ प्रयास करने के लिए संसाधनों का प्रबंधन करने के लिए क्लाइंट कोड की आवश्यकता होती है। वे विशेषताएं (सार, अधिक सटीक) जो एक है और दूसरी कमी पूरी तरह से प्रासंगिक हैं - वास्तव में, वे वास्तव में वही हैं जो एक भाषा को दूसरे की तुलना में उच्च स्तर का बनाती है।
जेरी कॉफिन

1

हालांकि प्रदर्शन वास्तव में यहां कोई मुद्दा नहीं है, printlnबयानों की एक गुच्छा की खराब पठनीयता एक लापता डिजाइन पहलू की ओर इशारा करती है।

हम कई printlnकथनों का क्रम क्यों लिखते हैं ? यदि यह केवल एक निश्चित पाठ खंड था, --helpएक कंसोल कमांड में एक पाठ की तरह , तो इसे अलग से एक स्रोत के रूप में रखना और इसे पढ़ने और अनुरोध पर स्क्रीन पर लिखना बेहतर होगा।

लेकिन आमतौर पर यह गतिशील और स्थिर भागों का मिश्रण होता है। मान लें कि हमारे पास एक ओर कुछ नंगे ऑर्डर डेटा हैं, और दूसरी ओर कुछ स्थिर स्थिर पाठ भाग हैं, और ऑर्डर कन्फर्मेशन शीट बनाने के लिए इन चीजों को एक साथ मिलाया जाना है। फिर से, इस मामले में भी, अलग से रिसोर्स टेक्स्ट फाइल होना बेहतर है: रीसोर्स एक टेम्प्लेट होगा, जिसमें कुछ प्रकार के चिन्ह (प्लेसहोल्डर) होंगे, जिन्हें वास्तविक ऑर्डर डेटा द्वारा रनटाइम पर प्रतिस्थापित किया जाता है।

प्रोग्रामिंग भाषा को प्राकृतिक भाषा से अलग करने के कई फायदे हैं - इनमें से अंतर्राष्ट्रीयकरण है: यदि आप अपने सॉफ़्टवेयर के साथ बहुभाषी बनना चाहते हैं तो आपको पाठ का अनुवाद करना पड़ सकता है। इसके अलावा, यदि आप केवल एक पाठ सुधार करना चाहते हैं, तो एक संकलन कदम आवश्यक क्यों होना चाहिए, कुछ गलत वर्तनी को ठीक करें।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.