Недавно попытался найти объективную информацию по поводу того, какого типа "include guard'ы" (в собирательном смысле) сейчас целесообразно использовать в портабельном коде. К сожалению ничего толкового найти не удалось; некоторые исследования были, но они либо используют очень старые версии компиляторов, либо не позволяют сделать объективных выводов, либо рассматривают не все варианты гардов/компиляторов, которые меня интересуют. В общем решил разобраться сам. Надеюсь будет полезно общественности, в особенности тем, кто имеет отношение к стандартам кодирования.
Тестировал 3 варианта:
1. Internal include guard
#ifndef XXX
#define XXX
...
#endif
2. Pragma once
#pragma once
...
3. External include guard (больше для полноты картины)
В дополнение к internal include guard в месте включения файла:
Структура заголовочных файлов для тестирования представляет из себя нижнюю диагональную матрицу размера N. Т.е. есть N заголовочных файлов, i-ый файл включает в себя заголовки от i+1 до N. Компилируемый cpp файл включает в себя все заголовки. Если кого интересует, запостю исходный код генератора отдельным постом. Использовал N=100, 195, 400. 195, т.к. g++ на 200 начинает выдавать ошибку компиляции.
Во время тестирования запускал компиляцию 4 раза, потом усреднял последние 3 запуска. Для замера времени использовалась внешняя команда time.
Результаты (все времена в миллисекундах):
cl
N=100
N=195
N=400
internal include guard
375
1232
31656
pragma once
172
312
4830
external include guard
125
156
957
g++
N=100
N=195
N=400
internal include guard
421
453
-
pragma once
452
468
-
external include guard
452
468
-
icl
N=100
N=195
N=400
internal include guard
140
202
2045
pragma once
140
203
2028
external include guard
140
156
1232
Выводы:
Для 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.
На самом деле, прагма — это вообще очень странное решение. Я понимаю, что препроцессор как-бы очень тупой, и в классическом варианте не знает, что он уже включал, а что нет — отсюда ifndef — define. Но когда препроцессор умнеет и начинает понимать, что он уже включал, а что нет — то нафига отдельно требовать от программиста указывать прагмы, ведь не бывает такого, чтобы один инклуд реально надо было два раза включать (ну или в крайнем случае предусмотреть отдельную директиву для РАЗРЕШЕНИЯ множественного включения). Кстати, есть мнение, что прагма обломает себе зубы на множественном включении через разные ссылки.
Здравствуйте, Vamp, Вы писали:
V>На самом деле, прагма — это вообще очень странное решение. Я понимаю, что препроцессор как-бы очень тупой, и в классическом варианте не знает, что он уже включал, а что нет — отсюда ifndef — define. Но когда препроцессор умнеет и начинает понимать, что он уже включал, а что нет — то нафига отдельно требовать от программиста указывать прагмы, ведь не бывает такого, чтобы один инклуд реально надо было два раза включать (ну или в крайнем случае предусмотреть отдельную директиву для РАЗРЕШЕНИЯ множественного включения).
Конечно же такое бывает, что один файл включают несколько раз. Очень полезно для генерации кода. Иногда из одного и того же фрагмента файла получают разный результирующий код за счёт разных наборов определенных макросов. А иногда просто "подцепляют" из файла разные фрагменты.
Когда компиляторы поумнели просто уже ничего нельзя было сделать из-за требований обратной совместимости. И тут pragma once выглядит вполне разумно, чего в ней странного?
А так в целом, меня тоже посещала мысль, что в идеальном мире надо было сделать *разрешение* многократного включения. А по-умолчанию запретить... ну собственно в Java/C# так и сделано.
V>Кстати, есть мнение, что прагма обломает себе зубы на множественном включении через разные ссылки.
Ну мненния по поводу таких моментов мало интересуют.
V>>Кстати, есть мнение, что прагма обломает себе зубы на множественном включении через разные ссылки. R>Ну мненния по поводу таких моментов мало интересуют.
Повезло тебе
Здравствуйте, 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.]
[Даю очевидные ответы на риторические вопросы]
Здравствуйте, remark, Вы писали:
R>Учитывая результаты и то, что pragma once использовать максимально просто и она поддерживается всеми этими компиляторами, а internal и external include guard дольше писать и есть вероятность ошибок, лично для меня вывод очевиден: использовать только pragma once.
R>
C pragma once есть проблема -- в CodeWarrior у этой прагмы другая семантика. Во всяком случае раньше (во времена классической MAC OS) была другая.
Конкретно такая. Она ставилась в on или в off НА ЕДИНИЦУ ТРАНСЛЯЦИИ. И влияла, соответственно, на все include в единице трансляции после своего переключения...
Кстати, лично мне, поэтому, больше всего нравится использование внутреннего гарда + pragma once.
Я использую простенький плагинчик, который добавляет новый хедер/cpp'щник к проекту. Он по шаблончику заполняет эти файлы. В частности пишет гарды, прагмы, копирайты, и краткое описание что там лежит, ну и время и дату создания + пользователя, породившего этот исходник
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, remark, Вы писали:
R>Учитывая результаты и то, что pragma once использовать максимально просто и она поддерживается всеми этими компиляторами, а internal и external include guard дольше писать и есть вероятность ошибок, лично для меня вывод очевиден: использовать только pragma once.
Eсли есть Visual Assist, который умеет сам вписывать internal guard'ы, то вывод совсем не очевиден
Здравствуйте, remark, Вы писали:
R>Результаты (все времена в миллисекундах):
cl
N=100
N=195
N=400
internal include guard
375
1232
31656
pragma once
172
312
4830
external include guard
125
156
957
У меня 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
Здравствуйте, Vamp, Вы писали:
V>>>Кстати, есть мнение, что прагма обломает себе зубы на множественном включении через разные ссылки. R>>Ну мненния по поводу таких моментов мало интересуют. V>Повезло тебе
Я имею в виде, что оно либо работает или нет, а мнения и везение на это не влияют.
Здравствуйте, Vain, Вы писали:
V>Здравствуйте, remark, Вы писали:
R>>Тестировал 3 варианта: V>Не хватает ещё парочки, — смеси include guards и pragma once. Имхо, так чаще пишут, чем просто отдельно include guard или только pragma once.
Здравствуйте, D14, Вы писали:
D14>Здравствуйте, remark, Вы писали:
R>>Учитывая результаты и то, что pragma once использовать максимально просто и она поддерживается всеми этими компиляторами, а internal и external include guard дольше писать и есть вероятность ошибок, лично для меня вывод очевиден: использовать только pragma once.
D14>Eсли есть Visual Assist, который умеет сам вписывать internal guard'ы, то вывод совсем не очевиден
Ну а плюсы-то какие?
Заткнули проблемы с муторностью набора инклюд гардов. Осталась проблема медленной компиляции. Осталась проблема, если файл скопи-пастили. Осталась проблема, если надо работать на машине, где не установлен Assist. Осталась проблема, если у других членов команды не установлен Assist. Плюсов не появилось.
ИМХО решение не выглядит очень сильным.
Здравствуйте, 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, ну что ж...
Здравствуйте, 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>Кто-нибудь может проверить на быстром твердотельном диске?
R>Я имею в виде, что оно либо работает или нет, а мнения и везение на это не влияют.
А оно работает? Я просто не представляю, как оно может разрулить жесткие ссылки.
Да здравствует мыло душистое и веревка пушистая.
Re: [Investigation] Include Guards: linux + gcc 4.4.1
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ти измерений. как и в оригинальном эксперименте сначала была выполнена "прогревочная" компиляция результат которой не вошел в среднее арифметическое.
Здравствуйте, remark, Вы писали:
R>>> internal и external include guard дольше писать и есть вероятность ошибок, лично для меня вывод очевиден: использовать только pragma once. D14>>Eсли есть Visual Assist, который умеет сам вписывать internal guard'ы, то вывод совсем не очевиден R>Ну а плюсы-то какие? R>Заткнули проблемы с муторностью набора инклюд гардов. R>Осталась проблема медленной компиляции.
А кто определил, что причиной медленной компиляции является повторное включение?
Медленная компиляция в первую очередь обусловлена сложностью синтаксиса С++ и выразительностью compile-time средств языка, позволяющих сделать время компиляции сколь угодно большим при сохранения компактности исходного кода.
Например, какую часть займет обработка include в таком фрагменте? Ноль целых хрен десятых.
R>Осталась проблема, если файл скопи-пастили.
Ну, положим даже решили одну и энного количества проблем, которые могут возникнуть при копипасте.
R>Осталась проблема, если надо работать на машине, где не установлен Assist. Осталась проблема, если у других членов команды не установлен Assist. Плюсов не появилось.
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
R>>Для cl, N=400, internal include guard — 6615, pragma once — 999.
GN>Интересно, откуда взялась такая погрешность измерений. Или за счёт чего разные цифры?
Я подозреваю, что первый раз я включил режим пониженного энергопотребления. Или фрагментация диска запустилась.
Здравствуйте, remark, Вы писали:
R>Недавно попытался найти объективную информацию по поводу того, какого типа "include guard'ы" (в собирательном смысле) сейчас целесообразно
Есть небольшая проблема в том что pragma once не везде работает.
В моей версии Codewarrior for Wii если написать
#include "mycoolheader.h"
#include "./mycoolheader.h"
то он включится 2 раза.
с ifdef'ами все ОК.
Понятно что баг препроцессора, но иногда такие моменты встречаются в случае компиляции специфичными средами.
Добавлю, что добавление комментариев перед внутренним гардом в gcc не меняет скорости компиляции. Что-то мне сдаётся, что в gcc вообще оптимизации инклюдов нет... Кто знает точно?
Здравствуйте, skeptik_, Вы писали:
_>Добавлю, что добавление комментариев перед внутренним гардом в gcc не меняет скорости компиляции. Что-то мне сдаётся, что в gcc вообще оптимизации инклюдов нет... Кто знает точно?
всё в нем есть (читайте доки, они рулез):
CPP optimizes even further. It remembers when a header file has a wrapper `#ifndef'. If a subsequent `#include' specifies that header, and the macro in the `#ifndef' is still defined, it does not bother to rescan the file at all.
You can put comments outside the wrapper. They will not interfere with this optimization.