Question
Why C++ stdin Line Reading Can Be Slower Than Python: cin, sync_with_stdio, and fgets
Question
I compared reading lines from standard input in Python and C++, and I was surprised that my C++ program was much slower than the Python version. I am not sure whether my C++ code is inefficient, whether I am benchmarking incorrectly, or whether I am misunderstanding how C++ input works.
Here is the C++ program:
#include <iostream>
#include <string>
#include <ctime>
using namespace std;
int main() {
string input_line;
long line_count = 0;
time_t start = time(NULL);
int sec;
int lps;
while (cin) {
getline(cin, input_line);
if (!cin.eof()) {
line_count++;
}
}
sec = static_cast<int>(time(NULL) - start);
cerr << "Read " << line_count << " lines in " << sec << " seconds.";
if (sec > 0) {
lps = line_count / sec;
cerr << " LPS: " << lps << endl;
} else {
cerr << endl;
}
return 0;
}
Compiled with:
g++ -O3 -o readline_test_cpp foo.cpp
Here is the Python version:
#!/usr/bin/env python
import time
import sys
count = 0
start_time = time.time()
for line in sys.stdin:
count += 1
delta_sec = int(time.time() - start_time)
if delta_sec >= 0:
lines_per_sec = int(round(count / delta_sec)) if delta_sec else count
print("Read {0} lines in {1} seconds. LPS: {2}".format(
count, delta_sec, lines_per_sec
))
Example results:
$ cat test_lines | ./readline_test_cpp
Read 5570000 lines in 9 seconds. LPS: 618889
$ cat test_lines | ./readline_test.py
Read 5570000 lines in 1 seconds. LPS: 5570000
Why is the C++ version so much slower than Python when reading lines from stdin? Is there something wrong with using std::getline(cin, input_line) this way?
Short Answer
By the end of this page, you will understand why C++ stream input can appear much slower than Python for large stdin workloads, what std::ios::sync_with_stdio(false) changes, when std::cin.tie(nullptr) helps, and when lower-level functions like fgets may be faster. You will also learn how to write a more accurate and more idiomatic C++ line-reading loop.
Concept
In this question, the real concept is input performance in C++, especially the difference between:
- C++ iostreams like
std::cinandstd::getline - C stdio functions like
fgets - Python's buffered input behavior
A beginner often assumes that C++ must automatically be faster than Python because C++ is compiled. In practice, performance depends heavily on the library and API you use, not just the language.
Why default std::cin can be slow
By default, C++ iostreams are usually synchronized with C stdio. This means C++ streams cooperate with functions like printf, scanf, and fgets so they can be safely mixed. That compatibility has a cost.
This line disables that extra synchronization:
std::ios::sync_with_stdio(false);
When you do that, std::cin often becomes dramatically faster.
Why Python can look faster
Python's sys.stdin iteration is implemented in highly optimized C code under the hood, with efficient buffering. So even though Python is an interpreted language, its input layer may outperform default-configured C++ iostreams.
That does not mean Python is inherently faster than C++ at I/O. It usually means:
- your C++ code is using a slower default I/O configuration
- Python is using efficient buffering internally
- the benchmark is measuring library behavior, not raw language speed
Another issue in the original C++ loop
This loop style is not ideal:
while (cin) {
getline(cin, input_line);
if (!cin.eof())
line_count++;
}
A more idiomatic and safer form is:
while (getline(cin, input_line)) {
line_count++;
}
This checks whether reading succeeded directly.
Why this matters in real programming
Input speed matters in:
- competitive programming
- log processing
- command-line tools
- ETL/data pipelines
- stream processing
- large text file analysis
If you read millions of lines, the difference between default cin and optimized input can be huge.
Mental Model
Think of input like receiving boxes at a warehouse.
- Default
std::cinis like checking each box through an extra security desk because it must stay compatible with another shipping system. sync_with_stdio(false)removes that extra coordination step.fgetsis like using a simpler, direct loading dock.- Python
sys.stdinmay look fast because the workers behind the scenes already batch the boxes efficiently.
So the surprising result is not really “Python is faster than C++.” It is more like “one input path has more overhead than another.”
Syntax and Examples
Fast C++ stream setup
If you want to use std::cin efficiently, do this near the start of main():
#include <iostream>
#include <string>
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::string line;
long long count = 0;
while (std::getline(std::cin, line)) {
count++;
}
std::cout << count << '\n';
}
What these lines do
std::ios::sync_with_stdio(false);
- Disables synchronization between C++ streams and C stdio.
- Usually gives a large speed boost.
- After this, avoid mixing
cin/coutwithscanf/printfunless you really know what you are doing.
std::cin.tie(nullptr);
- Unties
cinfromcout. - Prevents automatic flushing of
coutbefore every input operation. - Helpful when input is heavy.
Using fgets instead
If raw input speed is the priority, C stdio can also be a good option:
#include <cstdio>
int main() {
char buffer[4096];
long long count = 0;
while (fgets(buffer, sizeof(buffer), stdin)) {
count++;
}
printf("%lld\n", count);
}
Important note about fgets
This reads at most sizeof(buffer) - 1 characters at a time. If a line is longer than the buffer, one logical line may be split across multiple reads. So fgets is fast, but you must choose buffer handling carefully if line length is unknown.
Best beginner-friendly fix
If you want line-based input and simple code, start with this:
#include <iostream>
#include <string>
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::string line;
long long count = 0;
while (std::getline(std::cin, line)) {
++count;
}
std::cerr << "Read " << count << " lines\n";
}
This keeps the convenience of C++ strings while removing the biggest default overhead.
Step by Step Execution
Consider this improved example:
#include <iostream>
#include <string>
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::string line;
int count = 0;
while (std::getline(std::cin, line)) {
++count;
}
std::cout << count << '\n';
}
Suppose the input is:
apple
banana
carrot
Step-by-step
sync_with_stdio(false)disables the extra synchronization overhead.cin.tie(nullptr)stopscoutfrom being flushed before each read.linestarts as an empty string.countstarts at0.- First
getlinereadsappleintoline. - The read succeeds, so the
whilecondition is true. countbecomes1.- Second
getlinereadsbanana. countbecomes2.- Third
getlinereadscarrot. countbecomes3.- Next
getlinetries to read again, but reaches end-of-file. - The read fails, so the loop ends.
- The program prints
3.
Why this is better than while (cin)
With this form:
while (std::getline(std::cin, line)) {
++count;
}
you only enter the loop when a line was actually read successfully. That avoids off-by-one logic and avoids checking eof() manually.
Real World Use Cases
Fast input matters whenever your program reads a lot of text.
Common examples
- Log analysis tools
- Read millions of server log lines from
stdinor a file.
- Read millions of server log lines from
- Competitive programming
- Large input size can cause time limit errors if I/O is slow.
- Command-line filters
- Programs that process piped input, such as transforming CSV or JSON lines.
- Data ingestion scripts
- Import rows from exports, reports, or message streams.
- Monitoring pipelines
- Consume line-based events from another process.
Example scenario
A C++ tool that counts failed login attempts in logs:
#include <iostream>
#include <string>
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::string line;
long long failed = 0;
while (std::getline(std::cin, line)) {
if (line.find("FAILED LOGIN") != std::string::npos) {
++failed;
}
}
std::cout << failed << '\n';
}
Here, efficient line reading directly affects total runtime.
Real Codebase Usage
In real projects, developers usually combine fast input with clear loop patterns and simple validation.
Common patterns
1. Idiomatic read loop
std::string line;
while (std::getline(std::cin, line)) {
// process line
}
This is the standard pattern for line-based reading.
2. Fast I/O setup at program start
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
This is common in tools that do heavy input parsing.
3. Guard clause for empty lines
if (line.empty()) {
continue;
}
Useful when blank lines should be ignored.
4. Validation before processing
if (line.size() < 3) {
continue;
}
Avoids errors when input may be malformed.
5. Early return on setup failure
If reading from files instead of stdin, developers check input immediately:
#include <fstream>
#include <iostream>
#include <string>
int main() {
std::ifstream file("data.txt");
if (!file) {
std::cerr << "Could not open file\n";
return 1;
}
std::string line;
while (std::getline(file, line)) {
// process
}
}
When teams use fgets
Some performance-sensitive codebases still use C stdio functions because they are fast and predictable. But many teams prefer std::getline with fast-iostream settings because it is safer and easier to work with using std::string.
Practical rule
- Use
std::getlinefor clean, modern C++ string handling. - Add
sync_with_stdio(false)when performance matters. - Consider
fgetsonly if you have measured a real bottleneck and accept the lower-level handling.
Common Mistakes
1. Using while (cin) instead of checking the read directly
Broken style:
while (std::cin) {
std::getline(std::cin, line);
count++;
}
Problem:
- The loop condition checks the stream before reading.
- The final failed read can make your logic awkward or wrong.
Better:
while (std::getline(std::cin, line)) {
count++;
}
2. Forgetting to disable sync when benchmarking iostreams
std::string line;
while (std::getline(std::cin, line)) {
// slow for huge input compared with unsynced iostreams
}
If performance matters, add:
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
3. Mixing C and C++ I/O after disabling sync
Potentially confusing:
std::ios::sync_with_stdio(false);
std::cout << "Enter name: ";
printf("Now using printf\n");
After disabling sync, mixing cout and printf can lead to surprising order or buffering behavior.
Avoid this unless you understand the consequences.
4. Assuming a compiled language is always faster in every test
The language runtime is only part of the story. Library implementation, buffering, and system calls matter a lot for I/O.
5. Using fgets without handling long lines
Broken assumption:
char buffer[128];
while (fgets(buffer, sizeof(buffer), stdin)) {
count++; // may count partial lines if lines exceed 127 chars
}
If input lines can be longer than the buffer, a single line may be split across multiple reads.
6. Using low-resolution timing carelessly
Timing with whole seconds can hide important differences.
Better benchmarking tools include:
std::chronoin C++- repeated runs
- warm-up awareness
- consistent input source
Even when the broad conclusion is correct, poor timing can make results look more dramatic than they are.
Comparisons
| Approach | Ease of use | Typical speed | Best for | Notes |
|---|---|---|---|---|
std::getline(std::cin, line) with default settings | Easy | Often slower | Small programs, simple scripts | Stream sync adds overhead |
std::getline(std::cin, line) with sync_with_stdio(false) | Easy | Fast | Most C++ line-processing tasks | Good balance of speed and safety |
fgets | Medium | Very fast | Performance-sensitive C-style input | Must manage buffer size manually |
Python for line in sys.stdin | Very easy | Often fast | Scripting, quick tools | Implemented with efficient buffering in C |
cin vs fgets
| Feature | cin / getline | fgets |
|---|---|---|
Uses std::string | Yes | No |
| Automatic resizing for long lines | Yes | No |
| Easy to write safely | Yes | Less so |
| Raw speed | Good when unsynced | Often excellent |
| C++ style | Yes | No |
Synced vs unsynced iostreams
| Mode | Meaning | Performance | Mixing with printf/scanf |
|---|---|---|---|
| Synced | C++ streams coordinate with C stdio | Slower | Safer to mix |
| Unsynced | Coordination disabled | Faster | Do not casually mix |
Main takeaway
For most C++ programs, the sweet spot is:
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
plus:
while (std::getline(std::cin, line)) {
// process
}
Cheat Sheet
Fast line reading in C++
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
std::string line;
while (std::getline(std::cin, line)) {
// use line
}
Why default cin can be slow
- C++ streams are synchronized with C stdio by default.
- This adds overhead.
- Disabling sync often gives a large speedup.
Best loop pattern
Good:
while (std::getline(std::cin, line)) {
++count;
}
Avoid:
while (std::cin) {
std::getline(std::cin, line);
}
cin.tie(nullptr)
- Stops automatic flushing of
coutbefore input. - Useful in input-heavy programs.
fgets
char buffer[4096];
while (fgets(buffer, sizeof(buffer), stdin)) {
// use buffer
}
Watch out:
- long lines may be split across reads
- uses fixed-size buffers
Rules of thumb
- Need convenience and speed: use
getline+ unsynced iostreams. - Need maximum low-level speed: consider
fgets. - Do not mix C and C++ I/O casually after disabling sync.
- Benchmark with realistic inputs and accurate timing.
FAQ
Why is std::cin slower than Python in this benchmark?
Because default C++ iostreams often include synchronization overhead with C stdio, while Python's stdin iteration uses efficient buffered code underneath.
Does std::ios::sync_with_stdio(false) always make C++ input faster?
Usually yes for heavy input workloads, but you should measure. It removes compatibility overhead and often gives a large improvement.
Should I always call std::cin.tie(nullptr) too?
It is commonly used with fast input. It helps when your program does lots of reading and does not rely on automatic flushing before input.
Is fgets faster than std::getline?
It can be, especially in some benchmarks. But it is lower-level and requires manual buffer handling.
Is the original while (cin) loop correct?
It is not the best style. The idiomatic and safer form is while (std::getline(std::cin, line)).
Can I mix printf and cout after calling sync_with_stdio(false)?
You should avoid that unless you understand buffering behavior well, because output order can become surprising.
Does this mean Python is better for I/O than C++?
Not generally. It means a specific Python input path outperformed a specific default C++ setup. With proper C++ configuration, C++ can be much faster.
What is the simplest fix for this problem?
Use:
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
and read with:
while (std::getline(std::cin, line))
Mini Project
Description
Build a command-line program that reads log lines from standard input and counts how many lines match a keyword. This demonstrates fast line-based input in C++ and shows a realistic use case for std::getline, sync_with_stdio(false), and simple text processing.
Goal
Create a C++ program that reads from stdin, searches each line for a target word, and prints the total number of matching lines.
Requirements
- Read all input from standard input line by line.
- Disable iostream synchronization for faster input.
- Count only lines that contain a given keyword.
- Print the final count as a single number.
- Use
std::getlineandstd::string.
Keep learning
Related questions
Advantages of Brace Initialization in C++
Learn why C++ brace initialization is often clearer and safer than other object initialization styles, with examples and common pitfalls.
Basic Rules and Idioms for Operator Overloading in C++
Learn the core rules, syntax, and common idioms for operator overloading in C++, including member vs non-member operators.
C++ Aggregates, Trivial Types, Trivially Copyable Types, and PODs Explained
Learn what aggregates, trivial types, trivially copyable types, and PODs mean in C++, how they differ, and why they matter.