Mỗi lần tôi đề cập đến hiệu năng chậm của iostreams thư viện chuẩn C ++, tôi lại gặp một làn sóng hoài nghi. Tuy nhiên, tôi có kết quả trình hồ sơ cho thấy số lượng lớn thời gian dành cho mã thư viện iostream (tối ưu hóa trình biên dịch đầy đủ) và chuyển từ iostream sang API I / O dành riêng cho hệ điều hành và quản lý bộ đệm tùy chỉnh giúp cải thiện độ lớn.
Thư viện chuẩn C ++ đang làm thêm công việc gì, có phải là yêu cầu của tiêu chuẩn và nó có hữu ích trong thực tế không? Hoặc một số trình biên dịch cung cấp triển khai các iostream có khả năng cạnh tranh với quản lý bộ đệm thủ công?
Điểm chuẩn
Để giải quyết vấn đề, tôi đã viết một vài chương trình ngắn để thực hiện bộ đệm nội bộ của iostreams:
- đưa dữ liệu nhị phân vào
ostringstream
http://ideone.com/2PPYw - đưa dữ liệu nhị phân vào
char[]
bộ đệm http://ideone.com/Ni5ct - đưa dữ liệu nhị phân vào
vector<char>
sử dụngback_inserter
http://ideone.com/Mj2Fi - MỚI :
vector<char>
trình lặp đơn giản http://ideone.com/9iitv - MỚI : đưa dữ liệu nhị phân trực tiếp vào
stringbuf
http://ideone.com/qc9QA - MỚI :
vector<char>
kiểm tra lặp đơn giản cộng với giới hạn kiểm tra http://ideone.com/YyrKy
Lưu ý rằng ostringstream
và stringbuf
các phiên bản chạy ít lần lặp hơn vì chúng chậm hơn rất nhiều.
Trên ideone, ostringstream
tốc độ chậm hơn khoảng 3 lần so với std:copy
+ back_inserter
+ std::vector
và chậm hơn khoảng 15 lần so với memcpy
vào bộ đệm thô. Điều này cảm thấy phù hợp với hồ sơ trước và sau khi tôi chuyển ứng dụng thực sự của mình sang bộ đệm tùy chỉnh.
Đây đều là những bộ đệm trong bộ nhớ, do đó, sự chậm chạp của iostreams không thể đổ lỗi cho I / O đĩa chậm, quá nhiều lỗi, đồng bộ hóa với stdio hoặc bất kỳ điều gì khác mà mọi người sử dụng để bào chữa cho sự chậm chạp của thư viện chuẩn C ++ iostream.
Sẽ thật tuyệt khi thấy điểm chuẩn trên các hệ thống khác và bình luận về những việc triển khai phổ biến (như libc ++ của gcc, Visual C ++, Intel C ++) và bao nhiêu chi phí bắt buộc theo tiêu chuẩn.
Lý do cho bài kiểm tra này
Một số người đã chỉ ra một cách chính xác rằng iostreams thường được sử dụng cho đầu ra được định dạng. Tuy nhiên, chúng cũng là API hiện đại duy nhất được cung cấp theo tiêu chuẩn C ++ để truy cập tệp nhị phân. Nhưng lý do thực sự để thực hiện các bài kiểm tra hiệu năng trên bộ đệm nội bộ áp dụng cho I / O được định dạng điển hình: nếu iostreams không thể giữ bộ điều khiển đĩa được cung cấp dữ liệu thô, làm sao chúng có thể theo kịp khi chúng cũng chịu trách nhiệm định dạng?
Thời gian chuẩn
Tất cả những điều này là mỗi lần lặp của k
vòng lặp ngoài ( ).
Trên ideone (gcc-4.3.4, hệ điều hành và phần cứng không xác định):
ostringstream
: 53 mili giâystringbuf
: 27 msvector<char>
vàback_inserter
: 17,6 msvector<char>
với trình vòng lặp thông thường: 10,6 msvector<char>
kiểm tra vòng lặp và giới hạn: 11,4 mschar[]
: 3,7 ms
Trên máy tính xách tay của tôi (Visual C ++ 2010 x86, cl /Ox /EHsc
Windows 7 Ultimate 64-bit, Intel Core i7, RAM 8 GB):
ostringstream
: 73,4 mili giây, 71,6 msstringbuf
: 21,7 ms, 21,3 msvector<char>
vàback_inserter
: 34,6 ms, 34,4 msvector<char>
với trình vòng lặp thông thường: 1,10 ms, 1,04 msvector<char>
Kiểm tra vòng lặp và giới hạn: 1,11 ms, 0,87 ms, 1,12 ms, 0,89 ms, 1,02 ms, 1,14 mschar[]
: 1,48 ms, 1,57 ms
Visual C ++ 2010 x86, với sơ-Guided Tối ưu hóa cl /Ox /EHsc /GL /c
, link /ltcg:pgi
, chạy, link /ltcg:pgo
, biện pháp:
ostringstream
: 61,2 ms, 60,5 msvector<char>
với trình vòng lặp thông thường: 1,04 ms, 1,03 ms
Cùng một máy tính xách tay, cùng một hệ điều hành, sử dụng cygwin gcc 4.3.4 g++ -O3
:
ostringstream
: 62,7 ms, 60,5 msstringbuf
: 44,4 ms, 44,5 msvector<char>
vàback_inserter
: 13,5 ms, 13,6 msvector<char>
với trình vòng lặp thông thường: 4,1 ms, 3,9 msvector<char>
Kiểm tra vòng lặp và giới hạn: 4.0 ms, 4.0 mschar[]
: 3,57 ms, 3,75 ms
Cùng một máy tính xách tay, Visual C ++ 2008 SP1 , cl /Ox /EHsc
:
ostringstream
: 88,7 ms, 87,6 msstringbuf
: 23,3 ms, 23,4 msvector<char>
vàback_inserter
: 26,1 ms, 24,5 msvector<char>
với trình vòng lặp thông thường: 3,13 ms, 2,48 msvector<char>
Kiểm tra vòng lặp và giới hạn: 2,97 ms, 2,53 mschar[]
: 1,52 ms, 1,25 ms
Cùng một máy tính xách tay, trình biên dịch 64 bit Visual C ++ 2010:
ostringstream
: 48,6 ms, 45,0 msstringbuf
: 16,2 ms, 16,0 msvector<char>
vàback_inserter
: 26,3 ms, 26,5 msvector<char>
với trình vòng lặp thông thường: 0,87 ms, 0,89 msvector<char>
Kiểm tra vòng lặp và giới hạn: 0,99 ms, 0,99 mschar[]
: 1,25 ms, 1,24 ms
EDIT: Ran tất cả hai lần để xem kết quả phù hợp như thế nào. IMO khá nhất quán.
LƯU Ý: Trên máy tính xách tay của tôi, vì tôi có thể dành nhiều thời gian CPU hơn ideone cho phép, tôi đặt số lần lặp là 1000 cho tất cả các phương thức. Điều này có nghĩa là ostringstream
và vector
việc tái phân bổ, chỉ diễn ra ở lần đầu tiên, sẽ ít ảnh hưởng đến kết quả cuối cùng.
EDIT: Rất tiếc, đã tìm thấy một lỗi trong vector
-with-normal-iterator, iterator không được nâng cao và do đó có quá nhiều lần truy cập bộ đệm. Tôi đã tự hỏi làm thế nào vector<char>
là tốt hơn char[]
. Mặc dù vậy, nó không tạo ra nhiều khác biệt, vector<char>
vẫn nhanh hơn so với char[]
VC ++ 2010.
Kết luận
Việc đệm các luồng đầu ra đòi hỏi ba bước mỗi lần dữ liệu được thêm vào:
- Kiểm tra xem khối đến phù hợp với không gian bộ đệm có sẵn.
- Sao chép khối đến.
- Cập nhật con trỏ cuối dữ liệu.
Đoạn mã mới nhất tôi đã đăng, " vector<char>
kiểm tra đơn giản cộng với kiểm tra giới hạn" không chỉ thực hiện điều này, nó còn phân bổ không gian bổ sung và di chuyển dữ liệu hiện có khi khối đến không phù hợp. Như Clifford đã chỉ ra, việc đệm trong một lớp I / O tệp sẽ không phải làm điều đó, nó sẽ chỉ xóa bộ đệm hiện tại và sử dụng lại nó. Vì vậy, đây phải là một giới hạn trên về chi phí đầu ra của bộ đệm. Và đó chính xác là những gì cần thiết để tạo bộ đệm trong bộ nhớ hoạt động.
Vậy tại sao stringbuf
chậm hơn 2,5 lần trên ideone và chậm hơn ít nhất 10 lần khi tôi kiểm tra nó? Nó không được sử dụng đa hình trong tiêu chuẩn vi mô đơn giản này, do đó không giải thích được.
std::ostringstream
không đủ thông minh để tăng kích thước bộ đệm theo cấp số nhân std::vector
, thì đó là (A) ngu ngốc và (B) một cái gì đó mọi người nghĩ về hiệu suất I / O nên nghĩ về. Dù sao, bộ đệm được sử dụng lại, nó không được phân bổ lại mỗi lần. Và std::vector
cũng đang sử dụng một bộ đệm tăng trưởng năng động. Tôi đang cố gắng công bằng ở đây.
ostringstream
và bạn muốn hiệu suất càng nhanh càng tốt thì bạn nên xem xét việc đi thẳng stringbuf
. Các ostream
lớp được cho là liên kết chức năng định dạng nhận thức cục bộ với sự lựa chọn bộ đệm linh hoạt (tệp, chuỗi, v.v.) thông qua rdbuf()
giao diện chức năng ảo của nó. Nếu bạn không thực hiện bất kỳ định dạng nào thì mức độ gián tiếp bổ sung đó chắc chắn sẽ trông đắt đỏ tương đối so với các phương pháp khác.
ofstream
để fprintf
khi xuất ra thông tin đăng nhập liên quan đến đôi. MSVC 2008 trên WinXPsp3. iostreams chỉ là con chó chậm.