2012-01-26 26 views
10

escribí rápidamente un programa de C extracción de la línea i-ésimo de un conjunto de gzipped archivos (que contienen aproximadamente 500.000 líneas). Aquí está mi programa C:C más lento que Java: ¿por qué?

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

Como prueba, he escrito el siguiente código equivalente en 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; 
    } 
} 

Sucede que el programa Java fue mucho más rápido (~ 1'45 '') para obtener un índice grande que el programa C (~ 2'15 '') en la misma máquina (realicé esa prueba varias veces).

¿Cómo puedo explicar esa diferencia?

+2

Nota: Los buffersizes no son iguales por lo tanto, los programas no hacen lo mismo "exacta". –

+0

@SaniHuttunen - el código no es equivalente por más razones que eso :) – Perception

+0

@Perception: Es cierto, pero esa fue mi primera observación y me pareció suficiente para señalar que los programas no son iguales. –

Respuesta

22

La explicación más probable para que la versión de Java sea más rápida que la versión C es que la versión C es incorrecta.

Después de fijar la versión C, I obtuvieron los siguientes resultados (en contradicción con su afirmación de que Java es más rápido que 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 

La tarea consistía en imprimir la línea 200000-ésimo de archivo words.gz. El archivo words.gz fue generado por 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

¿Qué cambiaste en la versión C, por favor? – Pierre

+0

+1 para la investigación –

+0

¡Gracias! la primera vez que escribí mi código C, utilicé gzgets en lugar de gzread pero no cambié la prueba en el bucle sobre el búfer. – Pierre

15

Porque fputc() no es muy rápido y está agregando stuf char-by-char en su archivo de salida.

llamando a fputc_unlocked o más bien delimitando las cosas que desea agregar y llamar a fwrite() debe ser más rápido.

+0

Tu respuesta es incorrecta. El autor de la pregunta no especificó la longitud promedio de una línea en sus archivos GZIP. –

+0

'fputc()' solo se usa para una sola línea después de omitir una gran cantidad de líneas supuestamente similares. No es el * lazo interno * que deberíamos estar buscando. El gran buffer automático es un mejor candidato. Hacerlo del mismo tamaño que en java (2048) permitiría una comparación justa. – chqrlie

12

Bueno, sus programas están haciendo cosas diferentes. No perfil de su programa, pero al mirar su código Sospecho que esta diferencia:

Para la construcción de la línea, se utiliza este en Java:

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

Y esto en C:

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

Ie está imprimiendo un caracter a la vez para stdout. Que es buffere, de forma predeterminada, pero sospecho que todavía es más lento que el búfer de 500 caracteres que usa en Java.

0

Los buffers muy grandes pueden ser más lentos. Sugeriría que hagas igual el tamaño del búfer. es decir, ambos 2 u 8 KB

+0

Empecé a usar stdio: BUFSIZ: ~ el mismo resultado – Pierre

+0

En C (zlib), el búfer grande no importa en absoluto, en java lo hace ya que se copia varias veces. También puede usar un archivo mapeado en la memoria. El FileInputStream de Java está (¿estaba?) Optimizado para buffers más pequeños 2K en Win, 8K-linux, en ese caso utiliza la pila para asignar, de lo contrario es malloc/free (y algunos malloc son mucho más lentos que stack), por eso el búfer más pequeño funciona mejor. Tuve choques horribles en la memoria nativa al invocar recursiones más profundas, SIGSEG doble y el proceso está muerto (el segundo ocurre al intentar escribir el registro de fallos, por lo tanto no hay un evento de registro de fallos) – bestsss

Cuestiones relacionadas