2012-01-26 44 views
10

tôi nhanh chóng viết một chương trìnhC chiết xuất dòng thứ i một tập hợp các Gzipped file (chứa khoảng 500.000 lines). Đây là chương trình C của tôi:C chậm hơn so với Java: tại sao?

#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; 
    } 

Là một thử nghiệm, tôi đã viết mã tương đương sau đây trong java:

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; 
    } 
} 

Nó xảy ra rằng chương trình java là nhanh hơn nhiều (~ 1'45 '') để lấy chỉ mục lớn hơn chương trình C (~ 2'15 '') trên cùng một máy (tôi đã chạy thử nghiệm đó nhiều lần).

Làm cách nào để giải thích sự khác biệt đó?

+2

Lưu ý: Các buffersizes không bằng nhau do đó các chương trình không làm "chính xác" điều tương tự. –

+0

@SaniHuttunen - mã không tương đương vì nhiều lý do hơn :) – Perception

+0

@Perception: Đúng, nhưng đó là quan sát đầu tiên của tôi và dường như đủ để chỉ ra rằng các chương trình thực sự không bằng nhau. –

Trả lời

22

Giải thích có khả năng nhất cho phiên bản Java sẽ nhanh hơn phiên bản C là phiên bản C không chính xác.

Sau khi sửa chữa các phiên bản C, tôi thu được kết quả như sau (mâu thuẫn với tuyên bố của bạn rằng Java là nhanh hơn so với C):

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

Nhiệm vụ là để in các dòng 200000-thứ từ tập tin words.gz. Tệp words.gz được tạo bởi 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

bạn đã thay đổi gì trong phiên bản C? – Pierre

+0

+1 để điều tra –

+0

Cảm ơn! lần đầu tiên tôi viết mã C của mình, tôi đã sử dụng gzgets thay vì gzread nhưng tôi không thay đổi kiểm tra trong vòng lặp trên bộ đệm. – Pierre

15

Bởi vì fputc() không phải là rất nhanh và bạn đang thêm stuf char-by-char trong tệp đầu ra của bạn.

gọi fputc_unlocked hoặc đúng hơn là phân định nội dung bạn muốn thêm và gọi hàm fwrite() phải nhanh hơn.

+0

Câu trả lời của bạn không đúng. Tác giả của câu hỏi không chỉ định độ dài trung bình của một dòng trong các tệp GZIP của anh ấy. –

+0

'fputc()' chỉ được sử dụng cho một dòng đơn lẻ sau khi bỏ qua một số lượng lớn các dòng được cho là tương tự. Không phải * vòng lặp bên trong * chúng ta nên tìm kiếm. Bộ đệm tự động lớn là một ứng cử viên tốt hơn. Làm cho nó có cùng kích thước như trong java (2048) sẽ cho phép so sánh công bằng. – chqrlie

12

Các chương trình của bạn đang làm những việc khác nhau. Tôi không hồ sơ chương trình của bạn, nhưng từ nhìn vào mã của bạn tôi nghi ngờ sự khác biệt này:

Đối với việc xây dựng đường, bạn sử dụng điều này trong Java:

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

Và điều này trong C:

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

Tức là bạn đang in một ký tự tại một thời điểm để stdout. Đó là buffere, theo mặc định, nhưng tôi nghi ngờ nó vẫn còn chậm hơn so với bộ đệm 500 ký tự mà bạn sử dụng trong Java.

0

Tôi không có kiến ​​thức sâu hơn về những gì tối ưu hóa trình biên dịch thực hiện, nhưng tôi đoán điều này tạo nên sự khác biệt giữa các chương trình của bạn. Microbenchmarks như thế này rất, rất, rất khó để có được đúng và có ý nghĩa. Đây là một bài viết của Brian Goetz mô tả về điều này: http://www.ibm.com/developerworks/java/library/j-jtp02225/index.html

0

Bộ đệm rất lớn có thể chậm hơn. Tôi sẽ đề nghị bạn làm cho kích thước bộ đệm giống nhau. tức là cả 2 hoặc 8 KB

+0

Tôi bắt đầu sử dụng lệnh stdio: BUFSIZ: ~ cùng kết quả – Pierre

+0

Trong C (zlib) bộ đệm lớn không quan trọng chút nào, trong java nó có được vì nó được sao chép nhiều lần. Bạn cũng có thể sử dụng tệp ánh xạ bộ nhớ. FileInputStream của Java là (được?) Tối ưu hóa cho các bộ đệm nhỏ hơn 2K trong Win, 8K - linux, trong trường hợp đó sử dụng stack để cấp phát, nếu không nó là malloc/free (và một số malloc chậm hơn nhiều so với stack), đó là lý do tại sao bộ đệm nhỏ hơn thực hiện tốt hơn. Tôi đã bị treo trong bộ nhớ riêng khi gọi trong đệ quy sâu hơn, SIGSEG kép và quá trình đã chết (lần 2 xảy ra khi cố ghi nhật ký sự cố, do đó không có sự kiện nhật ký sự cố) – bestsss

Các vấn đề liên quan