Подтвердить что ты не робот

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

4b9b3361

Ответ 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() для использования быстрой (несовместимой) функции.