Значительная разница в скорости fprintf без "-std=c99"

Я неделями боролся с плохо работающим переводчиком, которого я написал. На следующем простом bechmark

#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 в конце, но был встречен с ошибками компилятора о неявных объявлениях и ошибками компоновщика, требующими неопределенных ссылок на эти функции.

Может ли быть другое объяснение этой проблемы, и что более важно, есть ли способ использовать 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 кажется, что это медленный удар, но я понятия не имею, какое поведение ему нужно для эмуляции, что делает его таким медленным.

4 ответа

Решение

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

С помощью -std=c99 отключить все расширения GNU.

С расширениями и оптимизацией GNU ваш fprintf(test, "B") вероятно заменен fputc('B', test)

Обратите внимание, что этот ответ устарел, см. /questions/44407553/znachitelnaya-raznitsa-v-skorosti-fprintf-bez-stdc99/44407616#44407616 и /questions/44407553/znachitelnaya-raznitsa-v-skorosti-fprintf-bez-stdc99/44407572#44407572

Начиная с MinGW32 3.15, соответствует printf функции доступны для использования вместо тех, которые есть в Microsoft C runtime (CRT). Новый printf функции используются при компиляции в строгих режимах ANSI, POSIX и / или C99.

Для получения дополнительной информации см. Журнал изменений mingw32.

Ты можешь использовать __msvcrt_fprintf() использовать быструю (несовместимую) функцию.

После некоторого рассмотрения вашего ассемблера, похоже, что медленная версия использует *printf() реализация MinGW, основанная, несомненно, на GCC, в то время как быстрая версия использует реализацию Microsoft от msvcrt.dll,

В настоящее время MS особенно выделяется отсутствием множества функций, которые реализует GCC. Некоторые из них являются расширениями GNU, а некоторые - для соответствия C99. И так как вы используете -std=c99 Вы запрашиваете соответствие.

Но почему так медленно? Ну, одним из факторов является простота, версия MS гораздо проще, поэтому ожидается, что она будет работать быстрее, даже в тривиальных случаях. Другим фактором является то, что вы работаете под Windows, поэтому ожидается, что версия MS будет более эффективной, чем та, что скопирована из мира Unix.

Это объясняет фактор х10? Возможно нет...

Еще одна вещь, которую вы можете попробовать:

  • замещать fprintf() с sprintf(), печать в буфер памяти, не касаясь файла вообще. Тогда вы можете попробовать сделать fwrite() без печати Таким образом, вы можете угадать, происходит ли потеря в форматировании данных или в записи в FILE,
Другие вопросы по тегам