c++で行列をバイト列で保存&復帰

巨大な行列を沢山のファイルに保存するととんでもないサイズになるのでどうにかしてディスク容量を節約できないかと考えた結果、行列をバイナリで保存と読み込みをするテンプレートクラスを書けばいいやと考えた。

どの程度ポータブルなのかちょっとわからないので注意して使う。

/*
 * iomatrix.hpp
 * 2次元配列のバイナリ形式での保存と読み込み
 * 
 * double matrix[m][n];
 * MatrixReader<double, m, n> raeder("mat.bin"); // MxNサイズの行列リーダ
 * reader.read(matrix); // matrixがファイルの内容で上書きされる
 * 
 * MatrixWriter<double, m, n> writer("result.bin");
 * writer.write(matrix);
 * 
 */
//
// データ長の違うプラットフォーム間では動かないよなあ
// エンディアンの違いを吸収しなければならない?
// どの程度可搬性があるのかわからない
//

#ifndef IO_MATRIX_HPP
#define IO_MATRIX_HPP

#include<iostream>
#include<string>
using namespace std;

template<class type, int m, int n> class MatrixWriter {
    string fname;
public:
    MatrixWriter (string name): fname(name){}
    ~MatrixWriter (){}
    void write (type p[m][n])
    {
        FILE *fp = fopen(fname.c_str(), "wb");
        if (!fp){
            perror(fname.c_str());
            return;
        }
        for (int i = 0; i < m; i++)
            fwrite(p[i], sizeof(type), n, fp);
        fclose(fp);
    };
};

template<class type, int m, int n> class MatrixReader {
    string fname;
public:
    MatrixReader(string name): fname(name){}
    ~MatrixReader(){}
    void read(type p[m][n])
    {
        FILE *fp = fopen(fname.c_str(), "rb");
        if (!fp){
            perror(fname.c_str());
            return;
        }
        for (int i = 0; i < m; i++)
            fread(p[i], sizeof(type), n, fp);
        fclose(fp);
    }
};

#endif /* IO_MATRIX_HPP */

実行

九九表をバイナリで保存&読み込み
// 九九表をバイナリで保存&読み込み
#include<iostream>
#include"iomatrix.hpp"
using namespace std;

void dump(int m[9][9])
{
    for (int i = 0; i < 9; i++){
        for (int j = 0; j < 9; j++)
            printf("%2d ", m[i][j]);
        printf("\n");
    }
    printf("\n");
}

int main()
{
    // write
    int a[9][9];
    for (int i = 0; i < 9; i++)
        for (int j = 0; j < 9; j++)
            a[i][j] = (i+1)*(j+1);
    dump(a);

    MatrixWriter<int, 9, 9> writer("out.bin");
    writer.write(a);

    // read
    int b[9][9];
    MatrixReader<int, 9, 9> reader("out.bin");
    reader.read(b);
    dump(b);
    return 0;
}
実行結果
 1  2  3  4  5  6  7  8  9 
 2  4  6  8 10 12 14 16 18 
 3  6  9 12 15 18 21 24 27 
 4  8 12 16 20 24 28 32 36 
 5 10 15 20 25 30 35 40 45 
 6 12 18 24 30 36 42 48 54 
 7 14 21 28 35 42 49 56 63 
 8 16 24 32 40 48 56 64 72 
 9 18 27 36 45 54 63 72 81 

 1  2  3  4  5  6  7  8  9 
 2  4  6  8 10 12 14 16 18 
 3  6  9 12 15 18 21 24 27 
 4  8 12 16 20 24 28 32 36 
 5 10 15 20 25 30 35 40 45 
 6 12 18 24 30 36 42 48 54 
 7 14 21 28 35 42 49 56 63 
 8 16 24 32 40 48 56 64 72 
 9 18 27 36 45 54 63 72 81 

double型の行列をバイナリ形式で保存するのとテキストファイルで保存するのと比較してみる

#include<iostream>
#include"iomatrix.hpp"
using namespace std;

double urand(){ return random() / (double)RAND_MAX; }
double xrand()
{
    return 1e5 - 2e5*urand();
}

const int m = 2048;
const int n = 4096;
double mat[m][n];

int main()
{
    srandom(time(NULL));

    for (int i = 0; i < m; i++)
        for (int  j = 0; j < n; j++)
            mat[i][j] = xrand();
    printf("init complete\n");

    // バイナリファイルで書き出し
    MatrixWriter<double, m,n> writer("mat.bin");
    writer.write(mat);
    // テキストファイルで書き出し
    FILE *fp = fopen("mat.txt", "w");
    for (int i = 0; i < m; i++){
        for (int j = 0; j < n; j++){
            fprintf(fp, "%lf ", mat[i][j]);
        }
        fprintf(fp, "\n");
    }
}
ファイルサイズ比較

バイナリファイルとテキストファイルをそれぞれtar.bz2とtar.gzとzipで圧縮してサイズを比較してみた

$ du -h mat.bin mat.txt
65M	mat.bin
108M	mat.txt
$ tar cvjf mat.bin.tar.bz2 mat.bin
$ tar cvjf mat.txt.tar.bz2 mat.txt
$ tar cvzf mat.bin.tar.gz mat.bin
$ tar cvzf mat.txt.tar.gz mat.txt
$ zip mat.bin.zip mat.bin
$ zip mat.txt.zip mat.txt
$ du -h mat.bin mat.bin.tar.bz2 mat.bin.tar.gz mat.bin.zip mat.txt mat.txt.tar.bz2 mat.txt.tar.gz mat.txt.zip
65M	mat.bin
63M	mat.bin.tar.bz2
62M	mat.bin.tar.gz
62M	mat.bin.zip
108M	mat.txt
47M	mat.txt.tar.bz2
53M	mat.txt.tar.gz
53M	mat.txt.zip

バイナリファイルにしたら圧縮かけたときにほとんど小さくならなくなってしまった・・・

R/W速度

測るのめんどくさい。fscanfとfread、fprintf,fwrite位違う速度。行列が大きいと明らかにバイナリR/Wが早い。

シリアライズ

3次元ベクトル構造体の行列のシリアライズとかできるんでね?と思ったので試したみた

#include<stdio.h>
#include<stdlib.h>
#include<time.h>
#include"binaryMatrixIO.hpp"

float urand()
{
    return rand() / (float)RAND_MAX;
}

typedef struct vector{
    float x, y, z;
} vec3;

void dump(vec3 v[3][3])
{
    for (int i = 0; i < 3; i++){
        for (int j = 0; j < 3; j++){
            vec3 &p = v[i][j];
            printf("%f %f %f\n", p.x, p.y, p.z);
        }
    }
    printf("\n");
}

int main()
{
    vec3 u[3][3];
    vec3 v[3][3];
    
    BinaryMatrixWriter<vec3,3,3> writer("out.bin");
    BinaryMatrixReader<vec3,3,3> reader("out.bin");
    
    // 乱数で初期化
    srand(time(NULL));
    for (int i = 0; i < 3; i++)
        for (int j = 0; j < 3; j++){
            vec3 &p = u[i][j];
            p.x = urand();
            p.y = urand();
            p.z = urand();
        }
    dump(u);
    // 書き出し
    writer.write(u);
    
    // 読み込み
    reader.read(v);
    dump(v);
    return 0;
}
出力
0.142629 0.618720 0.695503
0.284359 0.001272 0.300783
0.903097 0.981465 0.468198
0.532429 0.858546 0.180134
0.643004 0.195832 0.974500
0.247794 0.828028 0.904411
0.261974 0.088152 0.708580
0.295095 0.503187 0.353356
0.666642 0.023045 0.532530

0.142629 0.618720 0.695503
0.284359 0.001272 0.300783
0.903097 0.981465 0.468198
0.532429 0.858546 0.180134
0.643004 0.195832 0.974500
0.247794 0.828028 0.904411
0.261974 0.088152 0.708580
0.295095 0.503187 0.353356
0.666642 0.023045 0.532530

できたー。