Почему чтение строк из stdin в C++ гораздо медленнее, чем в Python?
Я хотел сравнить скорость чтения строк из стандартного ввода, используя Python и C++, и был поражен тем, что мой код на C++ работает на порядок медленнее, чем эквивалентный код на Python. Поскольку мой C++ немного ржавый, а я еще не стал экспертом в Python, прошу вас сказать, делаю ли я что-то неправильно или неправильно понимаю ситуацию.
Краткий ответ: Добавьте инструкцию cin.sync_with_stdio(false)
или просто используйте fgets
вместо этого.
Краткие результаты: Прокрутите до самого конца моего вопроса и посмотрите на таблицу.
Код на C++:
#include <iostream>
#include <time.h>
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 = (int) time(NULL) - start;
cerr << "Прочитано " << line_count << " строк за " << sec << " секунд.";
if (sec > 0) {
lps = line_count / sec;
cerr << " LPS: " << lps << endl;
} else
cerr << endl;
return 0;
}
// Компилировалось с помощью:
// g++ -O3 -o readline_test_cpp foo.cpp
Эквивалент на Python:
#!/usr/bin/env python
import time
import sys
count = 0
start = time.time()
for line in sys.stdin:
count += 1
delta_sec = int(time.time() - start)
if delta_sec >= 0:
lines_per_sec = int(round(count/delta_sec))
print("Прочитано {0} строк за {1} секунд. LPS: {2}".format(count, delta_sec, lines_per_sec))
Вот мои результаты:
$ cat test_lines | ./readline_test_cpp
Прочитано 5570000 строк за 9 секунд. LPS: 618889
$ cat test_lines | ./readline_test.py
Прочитано 5570000 строк за 1 секунд. LPS: 5570000
Я должен заметить, что я тестировал это как на Mac OS X v10.6.8 (Snow Leopard), так и на Linux 2.6.32 (Red Hat Linux 6.2). Первый — это MacBook Pro, а второй — весьма мощный сервер, хотя это не слишком актуально.
$ for i in {1..5}; do echo "Тестовый запуск $i в `date`"; echo -n "CPP:"; cat test_lines | ./readline_test_cpp ; echo -n "Python:"; cat test_lines | ./readline_test.py ; done
Тестовый запуск 1 в Mon Feb 20 21:29:28 EST 2012
CPP: Прочитано 5570001 строк за 9 секунд. LPS: 618889
Python:Прочитано 5570000 строк за 1 секунд. LPS: 5570000
Тестовый запуск 2 в Mon Feb 20 21:29:39 EST 2012
CPP: Прочитано 5570001 строк за 9 секунд. LPS: 618889
Python:Прочитано 5570000 строк за 1 секунд. LPS: 5570000
Тестовый запуск 3 в Mon Feb 20 21:29:50 EST 2012
CPP: Прочитано 5570001 строк за 9 секунд. LPS: 618889
Python:Прочитано 5570000 строк за 1 секунд. LPS: 5570000
Тестовый запуск 4 в Mon Feb 20 21:30:01 EST 2012
CPP: Прочитано 5570001 строк за 9 секунд. LPS: 618889
Python:Прочитано 5570000 строк за 1 секунд. LPS: 5570000
Тестовый запуск 5 в Mon Feb 20 21:30:11 EST 2012
CPP: Прочитано 5570001 строк за 10 секунд. LPS: 557000
Python:Прочитано 5570000 строк за 1 секунд. LPS: 5570000
Добавление и краткое резюме по небольшому бенчмарку Для полноты картины я подумал, что стоит обновить скорость чтения для того же файла на том же компьютере с оригинальным (с синхронизацией) кодом C++. Сравнение выглядит следующим образом, с несколькими решениями/подходами:
Реализация | Строк в секунду |
---|---|
python (по умолчанию) | 3,571,428 |
cin (по умолчанию/наивно) | 819,672 |
cin (без синхронизации) | 12,500,000 |
fgets | 14,285,714 |
wc (несправедливое сравнение) | 54,644,808 |
5 ответ(ов)
getline
, операторы потоков и scanf
могут быть удобны, если вам не важна скорость загрузки файла или если вы загружаете небольшие текстовые файлы. Но, если производительность для вас имеет значение, лучше всего загрузить весь файл в память (при условии, что он помещается).
Вот пример:
// открываем файл в двоичном режиме
std::fstream file(filename, std::ios::in | std::ios::binary);
if (!file) return NULL;
// читаем размер...
file.seekg(0, std::ios::end);
size_t length = (size_t)file.tellg();
file.seekg(0, std::ios::beg);
// читаем в буфер памяти, затем закрываем его.
char *filebuf = new char[length + 1];
file.read(filebuf, length);
filebuf[length] = '\0'; // делаем его нуль-терминированным
file.close();
Если хотите, можете обернуть поток вокруг этого буфера для более удобного доступа, как вот здесь:
std::istrstream header(&filebuf[0], length);
Кроме того, если у вас есть контроль над форматом файла, рассмотрите возможность использования плоского бинарного формата вместо текстового. Это более надежный способ чтения и записи, поскольку вам не придется иметь дело со всеми неопределенностями, связанными с пробелами. Он также меньше по размеру и гораздо быстрее в обработке.
Причина, по которой количество строк в версии на C++ на одну больше, чем в версии на Python, заключается в том, что флаг eof устанавливается только при попытке считывания за пределами конца файла. Поэтому правильный цикл будет выглядеть так:
while (cin) {
getline(cin, input_line);
if (!cin.eof())
line_count++;
};
В вашем втором примере (с использованием scanf()
) причиной, по которой это всё ещё медленнее, может быть то, что scanf("%s")
разбирает строку и ищет любой символ пробела (пробел, табуляция, символ новой строки).
Кроме того, да, CPython использует некоторый механизм кэширования, чтобы избежать чтения с жесткого диска.
Первый элемент ответа: <iostream>
действительно медленный. Черт возьми, как медленный! Я получаю значительный прирост производительности с помощью scanf
, как показано ниже, но всё равно это в два раза медленнее, чем Python.
#include <iostream>
#include <time.h>
#include <cstdio>
using namespace std;
int main() {
char buffer[10000];
long line_count = 0;
time_t start = time(NULL);
int sec;
int lps;
int read = 1;
while(read > 0) {
read = scanf("%s", buffer);
line_count++;
};
sec = (int) time(NULL) - start;
line_count--;
cerr << "Прочитано " << line_count << " строк за " << sec << " секунд." ;
if (sec > 0) {
lps = line_count / sec;
cerr << " Скорость обработки: " << lps << endl;
}
else
cerr << endl;
return 0;
}
Если вам нужно улучшить производительность ввода-вывода в C++, то стоит рассмотреть использование scanf
, так как он быстрее, чем стандартные средства ввода из <iostream>
. Однако имейте в виду, что в некоторых случаях такие подобные нижние уровни могут быть в два раза медленнее даже, чем интерпретируемые языки, такие как Python.
Ну, я вижу, что во втором решении вы переключились с cin
на scanf
, что было первым предложением, которое я хотел вам сделать (поскольку cin
очень медленный). Теперь, если вы перейдете с scanf
на fgets
, вы увидите еще одно улучшение производительности: fgets
— самая быстрая функция C++ для ввода строк.
Кстати, я не знал про эту синхронизацию, любопытно. Но все равно стоит попробовать fgets
.
Как изменить цвет вывода echo в Linux
Почему код Python выполняется быстрее в функции?
Перемешать строки DataFrame
Сохранить график в файл изображения вместо его отображения
Преобразование списка словарей в DataFrame pandas