X10ファイルIOのベンチマーク


今回はIBM社の並列分散言語であるX10 (http://x10-lang.org/) を使って各種ファイルIO方法を比較して見た。X10を知っている人に対しては当たり前の結果が出ていると思うが、一応地味にやって見たことをポスティングする。

X10は今2.5.4まで出ているらしいが、今回はScaleGraphプロジェクト(http://scalegraph.sourceforge.net/web/) のSX10 2.3.1を使ってやって見た。

今回このバージョンを選んだ理由としては、恥ずかしながらSX10 2.3.1が出た時からやっていた研究がまだあまり進んでいなく、まだSX10 2.3.1を使って研究をしているから。

ベンチマークのやり方

SNAPから拾って来た論文参照関係ネットワークデータ(#Nodes=317,080 / #Edges=85,702,474)において、ファイルIOをテストする。

Read: FileReader VS BufferedReader VS C++ fstream called from x10
Write: Printer VS FileWriter VS C++ ofstream called from x10

C++関数の呼び出しは、以下のブログ文章を参照に作成

x10からC++の関数を呼ぶ
ただ、この記事にはC++関数へ渡す引数として、構造体や配列が扱えるのか、まだX10側でC++関数の結果が参照できるのかは不明だと言っていたが、SX10のソースコードを見ると可能であることがわかった。具体的なやり方は私の他の文章で共有する。

ソースコード

Graph.hpp
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
#include <fstream>
#include <iomanip>
#include <vector>
#include <map>

using namespace x10::lang;
using namespace x10::io;
using namespace x10aux;
using namespace std;

void writeFile(int size, int* data1);
void readGraphCPP1(const char* file, int maxNode);

Graph.cpp
#include "mycpp.hpp"

void writeFile(int size, int* data1) {
  char * filename = "test.bin";  
  ofstream foutput;
  foutput.open(filename, fstream::out | fstream::binary);
  for (int i = 0; i < size; ++i) {
    foutput.write((char *)(&data1[i]),4);
  }
  foutput.close();
}

void readGraphCPP1(const char* file, int maxNode) {
    ifstream finput;
    finput.open(file, fstream::in);
    vector<vector<pair<int, double> > > links(maxNode+1);

    int nb_links = 0;
    double weight = 1.0;
    while (!finput.eof()) {
        unsigned int src, dst;
        finput >> src >> dst;
        links[src].push_back(make_pair(dst, weight));
        nb_links++;
    }
}
BenchInputMain.x10

import x10.io.*;
import x10.compiler.*;
import x10.util.*;
import x10.array.Array;

@NativeCPPInclude("mycpp.hpp")
@NativeCPPCompilationUnit("mycpp.cpp")
public class Test {
    private var infile:String     = null;
    private var maxNode:int   = 0;

    public def parse_args(args:Array[String]) {
        for (var i:int=0; i<args.size; i++) {   
            if (args(i).equals("-n")) {
                maxNode = Int.parse(args(i+1));
            }
            if (args(i).equals("-i")) {
                infile = args(i+1);
            }
            if (args(i).equals("-o")) {
                outfile = args(i+1);    
            }
        }
    }

    @Native("c++", "readGraphCPP1((#1)->c_str(), #2);")
    native static def readGraphCPP1(infile: String, maxNode: int): void;

    public def readGraphCPP(){
        readGraphCPP1(infile, maxNode);
    }

    public def readGraphX10() {
        val finput = new File(infile);
        val links_r = new Array[ArrayList[Pair[int, double]]](maxNode+1);
        for (var startIndex :int=0; startIndex<=maxNode; startIndex++) {
            links_r(startIndex) = new ArrayList[Pair[int, double]]();
        }

        var nb_links: int = 0;
        val weight = 1.0;
        for (line in finput.lines()) {
            val src  = Int.parse(line.split(" ")(0));
            val dest = Int.parse(line.split(" ")(1));

            links_r(src).add(new Pair(dest as int, weight as double));
            nb_links++;
        }
    }

    public def readGraphBuffX10() {
        val finput = new File(infile);
        val reader = new BufferedReader(new FileReader(finput));

        val links_r = new Array[ArrayList[Pair[int, double]]](maxNode+1);
        for (var startIndex :int=0; startIndex<=maxNode; startIndex++) {
            links_r(startIndex) = new ArrayList[Pair[int, double]]();
        }

        var nb_links: int = 0;
        val weight = 1.0;
        for (line in reader.lines()) {
            val src  = Int.parse(line.split(" ")(0));
            val dest = Int.parse(line.split(" ")(1));

            links_r(src).add(new Pair(dest as int, weight as double));
            nb_links++;
        }
    }

    public static def main(args: Array[String]) {
        val test = new Test();
        test.parse_args(args);

        Console.OUT.println("Test read file");
        for (i in 0..4) {
            val t0 = Timer.milliTime();
            test.readGraphX10();
            Console.OUT.println("Read Graph Time (x10, FileReader): " + (Timer.milliTime()-t0) + "ms");

            val t1 = Timer.milliTime();
            test.readGraphBuffX10();
            Console.OUT.println("Read Graph Time (x10, BufferedReader): " + (Timer.milliTime()-t1) + "ms");

            val t2 = Timer.milliTime();
            test.readGraphCPP();
            Console.OUT.println("Read Graph Time (C++): " + (Timer.milliTime()-t2) + "ms");
        }
    }
}

BenchOutputMain.x10
import x10.io.*;
import x10.compiler.*;
import x10.util.*;
import x10.array.Array;

@NativeCPPInclude("mycpp.hpp")
@NativeCPPCompilationUnit("mycpp.cpp")
public class Test {
    @Native("c++", "writeFile(#1, (#2)->raw()->raw());")
    native static def writeFile(size: int, data: Array[int]): void;

    public def writeFileWriter(data: Array[int]) {
        val filename = "FileWriter.bin";
        val w = new x10.io.FileWriter(new File(filename));
        for (i in data) {
            w.writeInt(data(i));
        }
        w.close();
    }

    public def writePrinter(data: Array[int]) {
        val filename = "Printer.bin";
        val f = new File(filename);
        val printer = f.printer();
        for (i in data) {
            printer.writeInt(data(i));
        }
        printer.flush();
        printer.close();   
    }

    public static def main(args: Array[String]) {
        Console.OUT.println("Test write file!!");
        val test = new Test();
        val size = 100000000;        
        val data = new Array[int](size, (i: int)=>Random.nextInt());

        for (i in 0..4) {
            val t0 = Timer.milliTime();
            test.writeFileWriter(data);
            Console.OUT.println("Write (X10, FileWriter): " + (Timer.milliTime()-t0) + "ms");

            val t1 = Timer.milliTime();
            test.writePrinter(data);
            Console.OUT.println("Write (x10), Printer: " + (Timer.milliTime()-t1) + "ms");

            val t2 = Timer.milliTime();
            test.writeFile(size, data);
            Console.OUT.println("Write (C++): " + (Timer.milliTime()-t2) + "ms");
        }
    }
}

結果

Test read file!!
Read Graph Time (x10, FileReader): 13854ms
Read Graph Time (x10, BufferedReader): 13620ms
Read Graph Time (C++): 576ms

Read Graph Time (x10, FileReader): 13872ms
Read Graph Time (x10, BufferedReader): 13157ms
Read Graph Time (C++): 465ms

Read Graph Time (x10, FileReader): 13891ms
Read Graph Time (x10, BufferedReader): 13133ms
Read Graph Time (C++): 461ms

Read Graph Time (x10, FileReader): 13930ms
Read Graph Time (x10, BufferedReader): 12974ms
Read Graph Time (C++): 448ms

Read Graph Time (x10, FileReader): 13854ms
Read Graph Time (x10, BufferedReader): 13254ms
Read Graph Time (C++): 508ms
Test write file!!
Write (X10, FileWriter): 25057ms
Write (x10), Printer: 27068ms
Write (C++): 3914ms

Write (X10, FileWriter): 24732ms
Write (x10), Printer: 27039ms
Write (C++): 3818ms

Write (X10, FileWriter): 24905ms
Write (x10), Printer: 27558ms
Write (C++): 3816ms

Write (X10, FileWriter): 24817ms
Write (x10), Printer: 27008ms
Write (C++): 3625ms

Write (X10, FileWriter): 24616ms
Write (x10), Printer: 27984ms
Write (C++): 3533ms

ファイル読み込みにおいて、C++より20倍以上に遅く、ファイル書き込みでは、C++より7倍ぐらい遅くなっている。