2016-08-11 3 views
19

Скажем, у меня есть одна линия printf() с длинной строки:Несколько вызовов printf() против одного вызова printf() с длинной строкой?

printf("line 1\n" 
"line 2\n" 
"line 3\n" 
"line 4\n" 
"line 5\n" 
"line 6\n" 
"line 7\n"  
"line 8\n" 
"line 9\n.. etc"); 

Каковы затраты, понесенные этим стилем по сравнению с наличием нескольких printf() «S для каждой строки?
Возможно ли возникновение переполнения стека, если строка слишком длинная?

+3

Ваш синтаксис C недействителен. –

+26

@ammoQ; Кажется верным мне. – haccks

+1

@ammoQ Действительно. – LPs

ответ

16

Каковы затраты, понесенные этим стилем по сравнению с наличием нескольких printf() для каждой строки?

Несколько printf приведут к нескольким вызовам функций, и это единственные накладные расходы.

Возможно ли возникновение переполнения стека, если строка слишком длинная?

В этом случае переполнение стека не происходит. Строковые литералы обычно хранятся в памяти только для чтения, а не в стеке. Когда строка передается в printf, тогда в стек скопируется только указатель на свой первый элемент.

Компилятор будет рассматривать эту многоканальную строку «строку 1 \ П»

"line 2\n" 
"line 3\n" 
"line 4\n" 
"line 5\n" 
"line 6\n" 
"line 7\n"  
"line 8\n" 
"line 9\n.. etc" 

в одну строке

"line 1\nline 2\nline 3\nline 4\nline 5\nline 6\nline 7\nline 8\nline 9\n.. etc" 

и это будет храниться в секции только для чтения памяти.

Но обратите внимание, что (указываемого pmg в comment) стандартной секции C11 5.2.4.1 ограничивает перевод говорит, что

Реализация должна быть в состоянии перевести и выполнить хотя бы одну программу, которая содержит, по меньшей мере, один экземпляр каждого из следующих пределах 18):
[...]
- 4095 символов в строке буквальные (после конкатенации)
[...]

+4

Незначительное примечание: «Строковые литералы хранятся в памяти только для чтения» -> возможно. Строковые литералы предназначены для чтения. «Написание» для них - UB. – chux

3

A printf без модификаторов формата заменен молча (aka. оптимизирован) к вызову puts. Это уже ускорение. Вы действительно не хотите потерять это при вызове printf/puts несколько раз.

GCC имеет printf (среди прочих) как встроенный, поэтому он может оптимизировать вызовы во время компиляции.

См:

+0

У вас есть источник на это? Я бы хотел больше узнать об этом. Благодарю. – eXPerience

+0

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

+2

Это неправда. Это может быть справедливо для конкретного _compiler_. gcc для руки, например, заменит все операции соответствующими командами рук - конечно, это совершенно не имеет значения и просто детали реализации, точно так же как и оптимизация printf. – pipe

10

C Объединяет строковые литералы, если они разделены ничего или пробелами. Таким образом, ниже

printf("line 1\n" 
"line 2\n" 
"line 3\n" 
"line 4\n" 
"line 5\n" 
"line 6\n" 
"line 7\n"  
"line 8\n" 
"line 9\n.. etc"); 

прекрасно подходит и выделяется с точки зрения удобочитаемости. Кроме того, один printf вызова неоспоримо имеет меньшую нагрузку, чем 9 printf вызовов.

8

printf является медленной функцией, если вы выводите только постоянные строки, потому что printf должен сканировать каждый символ для спецификатора формата (%). Функции, такие как puts, являются значительно быстрее для длинных строк, потому что они могут в основном просто memcpy входная строка в выходной буфер ввода-вывода.

Многие современные компиляторы (GCC, Clang, возможно, другие) имеют оптимизацию, которая автоматически преобразует printf в puts, если входная строка является константной строкой без спецификаторов формата, которая заканчивается символом новой строки. Так, например, составление следующий код:

printf("line 1\n"); 
printf("line 2\n"); 
printf("line 3"); /* no newline */ 

результаты в следующей сборке (лязг 703.0.31, cc test.c -O2 -S):

... 
leaq L_str(%rip), %rdi 
callq _puts 
leaq L_str.3(%rip), %rdi 
callq _puts 
leaq L_.str.2(%rip), %rdi 
xorl %eax, %eax 
callq _printf 
... 

другими словами, puts("line 1"); puts("line 2"); printf("line 3");.

Если долго printf строка делает не конца с новой строкой, то ваша производительность может быть значительна хуже, чем если бы вы сделали кучу printf вызовов с новой строкой с завершающими строками, просто из-за эту оптимизацию. Для того, чтобы продемонстрировать, рассмотрят следующую программу:

#include <stdio.h> 

#define S "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa" 
#define L S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S S 
/* L is a constant string of 4000 'a's */ 

int main() { 
    int i; 
    for(i=0; i<1000000; i++) { 
#ifdef SPLIT 
     printf(L "\n"); 
     printf(S); 
#else 
     printf(L "\n" S); 
#endif 
    } 
} 

Если SPLIT не определена (продуцирующие один printf, без завершающего символа новой строки), время выглядит следующим образом:

[08/11 11:47:23] /tmp$ cc test.c -O2 -o test 
[08/11 11:47:28] /tmp$ time ./test > /dev/null 

real 0m2.203s 
user 0m2.151s 
sys 0m0.033s 

Если SPLIT определен (производство два printf с, одна с завершающим символом новой строкой, другой без), время выглядит следующим образом:

[08/11 11:48:05] /tmp$ time ./test > /dev/null 

real 0m0.470s 
user 0m0.435s 
sys 0m0.026s 

Итак, вы можете видеть, что в этом случае разделение printf на две части на самом деле дает ускорение в 4 раза. Конечно, это крайний случай, но он иллюстрирует, как printf можно оптимизировать в зависимости от ввода. (Обратите внимание, что использование fwrite еще быстрее - 0,197 с, поэтому вам следует подумать об использовании этого, если вы действительно хотите скорость!).

tl; dr: если вы печатаете только большие постоянные строки, полностью избегайте printf и используйте более быструю функцию, например puts или fwrite.

+0

OP должен был использовать 'printf («% s »,« очень длинная строка »)' в первую очередь, что смягчило бы проблему, но это хороший совет. –

1

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

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

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

Если вы действительно беспокоитесь о стоимости в своей основной теме, переместите этот материал в отдельный поток.