Здравствуйте, remark, Вы писали:
D>>Так что это очень индивидуально и зависит от того сколько объектов создатеся и разрушается, включая все локальный, временые переменные и временные переменные создающиеся под условием типа "some_cond ? A() : f(B())", здесь будет созадана или временная переменная типа A или B и в конце вычисления полного выражения так переменная которая была созданна должна быть разрешена. Такого рода вещи создают очень много EH контекстов.
R>EH контекстов (фреймов) такие вещи обычно не создают. EH фрейм обычно создаётся один на функцию. Все создания объектов с деструкторами (в т.ч. "some_cond ? A() : f(B())") просто инкрементируют неявный счётчик — некий аналог счётчика команд IP. По значению этого счётчика обработчик исключения (в т.ч. неявный, который генерируется компилятором и рушит объекты) может точно сказать какие объекты сейчас живы на стеке (т.е. какие надо разрушить).
Я был не совсем точен, я не имел в виду EH frame. Я понимал под EH контекстом уникальную область, которой соответствует уникальный набор объектов, которые нужно разрушить, если возникнет исключение в данной точке. Вот маленький пример, безусловно искуственный
class A {
public:
A(int i);
~A();
};
void goo();
void foo() {
A a1(1); A a2(2); A a3(3);
goo();
A a4(4); A a5(5); A a6(6);
}
-fno-exceptions:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 1] .text PROGBITS 00000000 000034 0000c1 00 AX 0 0 4
-fexceptions:
[Nr] Name Type Addr Off Size ES Flg Lk Inf Al
[ 1] .text PROGBITS 00000000 000034 000147 00 AX 0 0 4
[ 5] .gcc_except_table PROGBITS 00000000 00017c 000043 00 A 0 0 1
[ 6] .eh_frame PROGBITS 00000000 0001c0 00003c 00 A 0 0 4
0001c6
Здравствуйте, Pzz, Вы писали:
RO>>Нет, именно над микросхемой. У нее есть некое ограниченное, и не такое уж и большое, количество циклов чтения-записи (а точнее, стирания) на каждую ячейку. FAT использует пространство очень неравномерно, соответственно, когда она протрет дыру в начале раздела, где хранится собственно таблица, наступит виндекапец. Юниксукапец наступит значительно позже, потому что там есть JFFS2, специально созданная для флеш-памяти. Это особенно важно для новомодных безвинчестерных устройств вроде Asus Eee, где сама ОС живет на флешке. (NTFS/ext3/reiserfs ситуацию не исправят, потому что тоже рассчитаны на винчестеры.)
Pzz>В принципе, с протиранием дырок на флешке можно бороться не в файловой системе, а на уровне драйвера блочного устройства. Или даже вовсе запихнуть эту логику в контроллер, который делает из флешки иллюзию IDE-диска. Тем более, что какой-то интеллект на этом уровне все равно нужен, т.к. типичный размер "сектора" (минимального куска, который можно стереть и переписать за раз) на флешке — 8-16 килобайт, а традиционные файловые системы рассчитаны на 512-байтный сектор.
На флешке проблематична операция «прочесть байт, увеличить на 1, записать обратно». Потому традиционные файловые системы мало подходят. JFFS2 же дописывает изменения на свободное место, а при необходимости делает GC, чем уменьшает количество стираний и распределяет их равномерно по флешке.
Это и есть дело ФС, а не драйвера. Если ext3 постоянно меняет номер inode или, чего доброго, отслеживает atime, никакой драйвер это не соптимизирует.
Здравствуйте, Сергей, Вы писали:
D>>Это ты привел пример того, как работает обработка исключений на Linux — там используется табличный подход. С>Думаю, это специфика компилятора, линукс здесь ни при чём.
Обычно устройство обработки исключений является частью ABI для того чтобы код скомпилированный разными компиляторами мог работать вместе. Посмотри например ABI System V i386 или ABI AMD64. В Linux ABI следуют достаточно строго. На Windows больше разнообразия, можно сказать, что MS фактически установила на нем свой ABI (не до конца документированный, в частности как раз обработка исключений не полностью документирована) поэтому на Windows больше разнообразия.
Cyberax wrote: > > > H>Конечно. Но вот насчет "реально могут быть" — я бы так не сказал. Это > H>зависит от проектировщика, от его опыта, и его видения задачи. > При чем тут видение? Нет необходимости делать методы виртуальными просто > так.
Что значит — "просто так"? Когда я пишу программы, я всегда предполагаю,
куда и как она будет развиваться в дальнейшем. Исходя из этих
соображений я могу использовать интерфейс там, даже если на первом этапе
будет всего лишь одна его реализация. И не считаю это плохой практикой.
> > H>Но тем не менее все это — цена, которую приходится платить. И она не > H>равна нулю. Понятно, что в некоторых условиях получаемый выигрыш > H>существенно перевешивает затраты. > Так ведь в чистом С затраты будут тоже, и зачастую ну точно такие же, > как и в С++.
Ну кто-же спорит? Конечно будут. Я еще раз повторяю — C обычно
заставляет программистов использовать другие парадигмы, нежели C++.
Программы на C обычно более специализированны под конкретную область,
что делает их более эффективными, но создает сложности при попытке
расширения и модификации программы. Особенно, если требуется заменить
некоторое проектное решение.
Кроме того, если еще такая вещь, как инертность мышления. Когда
концентрируешься на определенных парадигмах решения задач, многие
последовательные решения выполняются в едином стиле, несмотря на то, что
некоторые из них было бы эффективнее выполнить в другом.
> > H>Я же написал о другом — об объеме требуемого машинного кода. В > H>приведенном примере компилятор не знает, может ли funcb генерировать > H>исключения, поэтому обязан всегда обрабатывать исключения чтобы > H>корректно отработал деструктор. > Нет. Тебе стоит узнать как работают исключения. Ладно, делаем тест:
Ну что-же, не буду врать, не знал, что gcc умеет отслеживает контекст
исполнения по адресу, а не по счетчику. Но тем не менее, исключения так
работают далеко не всегда. Хочешь приведу то, что генерирует тот-же
mingw-gcc 3.4.2, который использует sjlj обработку исключений? Или
поверишь на слово, что есть разница?
D>Я был не совсем точен, я не имел в виду EH frame. Я понимал под EH контекстом уникальную область, которой соответствует уникальный набор объектов, которые нужно разрушить, если возникнет исключение в данной точке.
Для поддержания этого EH контекста достаточно просто инкрементировать переменную. При этом если производится ручное управление ресурсами, то в общем случае надо вручную поддерживать эту же переменную.
D>Вот маленький пример, безусловно искуственный
Это две разные программы, имеющие разную семантику. Очевидно, что они будут иметь разный размер.
Здравствуйте, Roman Odaisky, Вы писали:
RO>На флешке проблематична операция «прочесть байт, увеличить на 1, записать обратно». Потому традиционные файловые системы мало подходят. JFFS2 же дописывает изменения на свободное место, а при необходимости делает GC, чем уменьшает количество стираний и распределяет их равномерно по флешке.
За описание принципов работы jffs2 спасибо, но я как-бы в курсе.
Представьте себе, что мы берем jffs2 и используем ее следующим образом: создаем файлы с именами от 0 до N, каждый файл — 512 байт. Представили? Теперь представьте себе, что мы делаем block device, который каждый 512-байтный сектор хранит в отдельном таком файле. А теперь выкинем из этой конструкции все лишнее — имена файлов, например.
В итоге мы получили блочный девайс, на котором можно держать не предназначенную для флешки файловую систему, и при этом она не будет протирать дырки во флешке, или мучаться из-за неподходящего размера блока.
Мне как-то всегда казалось, что когда из флешки делают IDE disk, то это делают каким-то примерно таким образом. В противном случае я не понимаю, они что, ради каждого записанного байта трут весь 8-килобайтный сектор? Как-то слабо верится...
Здравствуйте, remark, Вы писали:
D>>Я был не совсем точен, я не имел в виду EH frame. Я понимал под EH контекстом уникальную область, которой соответствует уникальный набор объектов, которые нужно разрушить, если возникнет исключение в данной точке. R>Для поддержания этого EH контекста достаточно просто инкрементировать переменную. При этом если производится ручное управление ресурсами, то в общем случае надо вручную поддерживать эту же переменную.
Как тут уже много говорили есть схемы основанные на табличной обработке исключения, она используется обычно на Linux. Для нее нет нужды инкриментировать ничего. По IP адресу точки вызова вычисляется контекст и адрес обработчика для раскрутки стека. Инкримент, про который ты пишешь, может быть достаточно дорог, это как минимум еще одна дополнительная переменная, для X86 с малым числом регистров это может быть весьма существенно — появление spill/fill для этой переменной, кроме того между вызовами она обязана быть на стеке, чтобы ее мог достать обработчик проводящий раскрутку стека. Сказать что табличный подход или подход со счетчиками лучше однозначно нельзя — это зависит от множества факторов.
D>>Вот маленький пример, безусловно искуственный R>Это две разные программы, имеющие разную семантику. Очевидно, что они будут иметь разный размер.
Когда компилятор компилирует этот модуль у него нет возможности узнать что поддержка для обработки исключений тут может быть не нужна. Единственная возможность узнать это компилятору — это ключ пользователя. Семантика это наблюдаемое поведение программы, есть множество программ, для которых семантика программы не меняется от включения или выключения опции поддержки исключений.
Здравствуйте, dupamid, Вы писали:
D>Здравствуйте, remark, Вы писали:
D>>>Я был не совсем точен, я не имел в виду EH frame. Я понимал под EH контекстом уникальную область, которой соответствует уникальный набор объектов, которые нужно разрушить, если возникнет исключение в данной точке. R>>Для поддержания этого EH контекста достаточно просто инкрементировать переменную. При этом если производится ручное управление ресурсами, то в общем случае надо вручную поддерживать эту же переменную.
D>Как тут уже много говорили есть схемы основанные на табличной обработке исключения, она используется обычно на Linux. Для нее нет нужды инкриментировать ничего. По IP адресу точки вызова вычисляется контекст и адрес обработчика для раскрутки стека.
Ты сам сказал про создание контекстов в ран-тайм, поэтому я подумал, что мы говорим про студию
D>Инкримент, про который ты пишешь, может быть достаточно дорог, это как минимум еще одна дополнительная переменная, для X86 с малым числом регистров это может быть весьма существенно — появление spill/fill для этой переменной, кроме того между вызовами она обязана быть на стеке, чтобы ее мог достать обработчик проводящий раскрутку стека.
Она не становится дороже от того, что её создаёт коспилятор, чем если бы её создал программист.
D>Сказать что табличный подход или подход со счетчиками лучше однозначно нельзя — это зависит от множества факторов.
D>>>Вот маленький пример, безусловно искуственный R>>Это две разные программы, имеющие разную семантику. Очевидно, что они будут иметь разный размер.
D>Когда компилятор компилирует этот модуль у него нет возможности узнать что поддержка для обработки исключений тут может быть не нужна. Единственная возможность узнать это компилятору — это ключ пользователя. Семантика это наблюдаемое поведение программы, есть множество программ, для которых семантика программы не меняется от включения или выключения опции поддержки исключений.
Т.е. ты имеешь в виду случай, когда в программе используются деструкторы, но при этом не кидаются исключения, и используется только new(std::nothrow) и не используется dynamic_cast для ссылок и at() для vector и т.д., и не используются никакие библиотеки, которые могут это использовать? Я не уверен, что такой случай вообще имеет смысл рассматривать... И в любом случае, я бы не назвал эти программы "множество программ"...
Здравствуйте, Pzz, Вы писали:
Pzz>Мне как-то всегда казалось, что когда из флешки делают IDE disk, то это делают каким-то примерно таким образом. В противном случае я не понимаю, они что, ради каждого записанного байта трут весь 8-килобайтный сектор? Как-то слабо верится...
Судя по ничтожной скорости создания-записи мелких файлов — таки да.
Здравствуйте, The Lex, Вы писали:
Pzz>>Мне как-то всегда казалось, что когда из флешки делают IDE disk, то это делают каким-то примерно таким образом. В противном случае я не понимаю, они что, ради каждого записанного байта трут весь 8-килобайтный сектор? Как-то слабо верится...
TL>Судя по ничтожной скорости создания-записи мелких файлов — таки да.
Куда катится мир?...
Может это только в дешевых китайских флешках так? А в более приличных — по-человечески?
J>Это где же Александреску говорил, что его решения — для прикладного программирования??? J>А если какие-то идиоты принимаются писать прикладной код в стиле библиотечного кода, а гуй — на асме, а драйвер — на вижуал-бейсике — так это их персональные половые проблемы.
Да и в "Моей Борьбе" ничего такого в целом ненаписано. Но идиоты нашлись и находятся до сих пор... Может таки не толкьо в идиотах дело?
Все эмоциональные формулировки не соотвествуют действительному положению вещей и приведены мной исключительно "ради красного словца". За корректными формулировками и неискажённым изложением идей, следует обращаться к их автором или воспользоваться поиском
Здравствуйте, Pzz, Вы писали:
Pzz>Отчасти за счет того, что в C++'ном коде были очень дотошно расписаны правила игры в терминах интерфейсов — в Си так не сделаешь, поэтому и сделано не было. Отчасти же за счет того, что C++'ный код был более абстрактным и заточенным на дальнейшее развитие в неизвестно какую сторону, а Сишный был более сконцентрирован на решении практической задачи.
Это называется синдром YAGNI (You Ain't Gonna Need It) — если бы на С проектировали в таком же стиле, то объем кода был бы в разы больше С++ То есть, это опять проблемы не языка, а разработчиков.
Здравствуйте, dupamid, Вы писали:
C>>По реальным данным 10-20% оверхеда по объему. Оно все достаточно компактно получается. На Windows в GCC, кстати, можно тоже табличные исключения использовать D>Я видел реальные программы, которые распухали почти в два раза Так что это очень индивидуально и зависит от того сколько объектов создатеся и разрушается, включая все локальный, временые переменные и временные переменные создающиеся под условием типа "some_cond ? A() : f(B())", здесь будет созадана или временная переменная типа A или B и в конце вычисления полного выражения так переменная которая была созданна должна быть разрешена. Такого рода вещи создают очень много EH контекстов.
EH-таблицы очень компактны — это просто, фактически, указания какие диапазоны указателя инструкций (eip/rip/...) соответствуют каким объектам.
Может, конечно, в вырожденных случаях и будет все плохо — но на KDE я полчал порядка 15% уменьшения объема кода при отключении исключений.
D>Разница может быть в том, что компилятор будет учитывать дополнительный переход от места вызова к выходу из функции на графе потока управления (CFG) функции. Пример когда это может иметь значение, статическая переменная была поднята в регистр, значит перед вызовом функции ее предется опустить обратно в пямять (хотя может быть известно что внутри функции она не используется), так как функция может не вернуться.
AFAIR, при вызове функций и так придется закоммитить переменные в память — так как мы не знаем, какие регистры затронет функция. Если же у компилятора есть доступ к телу функции, то мы можем закоммитить переменную перед местами броска исключений.
На практике, от включения исключений тормозов не заметно (кроме "эффектов второго порядка" от большего cache contention'а).
Здравствуйте, Cyberax, Вы писали:
C>Это называется синдром YAGNI (You Ain't Gonna Need It) — если бы на С проектировали в таком же стиле, то объем кода был бы в разы больше С++ То есть, это опять проблемы не языка, а разработчиков.
Если бы мне такое захотелось напрограммировать на Си, я бы придумал domain-specific language, и написал бы компилятор. Чем сократил бы объем кода в разы
В тех 60К строк компилятор, кстати, был. Но не со столь фундаментальной целью. Выплюнул он из себя примерно столько же кода, сколько весил сам вместе с исходниками на евонном языке, но писать его было гораздо веселее, чем написать руками (а тем более — поддерживать) эквиавалент автоматически сгенеренного кода.
Но в общем и целом, я свое мнение сказал выше — по любым 2-м отдельно взятым проектам сравнивать бессмысленно, т.к. влияние стиля разработчика скажется больше, чем разница между языками.
Здравствуйте, hexis, Вы писали:
H>Что значит — "просто так"? Когда я пишу программы, я всегда предполагаю, H>куда и как она будет развиваться в дальнейшем. Исходя из этих H>соображений я могу использовать интерфейс там, даже если на первом этапе H> будет всего лишь одна его реализация. И не считаю это плохой практикой.
Тогда тебе нужно будет делать то же самое и для программ на С (с помощью самодельных виртуальных таблиц). Или признать, что это плохая практика.
>> Нет. Тебе стоит узнать как работают исключения. Ладно, делаем тест: H>Ну что-же, не буду врать, не знал, что gcc умеет отслеживает контекст H>исполнения по адресу, а не по счетчику. Но тем не менее, исключения так H>работают далеко не всегда. Хочешь приведу то, что генерирует тот-же H>mingw-gcc 3.4.2, который использует sjlj обработку исключений? Или H>поверишь на слово, что есть разница?
Да я знаю, что sjlj-исключения (и их аналог — SEH) — это мастдай. Поэтому и надо нормальные компиляторы и инструменты использовать.
Здравствуйте, Pzz, Вы писали:
Pzz>В принципе, с протиранием дырок на флешке можно бороться не в файловой системе, а на уровне драйвера блочного устройства. Или даже вовсе запихнуть эту логику в контроллер, который делает из флешки иллюзию IDE-диска. Тем более, что какой-то интеллект на этом уровне все равно нужен, т.к. типичный размер "сектора" (минимального куска, который можно стереть и переписать за раз) на флешке — 8-16 килобайт, а традиционные файловые системы рассчитаны на 512-байтный сектор.
Pzz>Я удивляюсь, неужели в реальности этого не было сделано?
Сделано. Штука называется "wear leveling". Так что можете за флэшку не бояться.
Здравствуйте, Cyberax, Вы писали:
C>AFAIR, при вызове функций и так придется закоммитить переменные в память — так как мы не знаем, какие регистры затронет функция. Если же у компилятора есть доступ к телу функции, то мы можем закоммитить переменную перед местами броска исключений.
Регистры обычно разделяются на три категории: caller save, callee save и scratch. Таким образом, во время вызова функции значение можно оставить в callee save регистре, так как вызываемая функция не имеет права его испортить, если он ей будет нужен, она сама его сохранит и перед выходом восстановит.
C>На практике, от включения исключений тормозов не заметно (кроме "эффектов второго порядка" от большего cache contention'а).
А что за эффект cache contention'а при включении исключений? Можно поподробнее?
Я правильно понял, что ты говоришь о ситуации, когда мы просто отключаем исключения в программе, которая на самом деле их использует, и тем самым фактически выключаем всю обработку ошибок (по крайней мере обработку нехватки памяти)?
Здравствуйте, remark, Вы писали:
C>>На практике, от включения исключений тормозов не заметно (кроме "эффектов второго порядка" от большего cache contention'а). R>А что за эффект cache contention'а при включении исключений? Можно поподробнее?
Код начинает занимать больший объем, а значит появляется больше шансов, что переход или вызов функции не попадут в кэш. На практике, такие эффекты почти не заметны, хотя в вырожденных случаях можно сделать замедление в разы.
R>Я правильно понял, что ты говоришь о ситуации, когда мы просто отключаем исключения в программе, которая на самом деле их использует, и тем самым фактически выключаем всю обработку ошибок (по крайней мере обработку нехватки памяти)?
Да, в качестве теста на верхнюю границу оверхеда — вполне подходит.
Здравствуйте, Cyberax, Вы писали:
C>>>На практике, от включения исключений тормозов не заметно (кроме "эффектов второго порядка" от большего cache contention'а). R>>А что за эффект cache contention'а при включении исключений? Можно поподробнее? C>Код начинает занимать больший объем, а значит появляется больше шансов, что переход или вызов функции не попадут в кэш. На практике, такие эффекты почти не заметны, хотя в вырожденных случаях можно сделать замедление в разы.
Понял.
R>>Я правильно понял, что ты говоришь о ситуации, когда мы просто отключаем исключения в программе, которая на самом деле их использует, и тем самым фактически выключаем всю обработку ошибок (по крайней мере обработку нехватки памяти)? C>Да, в качестве теста на верхнюю границу оверхеда — вполне подходит.
Но ведь это фактически сравнение 2 разных программ. Какой смысл это сравнивать? Логично сравнивать что-то сделанное одним методов против того же сделанного другим методом.
Либо тогда надо сравнивать как-то так. Замерить С код с удалёнными проверками возвращаемых значений и соотв. освобождениями ресурсов. Даже правильнее у функций сделать возвращаемое значение void. Далее замерить С код с проверками и освобождениями. И С++ код с исключениями.
Тогда можно будет сделать какое-то заключение об оверхеде исключений. А так даже не понятно что с чем сравнивать.
Кстати. Ради интереса отключил исключения и студии. Она всё равно на месте try/catch вставляет генерацию фрейма:
И исключения продолжают кидаться и ловиться. Что она перестала делать — создавать неявные фреймы для разрушения локальных объектов, и соотв. поддерживать неявный счётчик созданных объектов.
А явные try/catch — это как раз подход использующийся в контейнерах стандартной библиотеки в студии. Т.у. если используются стандартная библиотека или явные try/catch, то значительная часть оверхеда от них остаётся.
Если бы в стандартной библиотеке студии использовали RAII вместо try/catch, то она была бы генерировать более быстрый код при отключенных исключениях...