본문 바로가기

3.구현/C or C++

[C++] 파일 읽기 성능 비교

파일 읽기 성능을 비교해본 자료이다.
테스트 환경은 Windows7 32bit에서 수행하였다.
읽어온 텍스트 파일 크기는 14,682,256 byte이다.

작성: http://ospace.tistory.com/,2011.12.28 (ospace114@empal.com)

결과

일단 결과를 말하면 암담하지만 c++의 std의 ifstream의 성능은 매우 안좋다.

아래가 수행결과 이미지이다.

간단하게 설명을 하면 test_ifstream1에서 test_ifstream2까지가 한 줄의 문자열을 읽어오면서 반복적으로 모든 파일 내용을 읽어왔다. 그리고 test_ifstream3 ~ test_ifstream6까지가 모든 파일을 읽어오는 경우이다.

그리고, test_osfile1과 test_osfile2는 fopen()을 사용하여 읽어오는 경우이다.

test_ifstream1

ifstream에 있는 getline()를 사용하여 읽어 왔다. 직접 배열을 넘겨줘서 처리하였기에 ifstream 중에서 가장 성능이 좋았다.

void test_ifstream1() {
    std::ifstream inFile;
    inFile.open(TEST_FILE);

    if (!inFile.is_open()) exit(1);

    char buf[512];
    while (!inFile.getline(buf, sizeof(buf)).eof()) {

    }
}

test_ifstream2

이번에는 getline()이 ifstream의 메소드가 아니라 string 영역에 있는 std::getline()을 사용하였다. 일부러 string크기도 512로 해놓았는데도 성능은 좋지 않다. string을 사용한 방법이 그다지 좋지 않는다는 슬픈 결과가 나왔다. 즉, test_ifstream1에서 파일을 읽어오고, 다시 string으로 변환해서 처리하는게 성능이 더 좋다.

void test_ifstream2() {
    std::ifstream inFile;
    inFile.open(TEST_FILE);

    if (!inFile.is_open()) exit(1);

    std::string buf;
    buf.reserve(512);
    while (!inFile.eof()) {
        std::getline(inFile, buf);
    }
}

test_ifstream3

이번에는 istreambuf_iterator를 사용하여 모든 파일을 읽어 들였다. 그리고 모든 내용은 vector형태로 저장하였다. 나름 버퍼를 사용하는구나라고 생각이 들어서 성능이 좋겠구나라고 생각했지만, 정반대이다.

void test_ifstream3() {
    std::ifstream inFile;
    inFile.open(TEST_FILE);

    if (!inFile.is_open()) exit(1);

    std::istreambuf_iterator<char> begin(inFile);
    std::istreambuf_iterator<char> end;
    std::vector<char> contents(begin, end);
}

test_ifstream4

test_ifstream3의 개선된 버전으로 vector의 공간을 미리 파일 크기만큼 지정한 방식이다.

당연히 성능은 절반가까이 줄어들었다. 그래도 성능은 암울하다.

void test_ifstream4() {
    std::ifstream inFile;
    inFile.open(TEST_FILE);

    if (!inFile.is_open()) exit(1);

    std::vector<char> contents;
    std::istreambuf_iterator<char> begin(inFile);
    std::istreambuf_iterator<char> end;
    contents.reserve(std::distance(begin, end));
    contents.assign(begin, end);
}

test_ifstream5

혹시나 해서 vector을 사용하기에 성능이 떨어지나해서 string으로 변경한 형태이다. 그러나 결과를 변함이 없다. 좋은 것은 vector에서 string으로 타입만 변환하고 나머지는 변경이 없다는 점이다.

void test_ifstream5() {
    std::ifstream inFile;
    inFile.open(TEST_FILE);

    if (!inFile.is_open()) exit(1);

    std::string contents;
    std::istreambuf_iterator<char> begin(inFile);
    std::istreambuf_iterator<char> end;
    contents.reserve(std::distance(begin, end));
    contents.assign(begin, end);
}

test_ifstream6

이번 예제는 test_ifstream1을 변형한 형태로 한줄씩 읽어오는데 모든 파일 내용을 저장하는 형태이다. 앞의 예는 이전에 불러온 내용은 사라지지만, 이번예제는 모두 보관하도록 수정하였다. 성능은 test_ifstream1보다 조금 늘어났지만, 다른 예보다는 상당히 좋았다.

std::ostringstream& operator << (std::ostringstream& oss, std::ifstream& in) {
    char buf[512];
    in.getline(buf, sizeof(buf));
    oss << buf;
    return oss;
}

void test_ifstream6() {
    std::ifstream inFile;
    inFile.open(TEST_FILE);

    if (!inFile.is_open()) exit(1);

    std::ostringstream oss;
    char buf[512];

    while (!inFile.eof()) {       
        oss << inFile;
    }
}

test_osfile1

fopen()을 이용한 예로서 파일 내용은 fread()을 사용하였다. 앞의 예제와 다른 것은 한꺼번에 512 byte을 읽어오며, 한줄씩 읽어오지는 않는다. 그 이유 때문인지 성능이 가장 좋았다. ifstream을 사용하여 가장 빠른 경우보다 약 7배 이상 성능이 좋았다. 이 경우는 한 줄씩 읽어서 처리할 경우 문자별 매칭을 수행해야하는 번거러움이 생긴다.

void test_osfile1() {
    FILE *fp = fopen(TEST_FILE, "r");

    if (NULL == fp) exit(1);

    char buf[512];

    size_t len = 0;
    while (!feof(fp)) {
        len = fread(buf, sizeof(buf[0]), sizeof(buf)/sizeof(buf[0]), fp);
    }
    fclose(fp);
}

test_osfile2

fopen()을 사용한 예로 fgets()을 사용하여 한 줄씩 읽어오는 예제이다. test_osfile1보다 성능이 안좋지만, ifstream 보다 최소 약 3배 정도 좋다.

void test_osfile2() {
    FILE *fp = fopen(TEST_FILE, "r");

    if (NULL == fp) exit(1);

    char buf[512];
    while (NULL != fgets(buf, sizeof(buf), fp)) {

    }
    fclose(fp);
}

결론

성능으로만 따지자면 ifstream 사용을 권장하지 않는다. 최적화해도 직접 API를 호출하는 것보다 좋을 수는 없지만, 유연성을 위해 너무 과정을 거치면서 너무 많은 성능하락이 커지는 문제점이 생긴다. 이 부분을 얼마나 줄이는지가 관건인데, 아직 멀었다고 본다.
여러 운영체제에 포팅하는 경우 이식성을 높이기 위해서 ifstream을 사용하는 것이 가장 좋은 방안이지만, 성능이 문제라면 다른 공통으로 사용할 수 있는 라이브러리를 찾는게 방안이지만, 이것 또한 안정성과 검증이 안되었기에 도입하는데 쉽지는 않다.
fopen()인 경우 공통 c 라이브러리이기 때문에 대부분 호환 가능하기에 이식성에 어려움은 없다. 그렇기에 fopen() 사용하는 것도 나쁘다고 보지 않는다. 문제는 기존 c++의 stl과 연동이 자연스럽지 않기에 이 부분을 어떻게 풀어가는가가 핵심이라고 볼 수 있다. 즉, 해야할 일들이 많이 생기게 된다.
그러나 성능이 큰 문제가 아니라면 ifstream을 쓰는것도 나쁘지는 않다. ifstream의 getline()을 사용하는 경우 성능은 좀 떨어지지만, 간단한 파일을 처리는데는 문제가 없다고 본다.
이 자료는 참고일뿐, 결정의 본인이 알아서...

모드 즐프~

ospace.

Source code

// test_ifstream.cpp : Defines the entry point for the console application.
// by ospace (2011.12.28)

#include <fstream>
#include <sstream>
#include <iostream>
#include <windows.h>
#include <vector>
#include <cstdio>
#include <string>

#define TEST_FILE "Sample.txt"

void test_ifstream1() {
    std::ifstream inFile;
    inFile.open(TEST_FILE);

    if (!inFile.is_open()) exit(1);

    char buf[512];
    while (!inFile.getline(buf, sizeof(buf)).eof()) {

    }
}

void test_ifstream2() {
    std::ifstream inFile;
    inFile.open(TEST_FILE);

    if (!inFile.is_open()) exit(1);

    std::string buf;
    buf.reserve(512);
    while (!inFile.eof()) {
        std::getline(inFile, buf);
    }
}

void test_ifstream3() {
    std::ifstream inFile;
    inFile.open(TEST_FILE);

    if (!inFile.is_open()) exit(1);

    std::istreambuf_iterator<char> begin(inFile);
    std::istreambuf_iterator<char> end;
    std::vector<char> contents(begin, end);
}

void test_ifstream4() {
    std::ifstream inFile;
    inFile.open(TEST_FILE);

    if (!inFile.is_open()) exit(1);

    std::vector<char> contents;
    std::istreambuf_iterator<char> begin(inFile);
    std::istreambuf_iterator<char> end;
    contents.reserve(std::distance(begin, end));
    contents.assign(begin, end);
}

void test_ifstream5() {
    std::ifstream inFile;
    inFile.open(TEST_FILE);

    if (!inFile.is_open()) exit(1);

    std::vector<char> contents;
    std::istreambuf_iterator<char> begin(inFile);
    std::istreambuf_iterator<char> end;
    contents.reserve(std::distance(begin, end));
    contents.assign(begin, end);
}

std::ostringstream& operator << (std::ostringstream& oss, std::ifstream& in) {
    char buf[512];
    in.getline(buf, sizeof(buf));
    oss << buf;
    return oss;
}

void test_ifstream6() {
    std::ifstream inFile;
    inFile.open(TEST_FILE);

    if (!inFile.is_open()) exit(1);

    std::ostringstream oss;
    char buf[512];

    while (!inFile.eof()) {       
        oss << inFile;
    }
}
void test_osfile1() {
    FILE *fp = fopen(TEST_FILE, "r");

    if (NULL == fp) exit(1);

    char buf[512];

    size_t len = 0;
    while (!feof(fp)) {
        len = fread(buf, sizeof(buf[0]), sizeof(buf)/sizeof(buf[0]), fp);
    }

    fclose(fp);
}

void test_osfile2() {
    FILE *fp = fopen(TEST_FILE, "r");

    if (NULL == fp) exit(1);

    char buf[512];
    while (NULL != fgets(buf, sizeof(buf), fp)) {

    }

    fclose(fp);
}

#define CALL_TIME(f) do {\
    DWORD start , end;\
    start = timeGetTime();\
    f();\
    end = timeGetTime();\
    std::cout << #f << " time: " << end - start << " msec" << std::endl;\
} while (0)

int main(int argc, char* argv[])
{
    CALL_TIME(test_ifstream1);
    CALL_TIME(test_ifstream2);
    CALL_TIME(test_ifstream3);
    CALL_TIME(test_ifstream4);
    CALL_TIME(test_ifstream5);
    CALL_TIME(test_ifstream6);
    CALL_TIME(test_osfile1);
    CALL_TIME(test_osfile2);

    return 0;
}
반응형

'3.구현 > C or C++' 카테고리의 다른 글

[C++0x] 람다식  (0) 2012.07.31
[C++] 콘솔입력(pipe) 성능 비교  (0) 2011.12.29
C++ Delegate 구현원리  (2) 2011.01.17
함수호출 규약  (2) 2011.01.12
Thunk와 용법3 - 인터페이스 프록시  (0) 2011.01.12