Массивная разность скоростей fprintf без "-std = c99"
Я боролся в течение нескольких недель с плохо работающим переводчиком, который я написал.
На следующем простом знаке
#include<stdio.h>
int main()
{
int x;
char buf[2048];
FILE *test = fopen("test.out", "wb");
setvbuf(test, buf, _IOFBF, sizeof buf);
for(x=0;x<1024*1024; x++)
fprintf(test, "%04d", x);
fclose(test);
return 0
}
мы видим следующий результат:
bash-3.1$ gcc -O2 -static test.c -o test
bash-3.1$ time ./test
real 0m0.334s
user 0m0.015s
sys 0m0.016s
Как вы можете видеть, в момент добавления флага "-std = c99" производительность падает с ошибкой:
bash-3.1$ gcc -O2 -static -std=c99 test.c -o test
bash-3.1$ time ./test
real 0m2.477s
user 0m0.015s
sys 0m0.000s
Я использую компилятор gcc 4.6.2 mingw32.
Сгенерированный файл составляет около 12 М, так что разница между ними составляет около 21 МБ/с.
Запуск diff
показывает, что сгенерированные файлы идентичны.
Я предположил, что это имеет какое-то отношение к блокировке файлов в fprintf
, из которых программа сильно использует, но я не смог найти способ отключить ее в версии C99.
Я попробовал flockfile
в потоке, который я использую в начале программы, и соответствующем funlockfile
в конце, но был встречен с ошибками компилятора о неявных объявлениях и ошибками компоновщика, ссылающимися на undefined ссылки на те функции.
Может быть, есть еще одно объяснение этой проблемы, и что еще более важно, есть ли способ использовать C99 для Windows, не заплатив такую огромную цену за производительность?
Изменить:
Посмотрев на код, сгенерированный этими параметрами, он выглядит как в медленных версиях, mingw придерживается следующего:
_fprintf:
LFB0:
.cfi_startproc
subl $28, %esp
.cfi_def_cfa_offset 32
leal 40(%esp), %eax
movl %eax, 8(%esp)
movl 36(%esp), %eax
movl %eax, 4(%esp)
movl 32(%esp), %eax
movl %eax, (%esp)
call ___mingw_vfprintf
addl $28, %esp
.cfi_def_cfa_offset 4
ret
.cfi_endproc
В быстрой версии этого просто не существует; в противном случае оба одинаковы. Я предполагаю, что __mingw_vfprintf
кажется здесь медленным, но я не знаю, какое поведение ему нужно подражать, что делает его настолько медленным.
Ответы
Ответ 1
После некоторого копания в исходном коде, я обнаружил, почему функция MinGW так ужасно медленна:
В начале [v,f,s]printf
в MinGW есть невинно выглядящий код инициализации:
__pformat_t stream = {
dest, /* output goes to here */
flags &= PFORMAT_TO_FILE | PFORMAT_NOLIMIT, /* only these valid initially */
PFORMAT_IGNORE, /* no field width yet */
PFORMAT_IGNORE, /* nor any precision spec */
PFORMAT_RPINIT, /* radix point uninitialised */
(wchar_t)(0), /* leave it unspecified */
0, /* zero output char count */
max, /* establish output limit */
PFORMAT_MINEXP /* exponent chars preferred */
};
Однако PFORMAT_MINEXP
не то, что кажется:
#ifdef _WIN32
# define PFORMAT_MINEXP __pformat_exponent_digits()
# ifndef _TWO_DIGIT_EXPONENT
# define _get_output_format() 0
# define _TWO_DIGIT_EXPONENT 1
# endif
static __inline__ __attribute__((__always_inline__))
int __pformat_exponent_digits( void )
{
char *exponent_digits = getenv( "PRINTF_EXPONENT_DIGITS" );
return ((exponent_digits != NULL) && ((unsigned)(*exponent_digits - '0') < 3))
|| (_get_output_format() & _TWO_DIGIT_EXPONENT)
? 2
: 3
;
}
Это вызывает вызовы каждый раз, когда я хочу распечатать, а getenv
в окнах не должно быть очень быстрым. Замена этого определения с помощью 2
возвращает рабочую среду туда, где она должна быть.
Итак, ответ сводится к следующему: при использовании -std=c99
или любого ANSI-совместимого режима MinGW переключает время выполнения CRT со своим собственным. Обычно это не проблема, но у библиотеки MinGW была ошибка, которая замедляла функции форматирования далеко за пределами чего-либо, что можно было вообразить.
Ответ 2
С помощью -std=c99
отключите все расширения GNU.
С расширениями GNU и оптимизацией ваш fprintf(test, "B")
, вероятно, заменяется на fputc('B', test)
Примечание этот ответ устарел, см. fooobar.com/questions/307335/... и fooobar.com/questions/307335/...
Ответ 3
После некоторого рассмотрения вашего ассемблера, похоже, что медленная версия использует реализацию *printf()
MinGW, основанную, несомненно, на GCC, а на быстрой версии используется реализация Microsoft из msvcrt.dll
.
Теперь MS-сервер отличается отсутствием множества функций, которые реализует GCC. Некоторые из них - расширения GNU, а некоторые другие - для соответствия C99. И поскольку вы используете -std=c99
, вы запрашиваете соответствие.
Но почему так медленно? Ну, одним из факторов является простота, версия MS намного проще, поэтому ожидается, что она будет работать быстрее, даже в тривиальных случаях. Другим фактором является то, что вы работаете под Windows, поэтому ожидается, что версия MS будет более эффективной, чем одна копия из мира Unix.
Объясняет ли он фактор x10? Наверное, не...
Еще одна вещь, которую вы можете попробовать:
- Замените
fprintf()
на sprintf()
, печатайте в буфер памяти, не касаясь файла вообще. Затем вы можете попробовать сделать fwrite()
без печати. Таким образом, вы можете догадаться, есть ли потеря в форматировании данных или в записи на FILE
.
Ответ 4
Так как MinGW32 3.15, доступны совместимые функции printf
, а не те, которые содержатся в среде выполнения Microsoft C (CRT).
Новые функции printf
используются при компиляции в строгих режимах ANSI, POSIX и/или C99.
Для получения дополнительной информации см. журнал изменений mingw32
Вы можете использовать __msvcrt_fprintf()
для использования быстрой (несовместимой) функции.