C++ で getline と Boost.Spirit を使って CSV ファイルを読み込む

Posted Fri Apr 18 2014

先日 C++ で整数データが約 2900 万要素ある CSV ファイルを読み込むプログラムを書く必要がありました.その時に書いたコードを貼っておきます.getline と Boost.Spirit を使った2種類のプログラムを試しています.固定長の配列に CSV ファイルの要素を読み込んでいます.

#include <fstream>
#include <string>

#include <boost/timer/timer.hpp>

#include <boost/spirit/include/qi.hpp>
#include <boost/spirit/home/phoenix/object/construct.hpp>
#include <boost/spirit/home/phoenix/container.hpp>
#include <boost/spirit/include/phoenix_core.hpp>

namespace qi = boost::spirit::qi;
namespace ascii = boost::spirit::ascii;
namespace phoenix = boost::phoenix;

template <typename Iterator>
bool parse_csv(Iterator first, Iterator last, std::vector<int>& vec)
{
  using qi::int_;
  using qi::phrase_parse;
  using qi::_1;
  using ascii::space;
  using phoenix::push_back;

  bool r = phrase_parse(first, last,
      // Begin grammar
      (
        int_[push_back(phoenix::ref(vec), _1)] % ','
      )
      ,
      // End grammar
      space);

  if (first != last)
    return false;
  return r;
}

bool read_csv_getline(const char* filename, unsigned short* buf)
{
  std::ifstream ifs(filename);

  std::string line;
  unsigned int index = 0;
  while (std::getline(ifs, line)) {
    std::stringstream ss(line);
    std::string value;
    while (std::getline(ss, value, ',')) {
      buf[index] = atoi(value.c_str());
      index++;
    }
  }
  return true;
}

bool read_csv_spirit(const char* filename, unsigned short* buf)
{
  std::ifstream ifs(filename);

  std::string line;
  unsigned int index = 0;
  while (std::getline(ifs, line)) {
    std::vector<int> read_buf;
    if (parse_csv(line.begin(), line.end(), read_buf)) {
      for (int i = 0; i < read_buf.size(); i++) {
        buf[index] = read_buf[i];
        index++;
      }
    } else {
      return false;
    }
  }
  return true;
}

int main() {

  unsigned short* buf = new unsigned short[28829184];

  boost::timer::cpu_timer t1;
  read_csv_getline("data.csv", buf);
  std::cout << t1.elapsed().wall / 1000000.0 << std::endl;

  boost::timer::cpu_timer t2;
  read_csv_spirit("data.csv", buf);
  std::cout << t2.elapsed().wall / 1000000.0 << std::endl;

  delete[] buf;

  return 0;
}

PC にインストールしてある下記の環境で試しに処理時間を計ってみました.

  • Windows 7 Professional 64 bit
  • Core i7 860 2.80GHz
  • 4.00 GB

Visual C++ 2012 Express + boost 1.55

std::getline:  4568.91 [msec]
boost::spirit: 2734.77 [msec]

Visual C++ 2008 Professional + boost 1.54

std::getline:  7601.67 [msec]
boost::spirit: 4396.55 [msec]

いずれも getline の方が spirit より 1.6-1.7 倍程度処理時間がかかるという結果になりました.