C ++ 표준 라이브러리 iostream의 성능 저하에 대해 언급 할 때마다 불신의 물결에 부딪칩니다. 그러나 나는 iostream 라이브러리 코드 (전체 컴파일러 최적화)에 소비 된 많은 시간을 보여주는 프로파일 러 결과를 가지고 있으며 iostream에서 OS 특정 I / O API 및 사용자 정의 버퍼 관리로 전환하면 순서가 크게 향상됩니다.
C ++ 표준 라이브러리는 어떤 추가 작업을 수행하며 표준에 필요하며 실제로 유용합니까? 아니면 일부 컴파일러는 수동 버퍼 관리와 경쟁하는 iostream 구현을 제공합니까?
벤치 마크
문제를 해결하기 위해, 나는 iostreams 내부 버퍼링을 실행하는 몇 가지 짧은 프로그램을 작성했습니다.
- 바이너리 데이터를 http://ideone.com/2PPYw에 넣기
ostringstream
- 바이너리 데이터를
char[]
버퍼에 넣기 http://ideone.com/Ni5ct - http://ideone.com/Mj2Fi를
vector<char>
사용하여 이진 데이터 넣기back_inserter
- 새로운 기능 :
vector<char>
간단한 반복자 http://ideone.com/9iitv - 새로운 기능 : 바이너리 데이터를 http://ideone.com/qc9QA에 직접 삽입
stringbuf
- 새로운 기능 :
vector<char>
간단한 반복자 플러스 경계 확인 http://ideone.com/YyrKy
점을 유의 ostringstream
하고 stringbuf
그들이 너무 느립니다 때문에 버전이 적은 반복을 실행합니다.
이데온에서는 + + ostringstream
보다 약 3 배 느리고 원시 버퍼 보다 약 15 배 느립니다 . 실제 응용 프로그램을 사용자 지정 버퍼링으로 전환했을 때 프로파일 링 전후에 일관성이 있다고 생각합니다.std:copy
back_inserter
std::vector
memcpy
이들은 모두 메모리 내 버퍼이므로 느린 디스크 I / O, 너무 많은 플러시, stdio와의 동기화 또는 사람들이 C ++ 표준 라이브러리의 느려진 관찰을 변명하기 위해 사용하는 다른 것들에서 iostream의 느림을 비난 할 수 없습니다 요오드.
다른 시스템의 벤치 마크와 일반적인 구현이 수행하는 작업 (gcc의 libc ++, Visual C ++, Intel C ++ 등)과 표준에서 요구하는 오버 헤드의 양에 대한 주석을 보는 것이 좋을 것입니다.
이 테스트의 근거
많은 사람들이 iostream이 포맷 된 출력에 더 일반적으로 사용된다고 올바르게 지적했습니다. 그러나 이진 파일 액세스를 위해 C ++ 표준에서 제공하는 유일한 최신 API이기도합니다. 그러나 내부 버퍼링에서 성능 테스트를 수행하는 실제 이유는 일반적인 형식의 I / O에 적용됩니다. iostream이 디스크 컨트롤러에 원시 데이터를 제공 할 수없는 경우 포맷을 담당 할 때 어떻게 유지할 수 있습니까?
벤치 마크 타이밍
이것들은 모두 외부 ( k
) 루프의 반복입니다.
ideone (gcc-4.3.4, 알려지지 않은 OS 및 하드웨어) :
ostringstream
: 53 밀리 초stringbuf
: 27msvector<char>
과back_inserter
: 17.6 MSvector<char>
일반 반복자와 함께 : 10.6 msvector<char>
반복자와 범위 검사 : 11.4mschar[]
: 3.7ms
내 랩톱 (Visual C ++ 2010 x86, cl /Ox /EHsc
Windows 7 Ultimate 64 비트, Intel Core i7, 8GB RAM)에서 :
ostringstream
: 73.4 밀리 초, 71.6msstringbuf
: 21.7ms, 21.3msvector<char>
및back_inserter
: 34.6ms, 34.4msvector<char>
일반 반복자 사용시 : 1.10ms, 1.04msvector<char>
반복자와 경계 검사 : 1.11ms, 0.87ms, 1.12ms, 0.89ms, 1.02ms, 1.14mschar[]
: 1.48ms, 1.57ms
프로파일 활용 최적화와 비주얼 C ++ 2010 86, cl /Ox /EHsc /GL /c
, link /ltcg:pgi
, 실행 link /ltcg:pgo
, 측정 :
ostringstream
: 61.2ms, 60.5msvector<char>
일반 반복기 사용시 : 1.04ms, 1.03ms
cygwin gcc 4.3.4를 사용하는 동일한 노트북, 동일한 OS g++ -O3
:
ostringstream
: 62.7ms, 60.5msstringbuf
: 44.4ms, 44.5msvector<char>
및back_inserter
: 13.5ms, 13.6msvector<char>
일반 반복자와 함께 : 4.1ms, 3.9msvector<char>
반복자와 범위 검사 : 4.0ms, 4.0mschar[]
: 3.57ms, 3.75ms
동일한 랩톱, Visual C ++ 2008 SP1, cl /Ox /EHsc
:
ostringstream
: 88.7ms, 87.6msstringbuf
: 23.3ms, 23.4msvector<char>
및back_inserter
: 26.1 ms, 24.5 msvector<char>
일반 반복기 사용시 : 3.13ms, 2.48msvector<char>
반복자와 범위 검사 : 2.97ms, 2.53mschar[]
: 1.52ms, 1.25ms
동일한 랩톱, Visual C ++ 2010 64 비트 컴파일러 :
ostringstream
: 48.6ms, 45.0msstringbuf
: 16.2ms, 16.0msvector<char>
및back_inserter
: 26.3 ms, 26.5 msvector<char>
일반 반복자 포함 : 0.87ms, 0.89msvector<char>
반복자 및 범위 검사 : 0.99ms, 0.99mschar[]
: 1.25ms, 1.24ms
편집 : 결과가 얼마나 일관성이 있는지 두 번 모두 실행했습니다. 꽤 일관된 IMO.
참고 : 랩탑에서, ideone이 허용하는 것보다 더 많은 CPU 시간을 절약 할 수 있으므로 모든 방법에 대해 반복 횟수를 1000으로 설정했습니다. 이 의미 ostringstream
와 vector
첫 번째 패스에 일어난 재 할당, 최종 결과에 거의 영향이 있어야합니다.
편집 : 죄송합니다. vector
-with-ordinary-iterator 에서 버그를 발견했습니다 . 반복자가 진행되지 않았으므로 캐시 적중이 너무 많습니다. 나는 어떻게 vector<char>
성과 가 좋은지 궁금했다 char[]
. VC ++ 2010 vector<char>
보다 훨씬 빠르지 만 여전히 큰 차이는 없었습니다 char[]
.
결론
출력 스트림 버퍼링에는 데이터가 추가 될 때마다 3 단계가 필요합니다.
- 들어오는 블록이 사용 가능한 버퍼 공간에 맞는지 확인하십시오.
- 들어오는 블록을 복사하십시오.
- 데이터 끝 포인터를 업데이트하십시오.
내가 게시 한 최신 코드 스 니펫 인 " vector<char>
simple iterator plus bounds check"는이를 수행 할뿐만 아니라 추가 공간을 할당하고 들어오는 블록이 맞지 않을 때 기존 데이터를 이동시킵니다. Clifford가 지적했듯이 파일 I / O 클래스의 버퍼링은 그렇게 할 필요가 없으며 현재 버퍼를 플러시하고 재사용합니다. 따라서 이것은 버퍼링 출력 비용의 상한이되어야합니다. 그리고 제대로 작동하는 인 메모리 버퍼를 만드는 데 필요한 것입니다.
그렇다면 왜 stringbuf
iideone에서 2.5 배가 느려지고 테스트 할 때 10 배 이상 느려 집니까? 이 간단한 마이크로 벤치 마크에서는 다형성으로 사용되지 않으므로 설명하지 않습니다.
std::ostringstream
기하 급수적으로 버퍼 크기 길 증가에 스마트 것만으로는 충분하지 않습니다 std::vector
I 생각의 (A) 바보와 (B) 어떤 사람들은 / O 성능에 대해 생각해야하지가. 어쨌든 버퍼는 재사용되며 매번 재 할당되지는 않습니다. 또한 std::vector
동적으로 증가하는 버퍼를 사용하고 있습니다. 나는 여기서 공정하게 노력하고 있습니다.
ostringstream
최대한 빠른 성능을 원한다면로 이동하는 것이 stringbuf
좋습니다. ostream
클래스를 통해 유연한 버퍼의 선택 (파일, 캐릭터 등)와 함께 로케일 포맷인지 기능을 묶어 생각되어 rdbuf()
그 가상 함수 인터페이스. 서식을 지정하지 않으면 추가 접근 수준이 다른 접근 방식에 비해 비례 적으로 비싸게 보일 것입니다.
ofstream
할 fprintf
때로 이동하여 질서 또는 규모가 빨라졌습니다 . WinXPsp3의 MSVC 2008. iostreams는 개가 느립니다.