Большая разница (x9) во времени выполнения между почти идентичным кодом в C и c++
Я пытался решить это упражнение из www.spoj.com :FCTRL-Factorial
вам действительно не нужно его читать, просто сделайте это, если вам интересно :)
сначала я реализовал его в C++ (вот мое решение):
#include <iostream>
using namespace std;
int main() {
unsigned int num_of_inputs;
unsigned int fact_num;
unsigned int num_of_trailing_zeros;
std::ios_base::sync_with_stdio(false); // turn off synchronization with the C library’s stdio buffers (from https://stackoverflow.com/a/22225421/5218277)
cin >> num_of_inputs;
while (num_of_inputs--)
{
cin >> fact_num;
num_of_trailing_zeros = 0;
for (unsigned int fives = 5; fives <= fact_num; fives *= 5)
num_of_trailing_zeros += fact_num/fives;
cout << num_of_trailing_zeros << "n";
}
return 0;
}
Я загрузил его как решение для g++ 5.1
результат: времени 0.18 мем 3.3 м
но потом я увидел некоторые комментарии, которые утверждали, что их время выполнения было меньше 0,1. Поскольку я не мог думать о более быстром алгоритме, я попытался реализовать тот же код в C:
#include <stdio.h>
int main() {
unsigned int num_of_inputs;
unsigned int fact_num;
unsigned int num_of_trailing_zeros;
scanf("%d", &num_of_inputs);
while (num_of_inputs--)
{
scanf("%d", &fact_num);
num_of_trailing_zeros = 0;
for (unsigned int fives = 5; fives <= fact_num; fives *= 5)
num_of_trailing_zeros += fact_num/fives;
printf("%d", num_of_trailing_zeros);
printf("%s","n");
}
return 0;
}
Я загрузил его как решение для gcc 5.1
в этот раз результат был: времени 0.02 мем 2.1 M
теперь код почти то же самое, я добавил std::ios_base::sync_with_stdio(false);
к коду C++, как было предложено здесь, чтобы отключить синхронизацию с буферами stdio библиотеки C. Я также разделил printf("%dn", num_of_trailing_zeros);
до printf("%d", num_of_trailing_zeros); printf("%s","n");
чтобы компенсировать двойной вызов operator<<
на cout << num_of_trailing_zeros << "n";
.
но я все равно видел x9 лучшая производительность и более низкое использование памяти в C и C++ кода.
почему это?
EDIT
исправил unsigned long
до unsigned int
в C код. Он должен были unsigned int
и результаты, которые показаны выше, связаны с новым (unsigned int
) версию.
3 ответа:
обе программы делают точно то же самое. Они используют один и тот же точный алгоритм, и, учитывая его низкую сложность, их производительность в основном связана с эффективностью обработки ввода и вывода.
сканирование ввода с помощью
scanf("%d", &fact_num);
с одной стороны иcin >> fact_num;
С другой стороны, это не кажется очень дорогостоящим в любом случае. На самом деле это должно быть дешевле в C++, так как тип преобразования известен во время компиляции, и правильный парсер может быть вызван непосредственно компилятором C++. Этот то же самое относится и к выходу. Вы даже делаете точку написания отдельного вызова дляprintf("%s","\n");
, но компилятор C достаточно хорош, чтобы скомпилировать это как вызовputchar('\n');
.поэтому, глядя на сложность ввода-вывода и вычислений, версия C++ должна быть быстрее, чем версия C.
полное отключение буферизации
stdout
замедляет реализацию C до чего-то еще медленнее, чем версия C++. Еще один тест от AlexLop сfflush(stdout);
после последнегоprintf
дает аналогичную производительность, как и версия C++. Это не так медленно, как полное отключение буферизации, потому что вывод записывается в систему небольшими кусками вместо одного байта за раз.это, кажется, указывает на определенное поведение в вашей библиотеке C++: я подозреваю, что ваша система реализует
cin
иcout
ает выводcout
когда ввод запрашивается отcin
. Некоторые библиотеки C делают это также, но обычно только при чтении / записи В и из терминал. Бенчмаркинг, проведенный www.spoj.com сайт, вероятно, перенаправляет вход и выход из файлов.AlexLop сделал еще один тест: чтение всех входных данных сразу в векторе, а затем вычисление и запись всех выходных данных помогает понять, почему версия C++ намного медленнее. Это повышает производительность до версии C, это доказывает мою точку зрения и устраняет подозрение на код форматирования C++.
еще один тест от Blastfurnace, хранящий все выходы в
std::ostringstream
и промывка, которая в одном взрыве в конце, улучшает производительность C++ до базовой версии C. КЭД.Чересстрочный ввод от
cin
и выводаcout
кажется, что вызывает очень неэффективную обработку ввода-вывода, побеждая схему буферизации потока. снижение производительности в 10 раз.PS: ваш алгоритм неверен для
fact_num >= UINT_MAX / 5
, потому чтоfives *= 5
переполнит и обернет вокруг, прежде чем он станет> fact_num
. Вы можете исправить это путемfives
anunsigned long
илиunsigned long long
если один из этих типов больше, чемunsigned int
. Также используйте%u
как . Вам повезло, что ребята на www.spoj.com не слишком строги в своих оценках.EDIT: как позже объяснил vitaux, это поведение действительно предусмотрено стандартом C++.
cin
связана сcout
по умолчанию. Операция ввода отcin
для которого входной буфер нуждается в пополнении вызоветcout
для сброса ожидающего вывода. В реализации ОП,cin
кажется, флашcout
систематически, что немного излишне и явно неэффективно.Илья Попов предоставил простое решение для этого:
cin
может быть "привязана" кcout
наложив еще одно магическое заклинание в дополнение кstd::ios_base::sync_with_stdio(false);
:
cin.tie(nullptr);
также обратите внимание, что такой принудительный сброс также происходит при использовании
std::endl
вместо'\n'
для получения конца линии наcout
. Изменение выходной строки на более идиоматичный и невинный вид C++cout << num_of_trailing_zeros << endl;
будет ухудшать производительность таким же образом.
еще один трюк, чтобы сделать
iostream
s быстрее при использованииcin
иcout
позвонитьcin.tie(nullptr);
по умолчанию, когда вы вводите что-либо из
cin
, оно топитcout
. Это может значительно ухудшить производительность, если вы делаете чередование ввода и вывода. Это делается для использования интерфейса командной строки, где вы показываете некоторые подсказки, а затем ждете данных:std::string name; cout << "Enter your name:"; cin >> name;
в этом случае вы хотите убедиться, что приглашение действительно отображается перед началом работы ждем ввода. С линией выше вы разорвать эту связь,
cin
иcout
стать независимым.начиная с C++11, Еще один способ добиться лучшей производительности с помощью iostreams-использовать
std::getline
вместе сstd::stoi
, например:std::string line; for (int i = 0; i < n && std::getline(std::cin, line); ++i) { int x = std::stoi(line); }
этот путь может прийти близко к C-типу в представлении, или даже перегнать
scanf
. Используяgetchar
и особенноgetchar_unlocked
вместе с рукописным разбором по-прежнему обеспечивает лучшую производительность.PS. У меня есть написано пост сравнение нескольких способов ввода чисел в C++, полезно для онлайн-судей, но это только на русском языке, извините. Однако примеры кода и окончательная таблица должны быть понятны.
проблема в том, что цитируешь cppreference:
любой вход из std::cin, выход в std::cerr или завершение программы вызывает вызов std:: cout.flush()
это легко проверить: если вы замените
cin >> fact_num;
С
scanf("%d", &fact_num);
и
cin >> num_of_inputs
но сохранитьcout
вы получите почти такую же производительность в своей версии C++ (или, скорее, версии IOStream), как и в C один:то же самое происходит, если вы держите
cin
но заменитьcout << num_of_trailing_zeros << "\n";
С
printf("%d", num_of_trailing_zeros); printf("%s","\n");
простое решение состоит в том, чтобы "развязать"
cout
иcin
как упоминал Илья Попов:cin.tie(nullptr);
стандартные реализации библиотеки позволяют опустить вызов flush в некоторых случаях, но не всегда. Вот цитата из C++14 27.7.2.1.3 (благодаря chqrlie):
класс basic_istream:: sentry: во-первых, если есть.tie () - это не нулевой указатель, а вызовы функций.tie () - >flush () для синхронизации выходной последовательности с любым связанным внешним потоком C. За исключением того, что этот вызов может быть подавлен, если область put is.галстук() пуст. Далее реализация позволяет отложить вызов для сброса до вызова is.rdbuf()->underflow() происходит. Если такой вызов не происходит до уничтожения объекта sentry, вызов flush может быть полностью устранен.