2017-04-23 15 views
11

this link पर निर्दिष्ट कोडिंग प्रतियोगिता में एक ऐसा कार्य है जहां आपको stdin पर अधिक डेटा पढ़ने की आवश्यकता है, कुछ गणना करें और stdout पर एक बहुत सारे डेटा प्रस्तुत करें।सी में फास्ट I/O, stdin/out

मेरी बेंचमार्किंग में यह लगभग केवल I/o है जो समय लगता है हालांकि मैंने जितना संभव हो इसे अनुकूलित करने का प्रयास किया है।

आपके पास इनपुट के रूप में क्या है एक स्ट्रिंग (1 <= len <= 100'000) और int की जोड़ी की क्यू पंक्तियां जहां q भी 1 <= q <= 100'000 है।

मैं एक 100 गुना बड़ा डाटासेट पर अपने कोड बेंचमार्क (लेन = 10M, q = 10M) और इस परिणाम है:

Activity   time  accumulated 

Read text:   0.004  0.004 
Read numbers:  0.146  0.150 
Parse numbers:  0.200  0.350 
Calc answers:  0.001  0.351 
Format output:  0.037  0.388 
Print output:  0.143  0.531 

मेरे अपने formating और संख्या को पार्स इनलाइन को लागू करने से मैं पाने में कामयाब रहे printf और scanf का उपयोग करते समय समय के 1/3 तक समय।

हालांकि जब मैंने प्रतियोगिताओं के वेबपृष्ठ पर अपना समाधान अपलोड किया तो मेरे समाधान में 1.88 सेकंड (मुझे लगता है कि 22 डेटासेट्स का कुल समय है)। जब मैं उच्च स्कोर में देखता हूं तो कई कार्यान्वयन (सी ++ में) होते हैं जो 0.05 सेकेंड में समाप्त होते हैं, मेरी तुलना में लगभग 40 गुना तेज! वो कैसे संभव है?

मुझे लगता है कि मैं इसे 2 धागे का उपयोग करके थोड़ा सा गति दे सकता हूं, फिर भी मैं stdin से पढ़ते समय गणना और लिखना शुरू कर सकता हूं। हालांकि यह मेरे बड़े डेटासेट पर सैद्धांतिक सर्वोत्तम मामले में min(0.150, 0.143) के समय को कम करेगा। मैं अभी भी हाईस्कॉर के करीब कहीं नहीं हूं ..

नीचे दी गई छवि में आप उपभोग किए गए समय के आंकड़े देख सकते हैं।

Statistics of the consumed time

कार्यक्रम इस विकल्प के साथ वेबसाइट द्वारा संकलित हो जाता है:

gcc -g -O2 -std=gnu99 -static my_file.c -lm 

और इस तरह समय समाप्त हो गया:

time ./a.out <sample.in> sample.out 

मेरे कोड इस तरह दिखता है:

#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

#define MAX_LEN (100000 + 1) 
#define ROW_LEN (6 + 1) 
#define DOUBLE_ROW_LEN (2*ROW_LEN) 

int main(int argc, char *argv[]) 
{ 
    int ret = 1; 

    // Set custom buffers for stdin and out 
    char stdout_buf[16384]; 
    setvbuf(stdout, stdout_buf, _IOFBF, 16384); 
    char stdin_buf[16384]; 
    setvbuf(stdin, stdin_buf, _IOFBF, 16384); 

    // Read stdin to buffer 
    char *buf = malloc(MAX_LEN); 
    if (!buf) { 
     printf("Failed to allocate buffer"); 
     return 1; 
    } 
    if (!fgets(buf, MAX_LEN, stdin)) 
     goto EXIT_A; 

    // Get the num tests 
    int m ; 
    scanf("%d\n", &m); 

    char *num_buf = malloc(DOUBLE_ROW_LEN); 
    if (!num_buf) { 
     printf("Failed to allocate num_buffer"); 
     goto EXIT_A; 
    } 

    int *nn; 
    int *start = calloc(m, sizeof(int)); 
    int *stop = calloc(m, sizeof(int)); 
    int *staptr = start; 
    int *stpptr = stop; 
    char *cptr; 
    for(int i=0; i<m; i++) { 
     fgets(num_buf, DOUBLE_ROW_LEN, stdin); 
     nn = staptr++; 
     cptr = num_buf-1; 
     while(*(++cptr) > '\n') { 
      if (*cptr == ' ') 
       nn = stpptr++; 
      else 
       *nn = *nn*10 + *cptr-'0'; 
     } 
    } 


    // Count for each test 
    char *buf_end = strchr(buf, '\0'); 
    int len, shift; 
    char outbuf[ROW_LEN]; 
    char *ptr_l, *ptr_r, *out; 
    for(int i=0; i<m; i++) { 
     ptr_l = buf + start[i]; 
     ptr_r = buf + stop[i]; 
     while(ptr_r < buf_end && *ptr_l == *ptr_r) { 
      ++ptr_l; 
      ++ptr_r; 
     } 

     // Print length of same sequence 
     shift = len = (int)(ptr_l - (buf + start[i])); 
     out = outbuf; 
     do { 
      out++; 
      shift /= 10; 
     } while (shift); 
     *out = '\0'; 
     do { 
      *(--out) = ""[len%10]; 
      len /= 10; 
     } while(len); 
     puts(outbuf); 
    } 



    ret = 0; 

    free(start); 
    free(stop); 
EXIT_A: 
    free(buf); 
    return ret; 
} 
+0

आप अलग-अलग चींटियों के लिए स्मृति आवंटित क्यों कर रहे हैं? आप किस प्रणाली पर हैं? लिनक्स पर, विंडोज़ पर स्टडीओ तेजी से (और विंडोज़ पर आईस्ट्रीम की तुलना में तेज) है, विंडोज़ पर, आईओस्ट्रीम stdio outpeforms। आईओ फंक्शंस के अनलॉक वेरिएंट्स (puts_unlocked के बजाय puts ,unlocked) का उपयोग करके stdio को कुछ हद तक तेज बनाया जा सकता है क्योंकि POSIX को कॉल के लिए रिकर्सिव लॉक का उपयोग करने के लिए stdio की आवश्यकता होती है, जबकि iostream (AFAIK) के लिए ऐसी कोई आवश्यकता मौजूद नहीं है। – PSkocik

+0

ऐसा लगता है कि आप प्रत्येक बार लूप के माध्यम से आउटपुट कर रहे हैं। क्या आपने गति के लिए स्मृति का व्यापार किया है, एक बड़ा बफर आवंटित किया है, और फिर पूरे आउटपुट को एक बार में मुद्रित किया है? या, यदि इसके लिए व्यवहार्य होने के लिए बहुत अधिक उत्पादन है, तो आप अभी भी बफरिंग के माध्यम से आउटपुट को समेकित कर सकते हैं। यदि समस्या 'वास्तव में * आपकी बाधा है तो यह समस्या हल करेगी। मुझे यकीन नहीं है कि आप उन समय पहुंचने के लिए कैसे माप रहे हैं। कहें, "प्रिंट आउटपुट" माप में सभी परिचालन शामिल हैं? –

+0

माइनर: 'cptr = num_buf-1; 'अपरिभाषित व्यवहार है - हालांकि यह वांछित के रूप में" काम करता है "। – chux

उत्तर

0

आपको अपने सभी बफर निरंतर आवंटित करना चाहिए। एक बफर आवंटित करें जो आपके सभी बफर का आकार है (num_buff, प्रारंभ करें, रोकें) फिर अंक को उनके आकार से संबंधित ऑफसेट पर पुनर्व्यवस्थित करें। यह आपके कैश मिस \ पेज दोषों को कम कर सकता है।

चूंकि पढ़ने और लिखने के ऑपरेशन में बहुत समय लगता है कि आपको धागे जोड़ने पर विचार करना चाहिए। एक थ्रेड को I \ O से निपटना चाहिए और दूसरे को गणना के साथ सौदा करना चाहिए। (यह जांचने लायक है कि प्रिंट के लिए एक और धागा चीजों को भी तेज कर सकता है)। सुनिश्चित करें कि आप ऐसा करते समय किसी भी ताले का उपयोग नहीं करते हैं।

0

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

दूसरी ओर, आप निम्न-स्तरीय सिस्टम फ़ंक्शंस का उपयोग करने का प्रयास कर सकते हैं। जो मेरी सोच में आता है वह mmap है जो आपको सीधे फ़ाइल में मैप करने और scanf और fgets का उपयोग करने के बजाय उस स्मृति तक पहुंचने की अनुमति देता है।

एक और चीज जो मैंने पाया है वह आपकी सोल्यूटीन में मदद कर सकती है, आपके पास दो while लूप हैं, क्यों न केवल कोशिश करें और उपयोग करें? एक और बात कुछ असिंक्रोनस I/O पढ़ने के लिए होगी, इसलिए लूप में पूरी फ़ाइल पढ़ने की बजाय, और फिर किसी अन्य लूप में गणना करने के बजाय, आप शुरुआत में एक भाग को आजमा सकते हैं और पढ़ सकते हैं, इसे एसिंक को प्रोसेस करना शुरू करें और जारी रखें पढ़ने। यह link एसिंक भाग

1

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

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

उस दावे की रक्षा कैसे करें? कोई वास्तविक एल्गोरिदमिक जटिलता नहीं है: समस्या ओ (एन) है। "चाल" इनपुट के प्रत्येक पहलू के लिए विशेष पार्सर्स लिखना है (और केवल डीबग मोड में किए गए काम से बचें)। 22 परीक्षणों के लिए कुल समय 50 मिलीसेकंड है, जिसका अर्थ है कि प्रत्येक परीक्षण 2.25 एमएस औसत है? हम मापनीयता की दहलीज के पास नीचे हैं।

जिस तरह की समस्या आपने स्वयं को संबोधित की है, वह प्रतिस्पर्धा दुर्भाग्यपूर्ण है, एक तरह से। वे निष्पक्ष विचार को मजबूत करते हैं कि प्रदर्शन एक कार्यक्रम का अंतिम उपाय है (स्पष्टता के लिए कोई स्कोर नहीं है)। इससे भी बदतर, वे "प्रदर्शन के लिए स्कैनफ" जैसी चीजों के आसपास जाने के लिए प्रोत्साहित करते हैं, जबकि वास्तविक जीवन में, सही ढंग से चलाने के लिए एक प्रोग्राम प्राप्त करना और तेजी से मूल रूप से कभी भी टाइडिंग से बचने या ट्यूनिंग करने में शामिल नहीं होता है। एक जटिल प्रणाली में, प्रदर्शन I/O से बचने, केवल एक बार डेटा को पार करने और प्रतियों को कम करने जैसी चीजों से मिलता है। डीबीएमएस का प्रभावी ढंग से उपयोग करना अक्सर महत्वपूर्ण होता है (जैसा कि यह था), लेकिन ऐसी चीजें प्रोग्रामिंग चुनौतियों में कभी दिखाई नहीं देतीं।

टेक्स्ट के रूप में पार्सिंग और स्वरूपण संख्या में समय लगता है, और दुर्लभ परिस्थितियों में एक बाधा हो सकती है। लेकिन जवाब शायद पार्सर को फिर से लिखना मुश्किल है। इसके बजाय, जवाब टेक्स्ट को एक सुविधाजनक बाइनरी रूप में पार्स करना है, और इसका उपयोग करना है। संक्षेप में: संकलन।

उसने कहा, कुछ अवलोकन मदद कर सकते हैं।

आपको इस समस्या के लिए गतिशील स्मृति की आवश्यकता नहीं है, और यह मदद नहीं कर रहा है। समस्या का बयान कहता है कि इनपुट सरणी 100,000 तत्वों तक हो सकती है, और परीक्षणों की संख्या 100,000 हो सकती है। प्रत्येक परीक्षण एक स्थान से अलग 6 अंकों तक दो पूर्णांक तार होता है और एक नई लाइन द्वारा समाप्त होता है: 6 + 1 + 6 + 1 = 14. कुल इनपुट, अधिकतम 100,000 + 1 + 6 + 1 + 100,000 * 14: नीचे है 16 केबी आपको 1 जीबी मेमोरी की अनुमति है।

मैंने अभी एक 16 केबी बफर आवंटित किया है, और इसे पढ़ने के साथ एक बार में पढ़ा है (2)। तब मैंने उस इनपुट पर एक ही पास किया।

आपको एसिंक्रोनस I/O और थ्रेड का उपयोग करने के लिए सुझाव मिल गए हैं। समस्या कथन का कहना है कि आप CPU समय पर मापा जाता है, इसलिए उन सहायता में से कोई भी नहीं। दो बिंदुओं के बीच सबसे छोटी दूरी एक सीधी रेखा है; स्थिर आवंटित स्मृति में एक एकल पढ़ा कोई गति नहीं था।

प्रदर्शन को मापने के तरीके का एक हास्यास्पद पहलू यह है कि वे gcc -g का उपयोग करते हैं।इसका मतलब है जोर (3) कोड में शामिल किया गया है जो प्रदर्शन के लिए मापा जाता है! मैं परीक्षण 22 पर 4 सेकंड के भीतर नहीं मिल सका जब तक कि मैंने अपने आवेषण हटा दिए।

कुल मिलाकर, आपने बहुत अच्छा किया है, और मुझे संदेह है कि जिस विजेता को आप परेशान कर रहे हैं वह एक प्रेत है। आपका कोड थोड़ा सा है, और आप गतिशील स्मृति और ट्यूनिंग stdio के साथ बांट सकते हैं। मैं शर्त लगाता हूं कि आपका समय इसे सरल बनाकर छंटनी की जा सकती है। उस हद तक प्रदर्शन की बात है, यही वह जगह है जहां मैं आपका ध्यान निर्देशित करूंगा।

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