смешивание cout и printf для более быстрого вывода - PullRequest
46 голосов
/ 17 декабря 2009

После выполнения некоторых тестов я заметил, что 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" вместо этого. Это может быть интересно, если вы производите большие выходы.

Ответы [ 9 ]

69 голосов
/ 18 декабря 2009

Прямой ответ: да, ничего страшного.

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

#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 после компиляции с VC ++ 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 / put намного быстрее, чем cout, при записи на устройство NUL
  2. но cout отлично справляется с записью в реальный файл
  3. Довольно много предложенных оптимизаций мало что дают
    • В моем тестировании fill_n примерно так же быстр, как и все остальные
  4. Самая большая оптимизация избегает endl
  5. cout.write показал самое быстрое время (хотя, вероятно, не со значительным отрывом

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

19 голосов
/ 18 декабря 2009

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

12 голосов
/ 18 декабря 2009

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

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

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

  • несинхронизация ввода-вывода C ++ из ввода-вывода C (см. Sync_with_stdio ()).
  • Убедитесь, что количество приливов сопоставимо.
12 голосов
/ 18 декабря 2009

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

9 голосов
/ 18 декабря 2009

Вы можете еще больше улучшить производительность 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, сбрасывание буфера лишает цель увеличения размера буфера.

5 голосов
/ 18 декабря 2009

Вы можете использовать sync_with_stdio для ускорения ввода-вывода C ++.

cout.sync_with_stdio(false);

Должно улучшить вашу производительность с cout.

3 голосов
/ 18 декабря 2009

Не беспокойтесь о производительности между 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);

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

1 голос
/ 18 декабря 2009

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

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

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

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

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

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

0 голосов
/ 18 декабря 2009

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

...