Смешение cout и printf для более быстрого вывода

После выполнения некоторых тестов я заметил, что printf намного быстрее, чем cout. Я знаю, что это зависит от реализации, но на моем Linux-сервере printf на 8 раз быстрее. Поэтому моя идея состоит в том, чтобы смешивать два метода печати: я хочу использовать cout для простых распечаток, и я планирую использовать printf для создания огромных выходов (как правило, в цикле). Я считаю, что это безопасно делать, пока я не забываю свернуть, прежде чем переключиться на другой метод:

cout << "Hello" << endl;
cout.flush();

for (int i=0; i<1000000; ++i) {
    printf("World!\n");
}
fflush(stdout);

cout << "last line" << endl;
cout << flush;

Хорошо ли это?

Обновление: Спасибо за все драгоценные отзывы. Резюме ответов: если вы хотите избежать сложных решений, просто не используйте endl с cout, так как он неявно очищает буфер. Вместо этого используйте "\n". Может быть интересно, если вы производите большие выходы.

Ответ 1

Прямой ответ: да, это нормально.

Многие люди бросили вокруг различных идей о том, как улучшить скорость, но, похоже, довольно много разногласий по поводу того, что наиболее эффективно. Я решил написать программу быстрого тестирования, чтобы получить хотя бы некоторое представление о том, какие методы сделали что.

#include <iostream>
#include <string>
#include <sstream>
#include <time.h>
#include <iomanip>
#include <algorithm>
#include <iterator>
#include <stdio.h>

char fmt[] = "%s\n";
static const int count = 3000000;
static char const *const string = "This is a string.";
static std::string s = std::string(string) + "\n";

void show_time(void (*f)(), char const *caption) { 
    clock_t start = clock();
    f();
    clock_t ticks = clock()-start;
    std::cerr << std::setw(30) << caption 
        << ": " 
        << (double)ticks/CLOCKS_PER_SEC << "\n";
}

void use_printf() {
    for (int i=0; i<count; i++)
        printf(fmt, string);
}

void use_puts() {
    for (int i=0; i<count; i++) 
        puts(string);        
}

void use_cout() { 
    for (int i=0; i<count; i++)
        std::cout << string << "\n";
}

void use_cout_unsync() { 
    std::cout.sync_with_stdio(false);
    for (int i=0; i<count; i++)
        std::cout << string << "\n";
    std::cout.sync_with_stdio(true);
}

void use_stringstream() { 
    std::stringstream temp;
    for (int i=0; i<count; i++)
        temp << string << "\n";
    std::cout << temp.str();
}

void use_endl() { 
    for (int i=0; i<count; i++)
        std::cout << string << std::endl;
}

void use_fill_n() { 
    std::fill_n(std::ostream_iterator<char const *>(std::cout, "\n"), count, string);
}

void use_write() {
    for (int i = 0; i < count; i++)
        std::cout.write(s.data(), s.size());
}

int main() { 
    show_time(use_printf, "Time using printf");
    show_time(use_puts, "Time using puts");
    show_time(use_cout, "Time using cout (synced)");
    show_time(use_cout_unsync, "Time using cout (un-synced)");
    show_time(use_stringstream, "Time using stringstream");
    show_time(use_endl, "Time using endl");
    show_time(use_fill_n, "Time using fill_n");
    show_time(use_write, "Time using write");
    return 0;
}

Я запускал это в Windows после компиляции с VС++ 2013 (обе версии x86 и x64). Выход из одного запуска (с выходом, перенаправленным на файл диска) выглядел следующим образом:

          Time using printf: 0.953
            Time using puts: 0.567
   Time using cout (synced): 0.736
Time using cout (un-synced): 0.714
    Time using stringstream: 0.725
            Time using endl: 20.097
          Time using fill_n: 0.749
           Time using write: 0.499

Как и ожидалось, результаты меняются, но есть несколько моментов, которые я нашел интересными:

  1. printf/puts намного быстрее, чем cout при записи на устройство NUL
  • но cout отлично переносится при записи в реальный файл
Довольно много предлагаемых оптимизаций мало
  • В моем тестировании fill_n примерно так же быстро, как и все остальное
Самой большой оптимизацией является исключение endl cout.write дал самое быстрое время (хотя, вероятно, не с существенным запасом

Недавно я редактировал код, чтобы вызвать вызов printf. Андерс Касеорг был достаточно любезен, чтобы указать на то, что g++ распознает, что определенная последовательность printf("%s\n", foo); эквивалентна puts(foo);, и соответственно генерирует код (т.е. Генерирует код для вызова puts вместо printf). Перемещение строки формата в глобальный массив и передача этого значения в виде строки форматирования с идентичным выводом, но заставляет ее создаваться с помощью printf вместо puts. Разумеется, возможно, что и в этот день они могут оптимизироваться, но по крайней мере на данный момент (g++ 5.1) тест с g++ -O3 -S подтверждает, что он фактически вызывает printf (где предыдущий код скомпилирован для вызова puts).

Ответ 2

Отправка std::endl в поток добавляет newline и очищает поток. Последующее обращение cout.flush() является излишним. Если это было сделано при выборе времени cout против printf, вы не сравнивали яблоки с яблоками.

Ответ 3

По умолчанию стандартные выходные потоки C и С++ синхронизируются, так что запись на один вызывает сброс другой, поэтому явные сбросы не нужны.

Ответ 4

Также обратите внимание, что поток С++ синхронизируется с потоком C. Таким образом, он выполняет дополнительную работу, чтобы оставаться в синхронизации.

Еще одна вещь, которую нужно отметить, - убедиться, что вы очищаете потоки равной суммы. Если вы постоянно очищаете поток от одной системы, а не от другой, которая будет определенно влиять на скорость тестов.

Прежде чем считать, что один быстрее, чем другой, вы должны:

  • un-sync С++ I/O из C I/O (см. sync_with_stdio()).
  • Убедитесь, что количество флешей сопоставимо.

Ответ 5

Вы можете повысить производительность printf, увеличив размер буфера для stdout:

setvbuf (stdout, NULL, _IOFBF, 32768);  // any value larger than 512 and also a
                  // a multiple of the system i/o buffer size is an improvement

Количество вызовов операционной системы для выполнения операций ввода-вывода почти всегда является самым дорогим компонентом и ограничителем производительности.

Конечно, если вывод cout смешивается с stdout, буфер сбросит цель, увеличив размер буфера.

Ответ 6

Вы можете использовать sync_with_stdio, чтобы сделать С++ IO быстрее.

cout.sync_with_stdio(false);

Чтобы улучшить производительность с помощью cout.

Ответ 7

Не беспокойтесь о производительности между printf и cout. Если вы хотите повысить производительность, отформатируйте выходной файл из неформатированного вывода.

puts("Hello World\n") намного быстрее, чем printf("%s", "Hellow World\n"). (В первую очередь из-за форматирования служебных данных). После того как вы выделили форматированный текст, вы можете делать трюки, например:

const char hello[] = "Hello World\n";
cout.write(hello, sizeof(hello) - sizeof('\0'));

Чтобы ускорить форматированный вывод, трюк состоит в том, чтобы выполнить все форматирование в строке, а затем использовать вывод блока со строкой (или буфером):

const unsigned int MAX_BUFFER_SIZE = 256;
char buffer[MAX_BUFFER_SIZE];
sprintf(buffer, "%d times is a charm.\n", 5);
unsigned int text_length = strlen(buffer) - sizeof('\0');
fwrite(buffer, 1, text_length, stdout);

Чтобы повысить производительность вашей программы, уменьшите количество выходных данных. Чем меньше вы выведете, тем быстрее будет ваша программа. Побочным эффектом будет то, что размер исполняемого файла также уменьшится.

Ответ 8

Ну, я не могу придумать никаких причин действительно использовать cout, чтобы быть честным. Совершенно безумно иметь огромный громоздкий шаблон, чтобы сделать что-то настолько простое, что будет в каждом файле. Кроме того, он, как и он, был сконструирован так медленно, насколько возможно, и после миллионного времени ввода < < < а затем набрав значение между ними и получив что-то lik > variableName → > по поводу аварии, я больше не хочу этого делать.

Не говоря уже о том, что вы включаете пространство имен std, мир в конце концов взорвется, и если вы не наберете свою нагрузку, становится еще более смешной.

Однако мне тоже не нравится printf. Для меня решение состоит в том, чтобы создать мой собственный конкретный класс, а затем вызвать все необходимое для этого. Тогда вы можете иметь действительно простой io любым способом, который вы хотите, и с любой реализацией, какой хотите, любым форматом, который вы хотите, и т.д. (Как правило, вы хотите, чтобы поплавки всегда были одним из способов, например, не форматировать их без каких-либо причин, поэтому в форматировании с каждым вызовом - шутка).

Итак, все, что я типа, это что-то вроде dout + "Это более разумно, чем" + cPlusPlusMethod + "из" + debugIoType + ". ИМО как минимум"; DOUT ++;

но вы можете иметь все, что хотите. С большим количеством файлов это удивительно, насколько это улучшает время компиляции.

Кроме того, нет ничего плохого в смешении C и С++, это должно быть сделано просто смешно, и если вы используете то, что вызывает проблемы с использованием C, в первую очередь безопасно сказать, что меньше всего беспокоиться - это проблема с смешивание C и С++.

Ответ 9

Смешивание С++ и C iomethods было рекомендовано против моих книг на С++, FYI. Я уверен, что функции C преткнут в ожидании/сохранении С++.