読者です 読者をやめる 読者になる 読者になる

高速行読込クラス

C++ utility speed

ベンチマークを取る時には、対象となる部分以外に掛かる処理時間を極力抑えたい。
標準のファイル入力クラスであるifstreamは、結構便利でそこそこ高速ではあるのだが、大量の行を読み込む場合、少なくはないオーバヘッドが出てしまう。
なので、今回は、ベンチマーク用に行読み込みを(特に同じファイルを繰返し読む場合は、極めて)高速に行うクラス(ReadLineクラス)を作成した。

このクラスのデメリットは、一旦ファイルの中身を全てメモリ上にコピーするので、ファイルサイズ+α*1のメモリを消費するのと、初期化に若干時間を使うことくらい。

以下にifstreamとの比較とクラス定義を載せておく。

比較

まずは、ifstream:

// ifstream.cc
// g++ -O3 -c ifstream.cc
#include <fstream>
#include <string>
#include <sys/time.h>

void dummy_fn(const std::string& s);

inline double gettime(){
  timeval tv;
  gettimeofday(&tv,NULL);
  return static_cast<double>(tv.tv_sec)+static_cast<double>(tv.tv_usec)/1000000.0;
}

int main(int argc, char** argv) {
  if(argc != 3) {
    printf("Usage: %s <filepath> <loop count>\n",argv[0]);
    return 1;
  }

  double beg_t = gettime();
  printf("init: %f\n", gettime()-beg_t);

  beg_t = gettime();
  std::string line;

  for(int i=0; i < atoi(argv[2]); i++){
    std::ifstream in(argv[1]);
    while(getline(in,line))
      dummy_fn(line);
  }
  printf("loop: %f\n", gettime()-beg_t);

  return 0;
}

// dummy1.cc
// g++ -O3 -c dummy1.cc
#include <string>
void dummy_fn(const std::string& s) {}

////
// g++ -oifstream ifstream.o dummy1.o 

実行:

> ./ifstream /path/to/7million_line_file 10
init: 0.000001
loop: 9.220926


次にReadLine:

// read_line.cc
// g++ -O3 -c read_line.cc
#include "read_line.h"
#include <stdio.h>
#include <sys/time.h>

void dummy_fn(const char* s);

inline double gettime(){
  timeval tv;
  gettimeofday(&tv,NULL);
  return static_cast<double>(tv.tv_sec)+static_cast<double>(tv.tv_usec)/1000000.0;
}

int main(int argc, char** argv) {
  if(argc != 3) {
    printf("Usage: %s <filepath> <loop count>\n",argv[0]);
    return 1;
  }

  double beg_t = gettime();
  ReadLine rl(argv[1]);
  if(!rl) {
    printf("Can't open file: %s\n", argv[1]);
    return 1;
  }

  printf("init: %f\n", gettime()-beg_t);

  beg_t = gettime();
  const char* line;
  for(int i=0; i < atoi(argv[2]); i++) {
    rl.reset();
    while(line=rl.read())
      dummy_fn(line);
  }
  printf("loop: %f\n", gettime()-beg_t);
  return 0;
}

// dummy2.cc
// g++ -O3 -c dummy2.cc
void dummy_fn(const char* s){}

////
// g++ -oread_line read_line.o dummy2.o

実行:

> ./read_line /path/to/7million_line_file 10
init: 0.512597
loop: 0.229856

ループ部分に限れば40倍くらいの差がある。

クラス定義 unix用だが、ファイルの中身を読み込むところさえ変更すればWindowsでも使える。

// read_line.h
#ifndef READ_LINE_H
#define READ_LINE_H

#include <vector>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

class ReadLine {
public:
  ReadLine(const char* filepath) : buf(NULL),index(0) {
    int f = open(filepath, O_RDONLY);
    if(f==-1)
      return;
    
    struct stat statbuf;
    fstat(f, &statbuf);

    buf = new char[statbuf.st_size+1];
    ::read(f, buf, statbuf.st_size);
    close(f);

    buf[statbuf.st_size]='\0';

    init(buf,buf+statbuf.st_size-1);
  }
  ~ReadLine() {
    delete [] buf;
  }

  operator bool() const { 
    return buf!=NULL;
  }

  void reset() {
    index = 0;
  }

  const char* read() {
    if(index == lines.size())
      return NULL;

    return lines[index++];
  }

private:
  void init(char* cur,const char* end) {
    lines.push_back(cur);
    for(cur=strchr(cur,'\n'); cur; cur=strchr(cur,'\n')) {
      *cur = '\0';
      cur++;
      if(cur < end)
        lines.push_back(cur);
    }
  }

private:
  char* buf;
  std::size_t index;
  std::vector<const char*> lines;
};

#endif

*1:ファイルサイズ+行数×sizeof(char*)