После участия в расширенной дискуссии о предполагаемом компромиссе производительности между #pragma once
и #ifndef
охранниками против аргумента правильности или нет (я принимал сторону #pragma once
, основываясь на некоторой относительно недавней идеологической обработке с этой целью ), Я решил наконец проверить теорию, что #pragma once
быстрее, потому что компилятору не нужно пытаться повторно #include
файл, который уже был включен.
Для теста я автоматически сгенерировал 500 файлов заголовков со сложной взаимозависимостью и получил файл .c
, который #include
их всех. Я выполнил тест тремя способами: один раз с #ifndef
, один раз с #pragma once
и один раз с обоими. Я выполнил тест на довольно современной системе (MacBook Pro 2014 года, работающий под управлением OSX, использующий Clang в комплекте с XCode, с внутренним SSD).
Сначала тестовый код:
#include <stdio.h>
//#define IFNDEF_GUARD
//#define PRAGMA_ONCE
int main(void)
{
int i, j;
FILE* fp;
for (i = 0; i < 500; i++) {
char fname[100];
snprintf(fname, 100, "include%d.h", i);
fp = fopen(fname, "w");
#ifdef IFNDEF_GUARD
fprintf(fp, "#ifndef _INCLUDE%d_H\n#define _INCLUDE%d_H\n", i, i);
#endif
#ifdef PRAGMA_ONCE
fprintf(fp, "#pragma once\n");
#endif
for (j = 0; j < i; j++) {
fprintf(fp, "#include \"include%d.h\"\n", j);
}
fprintf(fp, "int foo%d(void) { return %d; }\n", i, i);
#ifdef IFNDEF_GUARD
fprintf(fp, "#endif\n");
#endif
fclose(fp);
}
fp = fopen("main.c", "w");
for (int i = 0; i < 100; i++) {
fprintf(fp, "#include \"include%d.h\"\n", i);
}
fprintf(fp, "int main(void){int n;");
for (int i = 0; i < 100; i++) {
fprintf(fp, "n += foo%d();\n", i);
}
fprintf(fp, "return n;}");
fclose(fp);
return 0;
}
А теперь мои различные тесты:
folio[~/Desktop/pragma] fluffy$ gcc pragma.c -DIFNDEF_GUARD
folio[~/Desktop/pragma] fluffy$ ./a.out
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null
real 0m0.164s
user 0m0.105s
sys 0m0.041s
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null
real 0m0.140s
user 0m0.097s
sys 0m0.018s
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null
real 0m0.193s
user 0m0.143s
sys 0m0.024s
folio[~/Desktop/pragma] fluffy$ gcc pragma.c -DPRAGMA_ONCE
folio[~/Desktop/pragma] fluffy$ ./a.out
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null
real 0m0.153s
user 0m0.101s
sys 0m0.031s
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null
real 0m0.170s
user 0m0.109s
sys 0m0.033s
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null
real 0m0.155s
user 0m0.105s
sys 0m0.027s
folio[~/Desktop/pragma] fluffy$ gcc pragma.c -DPRAGMA_ONCE -DIFNDEF_GUARD
folio[~/Desktop/pragma] fluffy$ ./a.out
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null
real 0m0.153s
user 0m0.101s
sys 0m0.027s
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null
real 0m0.181s
user 0m0.133s
sys 0m0.020s
folio[~/Desktop/pragma] fluffy$ time gcc -E main.c > /dev/null
real 0m0.167s
user 0m0.119s
sys 0m0.021s
folio[~/Desktop/pragma] fluffy$ gcc --version
Configured with: --prefix=/Applications/Xcode.app/Contents/Developer/usr --with-gxx-include-dir=/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX10.12.sdk/usr/include/c++/4.2.1
Apple LLVM version 8.1.0 (clang-802.0.42)
Target: x86_64-apple-darwin17.0.0
Thread model: posix
InstalledDir: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin
Как вы можете видеть, версии с #pragma once
действительно были немного быстрее для предварительной обработки, чем #ifndef
-только, , но разница была весьма незначительной, и ее было бы значительно затмить времени, которое потребовалось бы для построения и связывания кода. Возможно, с достаточно большой базой кода это может привести к разнице во времени сборки в несколько секунд, но между современными компиляторами, способными оптимизировать защиту #ifndef
, тот факт, что операционные системы имеют хорошие дисковые кэши, и увеличением скорости технологии хранения Похоже, что аргумент производительности является спорным, по крайней мере, для типичной системы разработчиков в наши дни. Более старые и более экзотические среды сборки (например, заголовки, размещенные на общем сетевом ресурсе, сборка с ленты и т. Д.) Могут несколько изменить уравнение, но в этих обстоятельствах представляется более полезным просто сделать менее хрупкую среду сборки в первую очередь.
В том-то и дело, что #ifndef
стандартизирован со стандартным поведением, тогда как #pragma once
- нет, а #ifndef
также обрабатывает странные случаи с файловой системой и путями поиска, тогда как #pragma once
может очень запутаться некоторыми вещами, приводя к неправильному поведению, которое программист не может контролировать. Основная проблема с #ifndef
заключается в том, что программисты выбирают плохие имена для своих охранников (с коллизиями имен и т. Д.), И даже в этом случае потребитель API может переопределить эти плохие имена, используя #undef
- не идеальное решение, возможно, но это возможно , тогда как #pragma once
не имеет возможности обратиться, если компилятор ошибочно выбрал #include
.
Таким образом, , хотя #pragma once
явно (немного) быстрее, я не согласен, что это само по себе является причиной для его использования над #ifndef
охранниками.
РЕДАКТИРОВАТЬ : Благодаря отзывам @LightnessRacesInOrbit я увеличил количество файлов заголовков и изменил тест, чтобы он выполнял только шаг препроцессора, исключая то небольшое количество времени, которое добавлялось компиляцией и процесс ссылки (который был тривиальным раньше и не существует сейчас). Как и ожидалось, дифференциал примерно такой же.