Người tham gia các cuộc thi lập trình thường gặp thách thức khi dữ liệu đầu vào rất lớn, và việc đọc dữ liệu từ stdin có thể trở thành điểm nghẽn hiệu năng. Các vấn đề như vậy thường đi kèm cảnh báo "Warning: large I/O data". Hãy tạo một tệp thử nghiệm chứa một dòng 16 byte và ký tự xuống dòng, với tổng cộng 1.000.000 dòng, tạo ra tệp 17MB để thử nghiệm.
// Tạo tệp giả kích thước 17MB để so sánh // hiệu năng của scanf() và cin() $ yes 1234567890123456 | head -1000000 > tmp/dummy_input
Hãy so sánh thời gian đọc tệp từ stdin (sử dụng redirection để lấy tệp từ đĩa vào stdin) bằng cách sử dụng `scanf()` versus `cin`.
// Tên tệp : cin_demo.cpp để kiểm tra
// Chúng ta chuyển hướng tệp tạm đã tạo
// có kích thước 17MB vào stdin khi chương trình
// được thực thi.
#include<iostream>
using namespace std;
int main()
{
char nhap[256];
while (cin >> nhap)
{
}
return 0;
}
Kết quả khi tệp giả được chuyển hướng vào stdin:
$ g++ cin_demo.cpp -o cin_demo $ time ./cin_demo < /tmp/dummy_input real 0m2.162s user 0m1.696s sys 0m0.332s
// Tên tệp : scanf_demo.c để kiểm tra
// hiệu năng của scanf()
// Chúng ta chuyển hướng tệp tạm đã tạo
// có kích thước 17MB vào stdin khi chương trình
// được thực thi.
#include<cstdlib>
#include<cstdio>
int main()
{
char nhap[256];
while (scanf("%s", nhap) != EOF)
{
}
return 0;
}
Kết quả khi tệp giả được chuyển hướng vào stdin:
$ g++ scanf_demo.c -o scanf_demo $ time ./scanf_demo < /tmp/dummy_input real 0m0.426s user 0m0.248s sys 0m0.084s
Kết quả trên nhất quán với quan sát của chúng ta. **Tại sao scanf lại nhanh hơn cin?** Ở mức độ cao, cả hai đều là các wrapper cho lời gọi hệ thống `read()`, chỉ khác nhau về mặt cú pháp. Sự khác biệt duy nhất có thể thấy là `scanf()` yêu cầu khai báo kiểu đầu vào rõ ràng, trong khi `cin` sử dụng template cho toán tử chuyển hướng. Điều này dường như không đủ để giải thích sự chậm 5 lần về hiệu năng. **Thực tế là `iostream` sử dụng cơ chế đệm của `stdio`.** Vì vậy, **`cin` tốn thời gian để đồng bộ hóa với bộ đệm stdio của thư viện C cơ bản, để các lời gọi đến cả `scanf()` và `cin` có thể xen kẽ.** May mắn là libstdc++ cung cấp tùy chọn tắt đồng bộ hóa tất cả luồng iostream tiêu chuẩn với các luồng C tiêu chuẩn tương ứng bằng cách sử dụng:
std::ios_base::sync_with_stdio(false);
Và khi đó, `cin` trở nên nhanh hơn `scanf()` như mong đợi. Một bài viết chi tiết về Input/Output nhanh trong Lập trình Thi đấu.
// Tên tệp : cin_fast.cpp để xem
// hiệu năng của cin() với đồng bộ hóa
// stdio bị tắt bằng sync_with_stdio(false).
#include<iostream>
using namespace std;
int main()
{
char nhap[256];
ios_base::sync_with_stdio(false);
while (cin >> nhap)
{
}
return 0;
}
Chạy chương trình:
$ g++ cin_fast.cpp -o cin_fast $ time ./cin_fast
- Như mọi thứ khác, có một lưu ý ở đây. **Với đồng bộ hóa bị tắt, việc sử dụng `cin` và `scanf()` cùng nhau sẽ dẫn đến kết quả không xác định.**
- Với đồng bộ hóa bị tắt, kết quả trên cho thấy `cin` nhanh hơn 8-10% so với `scanf()`. Điều này có thể là do `scanf()` diễn giải các đối số định dạng tại thời gian chạy và sử dụng số lượng đối số biến, trong khi `cin` thực hiện việc này tại thời gian biên dịch.
Bây giờ, bạn có đang tự hỏi, có thể nhanh hơn nữa không?
// Chuyển hướng nội dung tệp giả vào thiết bị null // (một thiết bị đặc biệt loại bỏ // thông tin được ghi vào nó) bằng dòng lệnh. $ time cat /tmp/dummy_input > /dev/null real 0m0.185s user 0m0.000s sys 0m0.092s
Wow! Điều này thật nhanh!!!