2012-01-26 15 views
10

मैं जल्दी से फ़ाइलें (लगभग 500,000 लाइनों से युक्त) Gzipped एक सी कार्यक्रम का एक सेट की i-वें लाइन निकालने लिखा था। यहाँ मेरी सी कार्यक्रम है:सी जावा से धीमा: क्यों?

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

/* compilation: 
gcc -o linesbyindex -Wall -O3 linesbyindex.c -lz 
*/ 
#define MY_BUFFER_SIZE 10000000 
static void extract(long int index,const char* filename) 
    { 
    char buffer[MY_BUFFER_SIZE]; 
    long int curr=1; 
    gzFile in=gzopen (filename, "rb"); 
    if(in==NULL) 
     { 
     fprintf(stderr,"Cannot open \"%s\" %s.\n",filename,strerror(errno)); 
     exit(EXIT_FAILURE);    } 
    while(gzread(in,buffer,MY_BUFFER_SIZE)!=-1 && curr<=index) 
     { 
     char* p=buffer; 
     while(*p!=0) 
      { 
      if(curr==index) 
       { 
       fputc(*p,stdout); 
       } 
      if(*p=='\n') 
       { 
       ++curr; 
       if(curr>index) break; 
       } 
      p++; 
      } 
     } 
    gzclose(in); 
    if(curr<index) 
     { 
     fprintf(stderr,"Not enough lines in %s (%ld)\n",filename,curr); 
     } 
    } 

int main(int argc,char** argv) 
    { 
    int optind=2; 
    char* p2; 
    long int count=0; 
    if(argc<3) 
     { 
     fprintf(stderr,"Usage: %s (count) files...\n",argv[0]); 
     return EXIT_FAILURE; 
     } 
    count=strtol(argv[1],&p2,10); 
    if(count<1 || *p2!=0) 
     { 
     fprintf(stderr,"bad number %s\n",argv[1]); 
     return EXIT_SUCCESS; 
     } 
    while(optind< argc) 
     { 
     extract(count,argv[optind]); 
     ++optind; 
     } 
    return EXIT_SUCCESS; 
    } 

एक परीक्षण के रूप में, मैं जावा में निम्नलिखित बराबर कोड लिखा है:

import java.io.*; 
import java.util.zip.GZIPInputStream; 

public class GetLineByIndex{ 
    private int index; 

    public GetLineByIndex(int count){ 
     this.index=count; 
    } 

    private String extract(File file) throws IOException 
     { 
     long curr=1; 
     byte buffer[]=new byte[2048]; 
     StringBuilder line=null; 
     InputStream in=null; 
     if(file.getName().toLowerCase().endsWith(".gz")){ 
      in= (new GZIPInputStream(new FileInputStream(file))); 
     }else{ 
      in= (new FileInputStream(file)); 
     } 
      int nRead=0; 
     while((nRead=in.read(buffer))!=-1) 
      { 
      int i=0; 
      while(i<nRead) 
       { 
       if(buffer[i]=='\n') 
        { 
        ++curr; 
        if(curr>this.index) break; 
            } 
       else if(curr==this.index) 
        { 
        if(line==null) line=new StringBuilder(500); 
        line.append((char)buffer[i]); 
        } 
       i++; 
       } 
      if(curr>this.index) break; 
      } 
     in.close(); 
     return (line==null?null:line.toString()); 
     } 

    public static void main(String args[]) throws Exception{ 
     int optind=1; 
     if(args.length<2){ 
      System.err.println("Usage: program (count) files...\n"); 
      return; 
     } 
     GetLineByIndex app=new GetLineByIndex(Integer.parseInt(args[0])); 

     while(optind < args.length) 
      { 
      String line=app.extract(new File(args[optind])); 
      if(line==null) 
       { 
       System.err.println("Not enough lines in "+args[optind]); 
       } 
      else 
       { 
       System.out.println(line); 
       } 
      ++optind; 
      } 
     return; 
    } 
} 

ऐसा होता है कि जावा कार्यक्रम था बहुत तेजी से (~ 1'45 '') एक ही मशीन पर सी प्रोग्राम (~ 2'15 '') की तुलना में एक बड़ी अनुक्रमणिका लाने के लिए (मैंने कई बार परीक्षण चलाया)।

मैं उस अंतर को कैसे समझा सकता हूं?

+2

नोट: buffersizes बराबर इसलिए कार्यक्रमों "सटीक" एक ही बात नहीं करते नहीं हैं। –

+0

@SaniHuttunen - कोड उस से अधिक कारणों के बराबर नहीं है :) – Perception

+0

@ धारणा: सच है, लेकिन यह मेरा पहला अवलोकन था और यह इंगित करने के लिए पर्याप्त लग रहा था कि कार्यक्रम वास्तव में बराबर नहीं हैं। –

उत्तर

22

जावा संस्करण के लिए सबसे अधिक संभावना स्पष्टीकरण सी संस्करण से तेज होना है कि सी संस्करण गलत है।

सी संस्करण फिक्सिंग के बाद, मैं निम्नलिखित परिणाम (अपने दावे कि जावा सी की तुलना में तेजी है विरोधाभासी) प्राप्त:

Java 1.7 -client: 65 milliseconds (after JVM warmed up) 
Java 1.7 -server: 82 milliseconds (after JVM warmed up) 
gcc -O3:   37 milliseconds 

कार्य फ़ाइल words.gz से 200000-वें लाइन मुद्रित करने के लिए किया गया था। फ़ाइल words.gz gzipping /usr/share/dict/words द्वारा उत्पन्न किया गया था।


... 
static char buffer[MY_BUFFER_SIZE]; 
... 
ssize_t len; 
while((len=gzread(in,buffer,MY_BUFFER_SIZE)) > 0 && curr<=index) 
    { 
    char* p=buffer; 
    char* endp=buffer+len; 
    while(p < endp) 
     { 
... 
+0

सी संस्करण में आपने क्या बदल दिया है? जांच के लिए – Pierre

+0

+1 –

+0

धन्यवाद! पहली बार मैंने अपना सी कोड लिखा, मैंने gzread के बजाय gzgets का उपयोग किया लेकिन मैंने बफर पर लूप में परीक्षण नहीं बदला। – Pierre

15

क्योंकि fputc() बहुत तेज नहीं है और आप अपनी आउटपुट फ़ाइल में स्टफ चार-बाय-char जोड़ रहे हैं।

fputc_unlocked को कॉल करना या उन चीज़ों को सीमित करना जो आप जोड़ना चाहते हैं और fwrite() को कॉल करना तेज़ होना चाहिए।

+0

आपका उत्तर गलत है। प्रश्न के लेखक ने अपनी जीजेआईपी फाइलों में एक लाइन की औसत लंबाई निर्दिष्ट नहीं की थी। –

+0

'fputc()' का उपयोग केवल एक ही पंक्ति के लिए किया जाता है, जो कि समान रूप से समान रेखाओं को छोड़कर बड़ी संख्या में छोड़ देता है। * आंतरिक लूप नहीं * हमें खोजना चाहिए। विशाल स्वचालित बफर एक बेहतर उम्मीदवार है। इसे जावा (2048) के समान आकार बनाना उचित तुलना करने की अनुमति देगा। – chqrlie

12

वैसे आपके कार्यक्रम अलग-अलग चीजें कर रहे हैं। मैं अपने कार्यक्रम प्रोफ़ाइल नहीं था, लेकिन अपने कोड में देखने से मैं इस अंतर को संदेह है:

लाइन का निर्माण के लिए, आप जावा में इस का उपयोग करें:

if(curr==this.index) 
{ 
    if(line==null) line=new StringBuilder(500); 
    line.append((char)buffer[i]); 
} 

और यह सी में:

if(curr==index) 
{ 
    fputc(*p,stdout); 
} 

आईई आप एक समय में एक चरित्र को stdout प्रिंट कर रहे हैं। जो डिफ़ॉल्ट रूप से बफर है, लेकिन मुझे संदेह है कि यह जावा में आपके द्वारा उपयोग किए जाने वाले 500 वर्ण बफर से धीमा है।

0

मुझे कंपाइलर के अनुकूलन के बारे में गहराई से ज्ञान नहीं है, लेकिन मुझे लगता है कि यह आपके कार्यक्रमों के बीच अंतर बनाता है। Microbenchmarks इस तरह की, बहुत, बहुत सही और सार्थक पाने के लिए बहुत मुश्किल है। ब्रायन गोएट्ज़ द्वारा यहां एक लेख दिया गया है जो इस पर विस्तारित करता है: http://www.ibm.com/developerworks/java/library/j-jtp02225/index.html

0

बहुत बड़े बफर धीमे हो सकते हैं। मैं सुझाव दूंगा कि आप बफर आकार को वही बना दें। यानी दोनों 2 या 8 केबी

+0

मैंने stdio का उपयोग शुरू किया: BUFSIZ: ~ उसी परिणाम – Pierre

+0

सी (zlib) में बड़ा बफर कोई फर्क नहीं पड़ता, जावा में यह कई बार कॉपी होने के बाद से करता है। आप मेमोरी मैप की गई फाइल का भी उपयोग कर सकते हैं। जावा का फ़ाइल इनपुटपुट (था?) विन में छोटे बफर 2 के लिए ऑप्टिमाइज़ किया गया है, 8 के - लिनक्स, उस मामले में आवंटित करने के लिए स्टैक का उपयोग करता है, अन्यथा यह मॉलोक/फ्री है (और कुछ मॉलोक स्टैक से बहुत धीमे होते हैं), यही कारण है कि छोटे बफर प्रदर्शन करता है बेहतर। गहरी रिकर्सन, डबल सिगसेग में कॉल करते समय मुझे मूल स्मृति में भयंकर दुर्घटनाएं हुईं और प्रक्रिया मर गई है (दूसरा क्रैश लॉग लिखने का प्रयास करते समय होता है, इसलिए कोई क्रैश लॉग इवेंट नहीं) – bestsss

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