Почему чтение строк из stdin в C++ происходит медленнее, чем в Python?
Почему ваш код медленный и как это исправить
Проблема не в C++ как таковом, а в вашем подходе к чтению и обработке данных. Вы совершаете несколько типичных ошибок, которые убивают производительность. Давайте разберём их по порядку.
Основные проблемы в вашем коде
- Многократные выделения памяти:
line.substr()создаёт новую строку для каждого числа. Это приводит к огромному количеству аллокаций и копирований. - Медленный парсинг:
std::stoiкаждый раз заново разбирает строку, что дорого. - Использование getline: Хотя это удобно, getline сам по себе медленнее прямого чтения через оператор
>>.
Правильное решение: используйте прямой ввод и быстрый парсинг
Вот оптимизированная версия, которая будет работать быстрее Python:
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
std::ios::sync_with_stdio(false);
std::cin.tie(nullptr);
// Читаем сразу весь поток в буфер
std::cin >> std::noskipws;
std::string input(std::istreambuf_iterator<char>(std::cin), {});
// Ручной быстрый парсинг чисел
std::vector<int> numbers;
numbers.reserve(10000000); // Резервируем место заранее
const char* ptr = input.data();
const char* end = ptr + input.size();
while (ptr < end) {
// Пропускаем пробелы
while (ptr < end && (*ptr == ' ' || *ptr == '\n')) ++ptr;
if (ptr >= end) break;
// Парсим число
int num = 0;
bool negative = false;
if (*ptr == '-') {
negative = true;
++ptr;
}
while (ptr < end && *ptr >= '0' && *ptr <= '9') {
num = num * 10 + (*ptr - '0');
++ptr;
}
if (negative) num = -num;
numbers.push_back(num);
}
std::cout << "Прочитано " << numbers.size() << " чисел" << std::endl;
return 0;
}
Почему это работает быстро?
- Однократное чтение: Мы читаем весь stdin в память одним махом через
istreambuf_iterator. Это минимизирует количество системных вызовов. - Ручной парсинг: Вместо
stoiиsubstrмы используем простой цикл с арифметикой указателей. Это в десятки раз быстрее. - Предварительное резервирование:
reserve()предотвращает множественные переаллокации вектора.
Альтернатива: используйте scanf (но осторожно)
Можно также использовать scanf, который работает быстрее cin >>:
#include <cstdio>
#include <vector>
int main() {
std::vector<int> numbers;
numbers.reserve(10000000);
int num;
while (scanf("%d", &num) == 1) {
numbers.push_back(num);
}
printf("Прочитано %zu чисел\n", numbers.size());
return 0;
}
Этот вариант тоже будет быстрым, но уступает ручному парсингу на 10-20%.
Почему Python быстрее в вашем случае?
Python использует sys.stdin.buffer.read(), который читает всё сразу, а затем split() и map(int, ...) обрабатывают данные нативно на C-уровне. Ваш C++ код делал то же самое, но с постоянными накладными расходами на создание строк. После оптимизации C++ обгонит Python в 2-3 раза.
Вывод: C++ не медленный — медленным может быть ваш код. Используйте правильные инструменты: читайте большими блоками, избегайте лишних аллокаций и используйте ручной парсинг для критичных участков.
<<Дополнительный совет: используйте memory-mapped файлы для максимальной производительности
Если вам нужно читать действительно огромные объёмы данных (сотни мегабайт или гигабайты), можно пойти ещё дальше и использовать memory-mapped файлы (mmap). Это позволяет отобразить файл (или stdin) прямо в адресное пространство процесса, избегая даже копирования данных из буфера ядра.
Вот пример для Linux:
#include <iostream>
#include <vector>
#include <sys/mman.h>
#include <unistd.h>
#include <fcntl.h>
int main() {
// Отображаем stdin как файл
int fd = STDIN_FILENO;
off_t size = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
void* mapped = mmap(nullptr, size, PROT_READ, MAP_PRIVATE, fd, 0);
if (mapped == MAP_FAILED) {
std::cerr << "mmap failed\n";
return 1;
}
const char* data = static_cast<const char*>(mapped);
// Далее парсим data так же, как в первом ответе
munmap(mapped, size);
return 0;
}
Важно: Этот метод не работает с piped stdin (например, при перенаправлении через |), только с файлами. Но если вы читаете из файла напрямую, mmap даёт прирост ещё на 30-50% по сравнению с обычным чтением.
Для Windows используйте CreateFileMapping и MapViewOfFile.
Однако для большинства задач достаточно оптимизации из первого ответа — ручной парсинг с резервированием памяти даёт отличные результаты без усложнения кода.