본문 바로가기

Programming/C, C++

C/C++ 입출력 속도 비교

우리는 C에서 C++로 넘어갈 때 cin과 cout라는 새로운 입출력 방식을 배우게 된다. scanf와 printf와 달리 매번 변수형을 직접 명시해줄 필요도 없어서 굉장히 편하다. 하지만 BOJ 1874 스택 수열 문제를 cin과 cout를 사용해 풀어보자. 신기하게도 Time Limit Exceed가 발생할 것이다. 그 이유는 바로 각 입출력 방식에 소요되는 시간이 다르기 때문이다.

 

이번 포스트에서는 C와 C++에서 주로 사용되는 입출력 함수들의 대략적인 소요 시간과 결론적으로 어떤 언어의, 어떤 함수를 사용해야 하는지에 대해 다뤄보려 한다.

 

* 수정 *

백준 게시판에 입출력 속도의 보다 객관적인 자료가 올라왔다.

위 자료를 참고해도 되고, 일반적인 풀이에 대해서는 아래 방법으로 입출력을 해도 된다.

 

1. 각 언어별 Input Method 비교

 

우선 각 언어별 입력 함수별 10,000,000개의 숫자를 입력받을 때 대략적으로 수행 시간을 계산해보면 다음과 같다.

(해당 자료는 각 언어별 input method 비교를 참고했다.)

 

1~2초 정도면 큰 문제 없지 않나 생각할 수도 있겠지만, 실제로 대부분의 알고리즘 문제를 풀다보면 전체 시간 제한이 2초 이내인 경우가 많기에 입출력을 하는데 저 정도 시간이 드는 것은 큰 문제가 된다. 특히 이러한 문제는 문자열 처리 시에 더 두드러지게 된다.

 

 

2. Char & Int 입출력

 

int형이나 char형의 입출력에 대한 처리는 생각보다 쉽다. BOJ 15552 빠른 A+B 문제를 참고하면, C++의 cin과 cout의 속도를 C와 비슷하도록 가속하기 위해 ios::sync_with_stdio(false);cin.tie(NULL);을 호출해도 된다. 또한 출력 시엔 endl; 대신 "\n"를 사용하면 flush가 불필요하게 호출되지 않아 속도가 많이 줄어든다.

 

하지만 해당 함수를 호출할 때는 몇 가지 제약사항이 따른다. 먼저 해당 함수 선언 뒤에는 scanf나 printf과 같은 C 표준 입출력을 사용할 수 없게 된다. 또한 멀티 스레딩을 할 수 없게 된다. 물론 알고리즘에서는 멀티 스레딩이 사용되지 않지만, 현업에서 코드를 짤 때를 고려할 때는 대부분 위 함수를 사용하는 대신 C 표준 입출력을 사용하는 것을 권고한다.

 

따라서 기본 변수 입출력 시에는 scanf와 printf를 사용하는 것을 추천한다. 

 

 

3. 문자열 입출력

 

문자열 입력에는 여러 방식이 존재한다.

 

1) C++ <iostream> cin

위에서 언급한 이유로 사용하는 것을 지양한다.

 

2) C <stdio.h> scanf

물론 속도는 가장 빠르다. 하지만 scanf만의 단점이라 하면, 입력의 단위를 공백으로 구분한다. 예를 들어 "nice to meet you" 라는 문자열을 입력받으면 scanf는 nice까지 밖에 저장하지 않는다. 그 뒤 남은 문자열의 내용은 버퍼에 그대로 남아 이후에 입력 함수를 호출 시 해당 문자열이 저장되는 등 여러 문제가 발생할 수 있다.

 

3) C++ <string> getline

getline 함수는 한 줄 전체 string형으로 입력받는 함수다.

 

#include <string>

using namespace std;

void main() {
	string str;

	getline(cin, str);
	printf("%s", str.c_str()); // c의 string형으로 변환
}

편하긴 하지만 문자열의 길이가 크면 확실히 속도가 떨어진다. 비슷한 함수로 C++ <ifstream> 멤버 함수 cin.getline()이 있지만 이 또한 속도가 낮게 측정된다.

 

4) C <stdio.h> fgets

결론부터 얘기하자면 제일 빠르다. 사용법과 주의사항은 C의 문자열 처리 함수 포스트를 참고하자.

#include <vector>
#include <string.h>

#define MAXSIZE 100

using namespace std;

int main() {
	vector <char*> input;

	for (int i = 0; i < 3; i++) {
		char* temp = new char[MAXSIZE];
		fgets(temp, sizeof(char)*MAXSIZE, stdin);
		temp[strlen(temp)-1] = NULL;
		input.push_back(temp);
	}

	int len = input.size();
	for (int i = 0; i < len; i++) {
		printf("%s\n", input[i]);
	}

	return 0;
}

물론 fgets는 C++의 string을 지원하지 않아 C의 char* 형을 사용하는 게 불편할 수 있지만, string형 관련 함수는 우리가 직접 구현한 것과 크게 시간 차이가 나지 않을 뿐더러 어려운 로직도 없기에 그냥 char*을 사용하는 것이 낫다.

 

따라서 문자열 입출력 시에는 fgets와 printf를 사용해 char*형을 이용하는 것을 추천한다. 그 외에 입출력과 관련된 문제를 더 풀어보고 싶다면 BOJ 1920 수 찾기 문제를 풀어보자.

 

* 참고 *

그렇더라도 C++의 string형을 이용하고 싶다면 다음과 같이 입력 시 string형으로 캐스팅한 뒤, 출력 시 c_str() 함수를 이용해 C의 char* 형으로 변환하면 된다.

input.push_back(string(temp));
printf("%s\n", input[i].c_str());

혹시 c_str 함수의 변환 과정이 또 시간에 영향을 미치지 않을까 싶어 내부 로직을 뜯어봤다.

살펴보니 단순히 string형의 포인터형을 반환하는 것이었다.

 

공식 문서에도 보니 포인터형을 반환할 뿐이었다. 따라서 c_str 함수는 크게 영향이 미치지 않는 것으로 보인다.

 

 

참고 사이트

C/C++ 입출력 방법에 따른 속도 정리

백준 질문 - 시간초과

자주하는 실수 모음

각 언어별 input method 비교

공백을 포함해 한 줄 입력받기

[C++] 문자열 입력 getline() 과 버퍼오버플로우

I/O처리속도에 대해...

fgets vs ifstream::getline 성능비교

 

'Programming > C, C++' 카테고리의 다른 글