[Investigation] Include Guards
От: remark Россия http://www.1024cores.net/
Дата: 08.10.09 15:52
Оценка: 100 (17) +3 -1
Недавно попытался найти объективную информацию по поводу того, какого типа "include guard'ы" (в собирательном смысле) сейчас целесообразно использовать в портабельном коде. К сожалению ничего толкового найти не удалось; некоторые исследования были, но они либо используют очень старые версии компиляторов, либо не позволяют сделать объективных выводов, либо рассматривают не все варианты гардов/компиляторов, которые меня интересуют. В общем решил разобраться сам. Надеюсь будет полезно общественности, в особенности тем, кто имеет отношение к стандартам кодирования.

Тестировал 3 варианта:
1. Internal include guard
#ifndef XXX
#define XXX
...
#endif


2. Pragma once
#pragma once
...


3. External include guard (больше для полноты картины)
В дополнение к internal include guard в месте включения файла:
#ifndef XXX
#include "xxx.h"
#endif


Компиляторы:
1. cl: MSVC 2008 (15.00.21022.08)
2. g++ 4.3.2
3. icl: Intel C++ Compiler 11.0.066

Структура заголовочных файлов для тестирования представляет из себя нижнюю диагональную матрицу размера N. Т.е. есть N заголовочных файлов, i-ый файл включает в себя заголовки от i+1 до N. Компилируемый cpp файл включает в себя все заголовки. Если кого интересует, запостю исходный код генератора отдельным постом. Использовал N=100, 195, 400. 195, т.к. g++ на 200 начинает выдавать ошибку компиляции.
Во время тестирования запускал компиляцию 4 раза, потом усреднял последние 3 запуска. Для замера времени использовалась внешняя команда time.

Результаты (все времена в миллисекундах):

cl
N=100N=195N=400
internal include guard375123231656
pragma once1723124830
external include guard125156957
g++
N=100N=195N=400
internal include guard421453-
pragma once452468-
external include guard452468-
icl
N=100N=195N=400
internal include guard1402022045
pragma once1402032028
external include guard1401561232
Выводы:
Для cl pragma once даёт существенное преимущество перед internal include guard. external include guard обрабатывается ещё лучше.
g++ практически одинаково поддерживает все 3 варианта. Однако internal include guard несколько лучше остальных.
icl одинаково поддерживает internal include guard и pragma once. external include guard даёт некоторое преимущество.

Учитывая результаты и то, что pragma once использовать максимально просто и она поддерживается всеми этими компиляторами, а internal и external include guard дольше писать и есть вероятность ошибок, лично для меня вывод очевиден: использовать только pragma once.


1024cores — all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re: Исходный код генератора
От: remark Россия http://www.1024cores.net/
Дата: 08.10.09 15:59
Оценка:
Исходный код генератора:

#include <stdio.h>
#include <stdlib.h>
#include <direct.h> 

int main(int argc, char** argv)
{
    size_t N = 10;
    if (argc > 1 && atoi(argv[1]) > 0)
        N = atoi(argv[1]);

    {
        char buf [1024];
        sprintf(buf, "noguard%u", N);
        _mkdir(buf);
        _chdir(buf);

        for (size_t i = 0; i != N; i += 1)
        {
            char buf [1024];
            sprintf(buf, "header_%u.h", i);
            FILE* f = fopen(buf, "w");
            for (size_t j = i + 1; j != N; j += 1)
            {
                fprintf(f, "#include \"header_%u.h\"\n", j);
            }
            fprintf(f, "\n");
            fprintf(f, "extern int i;\n\n");
            fclose(f);
        }

        FILE* f = fopen("main.cpp", "w");
        for (size_t i = 0; i != N; i += 1)
        {
            fprintf(f, "#include \"header_%u.h\"\n", i);
        }
        fprintf(f, "\n");
        fprintf(f, "int i = 0;\n\n");
        fprintf(f, "int main() {}\n\n");
        fclose(f);

        _chdir("..");
    }

    {
        char buf [1024];
        sprintf(buf, "pragma%u", N);
        _mkdir(buf);
        _chdir(buf);

        for (size_t i = 0; i != N; i += 1)
        {
            char buf [1024];
            sprintf(buf, "header_%u.h", i);
            FILE* f = fopen(buf, "w");
            fprintf(f, "#pragma once\n\n");
            for (size_t j = i + 1; j != N; j += 1)
            {
                fprintf(f, "#include \"header_%u.h\"\n", j);
            }
            fprintf(f, "\n");
            fprintf(f, "extern int i;\n\n");
            fclose(f);
        }

        FILE* f = fopen("main.cpp", "w");
        for (size_t i = 0; i != N; i += 1)
        {
            fprintf(f, "#include \"header_%u.h\"\n", i);
        }
        fprintf(f, "\n");
        fprintf(f, "int i = 0;\n\n");
        fprintf(f, "int main() {}\n\n");
        fclose(f);

        _chdir("..");
    }

    {
        char buf [1024];
        sprintf(buf, "guard%u", N);
        _mkdir(buf);
        _chdir(buf);

        for (size_t i = 0; i != N; i += 1)
        {
            char buf [1024];
            sprintf(buf, "header_%u.h", i);
            FILE* f = fopen(buf, "w");
            fprintf(f, "#ifndef GUARD_%u_H\n", i);
            fprintf(f, "#define GUARD_%u_H\n\n", i);
            for (size_t j = i + 1; j != N; j += 1)
            {
                fprintf(f, "#include \"header_%u.h\"\n", j);
            }
            fprintf(f, "\n");
            fprintf(f, "extern int i;\n\n");
            fprintf(f, "#endif\n\n", i);
            fclose(f);
        }

        FILE* f = fopen("main.cpp", "w");
        for (size_t i = 0; i != N; i += 1)
        {
            fprintf(f, "#include \"header_%u.h\"\n", i);
        }
        fprintf(f, "\n");
        fprintf(f, "int i = 0;\n\n");
        fprintf(f, "int main() {}\n\n");
        fclose(f);

        _chdir("..");
    }

    {
        char buf [1024];
        sprintf(buf, "external%u", N);
        _mkdir(buf);
        _chdir(buf);

        for (size_t i = 0; i != N; i += 1)
        {
            char buf [1024];
            sprintf(buf, "header_%u.h", i);
            FILE* f = fopen(buf, "w");
            fprintf(f, "#ifndef GUARD_%u_H\n", i);
            fprintf(f, "#define GUARD_%u_H\n\n", i);
            for (size_t j = i + 1; j != N; j += 1)
            {
                fprintf(f, "#ifndef GUARD_%u_H\n", j);
                fprintf(f, "#include \"header_%u.h\"\n", j);
                fprintf(f, "#endif\n");
            }
            fprintf(f, "\n");
            fprintf(f, "extern int i;\n\n");
            fprintf(f, "#endif\n\n", i);
            fclose(f);
        }

        FILE* f = fopen("main.cpp", "w");
        for (size_t i = 0; i != N; i += 1)
        {
            fprintf(f, "#ifndef GUARD_%u_H\n", i);
            fprintf(f, "#include \"header_%u.h\"\n", i);
            fprintf(f, "#endif\n");
        }
        fprintf(f, "\n");
        fprintf(f, "int i = 0;\n\n");
        fprintf(f, "int main() {}\n\n");
        fclose(f);

        _chdir("..");
    }
}



1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re: [Investigation] Include Guards
От: Vamp Россия  
Дата: 08.10.09 16:10
Оценка:
На самом деле, прагма — это вообще очень странное решение. Я понимаю, что препроцессор как-бы очень тупой, и в классическом варианте не знает, что он уже включал, а что нет — отсюда ifndef — define. Но когда препроцессор умнеет и начинает понимать, что он уже включал, а что нет — то нафига отдельно требовать от программиста указывать прагмы, ведь не бывает такого, чтобы один инклуд реально надо было два раза включать (ну или в крайнем случае предусмотреть отдельную директиву для РАЗРЕШЕНИЯ множественного включения). Кстати, есть мнение, что прагма обломает себе зубы на множественном включении через разные ссылки.
Да здравствует мыло душистое и веревка пушистая.
Re[2]: [Investigation] Include Guards
От: remark Россия http://www.1024cores.net/
Дата: 08.10.09 16:28
Оценка:
Здравствуйте, Vamp, Вы писали:

V>На самом деле, прагма — это вообще очень странное решение. Я понимаю, что препроцессор как-бы очень тупой, и в классическом варианте не знает, что он уже включал, а что нет — отсюда ifndef — define. Но когда препроцессор умнеет и начинает понимать, что он уже включал, а что нет — то нафига отдельно требовать от программиста указывать прагмы, ведь не бывает такого, чтобы один инклуд реально надо было два раза включать (ну или в крайнем случае предусмотреть отдельную директиву для РАЗРЕШЕНИЯ множественного включения).



Конечно же такое бывает, что один файл включают несколько раз. Очень полезно для генерации кода. Иногда из одного и того же фрагмента файла получают разный результирующий код за счёт разных наборов определенных макросов. А иногда просто "подцепляют" из файла разные фрагменты.

Когда компиляторы поумнели просто уже ничего нельзя было сделать из-за требований обратной совместимости. И тут pragma once выглядит вполне разумно, чего в ней странного?

А так в целом, меня тоже посещала мысль, что в идеальном мире надо было сделать *разрешение* многократного включения. А по-умолчанию запретить... ну собственно в Java/C# так и сделано.


V>Кстати, есть мнение, что прагма обломает себе зубы на множественном включении через разные ссылки.


Ну мненния по поводу таких моментов мало интересуют.



1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[3]: [Investigation] Include Guards
От: Vamp Россия  
Дата: 08.10.09 16:36
Оценка:
V>>Кстати, есть мнение, что прагма обломает себе зубы на множественном включении через разные ссылки.
R>Ну мненния по поводу таких моментов мало интересуют.
Повезло тебе
Да здравствует мыло душистое и веревка пушистая.
Re: [Investigation] Include Guards
От: Vain Россия google.ru
Дата: 08.10.09 17:26
Оценка:
Здравствуйте, remark, Вы писали:

R>Тестировал 3 варианта:

Не хватает ещё парочки, — смеси include guards и pragma once. Имхо, так чаще пишут, чем просто отдельно include guard или только pragma once.
R>
[In theory there is no difference between theory and practice. In
practice there is.]
[Даю очевидные ответы на риторические вопросы]
Re: [Investigation] Include Guards
От: Erop Россия  
Дата: 08.10.09 18:17
Оценка: 10 (2)
Здравствуйте, remark, Вы писали:

R>Учитывая результаты и то, что pragma once использовать максимально просто и она поддерживается всеми этими компиляторами, а internal и external include guard дольше писать и есть вероятность ошибок, лично для меня вывод очевиден: использовать только pragma once.


R>


C pragma once есть проблема -- в CodeWarrior у этой прагмы другая семантика. Во всяком случае раньше (во времена классической MAC OS) была другая.
Конкретно такая. Она ставилась в on или в off НА ЕДИНИЦУ ТРАНСЛЯЦИИ. И влияла, соответственно, на все include в единице трансляции после своего переключения...

Кстати, лично мне, поэтому, больше всего нравится использование внутреннего гарда + pragma once.
Я использую простенький плагинчик, который добавляет новый хедер/cpp'щник к проекту. Он по шаблончику заполняет эти файлы. В частности пишет гарды, прагмы, копирайты, и краткое описание что там лежит, ну и время и дату создания + пользователя, породившего этот исходник
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Re: [Investigation] Include Guards
От: D14  
Дата: 08.10.09 19:06
Оценка:
Здравствуйте, remark, Вы писали:

R>Учитывая результаты и то, что pragma once использовать максимально просто и она поддерживается всеми этими компиляторами, а internal и external include guard дольше писать и есть вероятность ошибок, лично для меня вывод очевиден: использовать только pragma once.


Eсли есть Visual Assist, который умеет сам вписывать internal guard'ы, то вывод совсем не очевиден
Re: [Investigation] Include Guards
От: remark Россия http://www.1024cores.net/
Дата: 08.10.09 20:25
Оценка: 8 (2)
Добавил вариант guard/pragma:
#ifndef XXX
#define XXX
#ifdef USE_PRAGMA_ONCE
#pragma once
#endif
...
#endif


и pragma/guard:
#ifdef USE_PRAGMA_ONCE
#pragma once
#endif
#ifndef XXX
#define XXX
...
#endif


А так же исправил все времена для N=400, они все были слишком высокие, сейчас перемерял, получаются значительно ниже.


Результаты (все времена в миллисекундах):

cl
N=100N=195N=400
internal include guard37512326615
pragma once172312999
guard/pragma1723281030
pragma/guard1723271030
external include guard125156219
g++
N=100N=195N=400
internal include guard421453-
pragma once452468-
guard/pragma421468-
pragma/guard452468-
external include guard452468-
icl
N=100N=195N=400
internal include guard140202452
pragma once140203436
guard/pragma140203452
pragma/guard140203483
external include guard140156280

1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re: [Investigation] Include Guards
От: gear nuke  
Дата: 08.10.09 23:37
Оценка:
Здравствуйте, remark, Вы писали:

R>Результаты (все времена в миллисекундах):


cl
N=100N=195N=400
internal include guard375123231656
pragma once1723124830
external include guard125156957
У меня 14.00.50727.762 на N=400
internal include guard ~ 16 сек.
pragma once ~ 5 сек.

30 сек надо на N=500

Разница похоже из-за вдвое меньшего seek у HDD WD VelociRaptor (вот такое тупое решение я нашел, когда скорость компиляции начала напрягать).

Кто-нибудь может проверить на быстром твердотельном диске?
.
People who are more than casually interested in computers should have at least some idea of what the underlying hardware is like. Otherwise the programs they write will be pretty weird (c) D.Knuth
Re[4]: [Investigation] Include Guards
От: remark Россия http://www.1024cores.net/
Дата: 09.10.09 06:47
Оценка:
Здравствуйте, Vamp, Вы писали:

V>>>Кстати, есть мнение, что прагма обломает себе зубы на множественном включении через разные ссылки.

R>>Ну мненния по поводу таких моментов мало интересуют.
V>Повезло тебе

Я имею в виде, что оно либо работает или нет, а мнения и везение на это не влияют.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[2]: [Investigation] Include Guards
От: remark Россия http://www.1024cores.net/
Дата: 09.10.09 06:48
Оценка:
Здравствуйте, Vain, Вы писали:

V>Здравствуйте, remark, Вы писали:


R>>Тестировал 3 варианта:

V>Не хватает ещё парочки, — смеси include guards и pragma once. Имхо, так чаще пишут, чем просто отдельно include guard или только pragma once.

Сделал:
http://www.rsdn.ru/forum/cpp/3562559.1.aspx
Автор: remark
Дата: 09.10.09


ИМХО смысла большого использовать смесь нет.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[2]: [Investigation] Include Guards
От: remark Россия http://www.1024cores.net/
Дата: 09.10.09 06:51
Оценка: +2
Здравствуйте, D14, Вы писали:

D14>Здравствуйте, remark, Вы писали:


R>>Учитывая результаты и то, что pragma once использовать максимально просто и она поддерживается всеми этими компиляторами, а internal и external include guard дольше писать и есть вероятность ошибок, лично для меня вывод очевиден: использовать только pragma once.


D14>Eсли есть Visual Assist, который умеет сам вписывать internal guard'ы, то вывод совсем не очевиден


Ну а плюсы-то какие?
Заткнули проблемы с муторностью набора инклюд гардов. Осталась проблема медленной компиляции. Осталась проблема, если файл скопи-пастили. Осталась проблема, если надо работать на машине, где не установлен Assist. Осталась проблема, если у других членов команды не установлен Assist. Плюсов не появилось.
ИМХО решение не выглядит очень сильным.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[2]: [Investigation] Include Guards
От: remark Россия http://www.1024cores.net/
Дата: 09.10.09 06:52
Оценка:
Здравствуйте, Erop, Вы писали:

R>>Учитывая результаты и то, что pragma once использовать максимально просто и она поддерживается всеми этими компиляторами, а internal и external include guard дольше писать и есть вероятность ошибок, лично для меня вывод очевиден: использовать только pragma once.


E>C pragma once есть проблема -- в CodeWarrior у этой прагмы другая семантика. Во всяком случае раньше (во времена классической MAC OS) была другая.

E>Конкретно такая. Она ставилась в on или в off НА ЕДИНИЦУ ТРАНСЛЯЦИИ. И влияла, соответственно, на все include в единице трансляции после своего переключения...

E>Кстати, лично мне, поэтому, больше всего нравится использование внутреннего гарда + pragma once.

E>Я использую простенький плагинчик, который добавляет новый хедер/cpp'щник к проекту. Он по шаблончику заполняет эти файлы. В частности пишет гарды, прагмы, копирайты, и краткое описание что там лежит, ну и время и дату создания + пользователя, породившего этот исходник

Универсального решения тут наверное и нет. Каждому решать самому.
Если надо работать со старым CodeWarrior, ну что ж...


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[2]: [Investigation] Include Guards
От: remark Россия http://www.1024cores.net/
Дата: 09.10.09 06:59
Оценка:
Здравствуйте, gear nuke, Вы писали:

GN>У меня 14.00.50727.762 на N=400

GN>internal include guard ~ 16 сек.
GN>pragma once ~ 5 сек.

GN>30 сек надо на N=500


GN>Разница похоже из-за вдвое меньшего seek у HDD WD VelociRaptor (вот такое тупое решение я нашел, когда скорость компиляции начала напрягать).


GN>Кто-нибудь может проверить на быстром твердотельном диске?


Вот тут я перемерил для N=400:
http://www.rsdn.ru/forum/cpp/3562559.1.aspx
Автор: remark
Дата: 09.10.09


Для cl, N=400, internal include guard — 6615, pragma once — 999.
Диск — обычный SATA винчестер.


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Re[5]: [Investigation] Include Guards
От: Vamp Россия  
Дата: 09.10.09 13:15
Оценка:
R>Я имею в виде, что оно либо работает или нет, а мнения и везение на это не влияют.
А оно работает? Я просто не представляю, как оно может разрулить жесткие ссылки.
Да здравствует мыло душистое и веревка пушистая.
Re: [Investigation] Include Guards: linux + gcc 4.4.1
От: zaufi Земля  
Дата: 09.10.09 23:12
Оценка:
Тестовая платформа
zaufi ~ $ uname -a
Linux zaufi 2.6.31-gentoo-r1 #1 SMP PREEMPT Tue Sep 29 05:14:19 MSD 2009 x86_64 Intel(R) Core(TM)2 Duo CPU T7300 @ 2.00GHz GenuineIntel GNU/Linux

zaufi ~ $ g++ --version | head -n 1
g++ (Gentoo 4.4.1 p1.0) 4.4.1

# это флаги с которыми собран сам компилятор
zaufi ~ $ cat /etc/paludis/bashrc | grep CFLAGS | head -n 1
CFLAGS="-march=native -O2 -pipe -ftree-vectorize -falign-jumps=64 -falign-functions=64 -minline-stringops-dynamically"

zaufi ~ $ sudo hdparm -t /dev/sda

/dev/sda:
 Timing buffered disk reads:  174 MB in  3.01 seconds =  57.73 MB/sec

zaufi ~ $ mount | grep /dev/root
/dev/root on / type reiserfs (rw,noatime)


Результаты (все времена в миллисекундах):

в таблице приведены средние результаты после 5ти измерений. как и в оригинальном эксперименте сначала была выполнена "прогревочная" компиляция результат которой не вошел в среднее арифметическое.

g++-4.4.1
N=100N=195N=400
internal include guard8996-
pragma once82100-
external include guard84109-
Выводы:

родная ОС для GNU gcc все таки линукс


Re[3]: [Investigation] Include Guards
От: D14  
Дата: 10.10.09 05:02
Оценка: 4 (3)
Здравствуйте, remark, Вы писали:

R>>> internal и external include guard дольше писать и есть вероятность ошибок, лично для меня вывод очевиден: использовать только pragma once.

D14>>Eсли есть Visual Assist, который умеет сам вписывать internal guard'ы, то вывод совсем не очевиден
R>Ну а плюсы-то какие?
R>Заткнули проблемы с муторностью набора инклюд гардов.
R>Осталась проблема медленной компиляции.
А кто определил, что причиной медленной компиляции является повторное включение?
Медленная компиляция в первую очередь обусловлена сложностью синтаксиса С++ и выразительностью compile-time средств языка, позволяющих сделать время компиляции сколь угодно большим при сохранения компактности исходного кода.
Например, какую часть займет обработка include в таком фрагменте? Ноль целых хрен десятых.
#include <iostream>
template <int i> struct L;
template <int i> struct R;
template <> struct L<0>{enum{val=0};};
template <> struct R<0>{enum{val=0};};
template <int i> struct L:virtual L<i-1>,virtual R<i-1>{enum{val=L<i-1>::val+R<i-1>::val};};
template <int i> struct R:virtual L<i-1>,virtual R<i-1>{enum{val=L<i-1>::val+R<i-1>::val};};
int main()
{
   const int x=L<30>::val;
   std::cout << x << std::endl;
   return 0;
}

R>Осталась проблема, если файл скопи-пастили.
Ну, положим даже решили одну и энного количества проблем, которые могут возникнуть при копипасте.

R>Осталась проблема, если надо работать на машине, где не установлен Assist. Осталась проблема, если у других членов команды не установлен Assist. Плюсов не появилось.


Тогда ой.
Re[3]: [Investigation] Include Guards
От: gear nuke  
Дата: 12.10.09 15:54
Оценка:
Здравствуйте, remark, Вы писали:

R>Вот тут я перемерил для N=400:

R>http://www.rsdn.ru/forum/cpp/3562559.1.aspx
Автор: remark
Дата: 09.10.09


R>Для cl, N=400, internal include guard — 6615, pragma once — 999.


Интересно, откуда взялась такая погрешность измерений. Или за счёт чего разные цифры?
.
People who are more than casually interested in computers should have at least some idea of what the underlying hardware is like. Otherwise the programs they write will be pretty weird (c) D.Knuth
Re[4]: [Investigation] Include Guards
От: remark Россия http://www.1024cores.net/
Дата: 12.10.09 16:08
Оценка:
Здравствуйте, gear nuke, Вы писали:

GN>Здравствуйте, remark, Вы писали:


R>>Вот тут я перемерил для N=400:

R>>http://www.rsdn.ru/forum/cpp/3562559.1.aspx
Автор: remark
Дата: 09.10.09


R>>Для cl, N=400, internal include guard — 6615, pragma once — 999.


GN>Интересно, откуда взялась такая погрешность измерений. Или за счёт чего разные цифры?


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


1024cores &mdash; all about multithreading, multicore, concurrency, parallelism, lock-free algorithms
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.