Сколько места занимает обработка исключений в C ++ - PullRequest
53 голосов
/ 27 марта 2009

Эта проблема особенно важна для разработки встраиваемых систем. Обработка исключений добавляет некоторый след в генерируемый двоичный вывод. С другой стороны, без исключений ошибки должны обрабатываться другим способом, который требует дополнительного кода, что в конечном итоге также увеличивает размер двоичного файла.

Меня интересует ваш опыт, особенно:

  1. Какова средняя занимаемая площадь, добавленная вашим компилятором для обработки исключений (если у вас есть такие измерения)?
  2. Действительно ли обработка исключений стоит дороже (многие говорят, что) с точки зрения размера двоичного вывода, чем другие стратегии обработки ошибок?
  3. Какую стратегию обработки ошибок вы бы предложили для разработки встраиваемых систем?

Пожалуйста, примите мои вопросы только в качестве руководства. Любые входные данные приветствуются.

Приложение: есть ли у какого-либо конкретного метода / сценария / инструмента, который для конкретного объекта / исполняемого файла C ++, будет отображать процент загруженной памяти, занимаемой сгенерированным компилятором кодом и структурами данных, предназначенными для обработки исключений

Ответы [ 8 ]

35 голосов
/ 27 марта 2009

Когда возникает исключение , возникают временные издержки, которые зависят от того, как вы реализуете обработку исключений. Но, будучи неподтвержденным, серьезность события, которое должно вызвать исключение, займет столько же времени для обработки с использованием любого другого метода. Почему бы не использовать широко поддерживаемый языковой метод решения таких проблем?

Компилятор GNU C ++ по умолчанию использует модель с нулевой стоимостью, т. Е. Нет никаких временных затрат, когда исключения не возникают.

Поскольку информация о коде обработки исключений и смещениях локальных объектов может быть вычислена один раз во время компиляции, такая информация может храниться в одном месте, связанном с каждой функцией, но не в каждом ARI. Вы по существу удаляете издержки исключений из каждого ARI и таким образом избегаете дополнительного времени, чтобы поместить их в стек. Этот подход называется моделью обработки исключений с нулевой стоимостью, а оптимизированное хранилище, упомянутое ранее, называется теневым стеком. - Брюс Экель, Мышление на С ++, Том 2

Затраты на сложность размера не поддаются количественной оценке, но Экель утверждает, что в среднем они составляют 5 и 15 процентов. Это будет зависеть от размера вашего кода обработки исключений в зависимости от размера кода вашего приложения. Если ваша программа небольшая, исключения будут большой частью двоичного файла. Если вы используете модель с нулевой стоимостью, то исключения будут занимать больше места для устранения временных затрат, поэтому, если вы заботитесь о пространстве, а не о времени, не используйте компиляцию с нулевой стоимостью.

Мое мнение заключается в том, что большинство встроенных систем имеют достаточно памяти, если у вашей системы есть компилятор C ++, у вас достаточно места для включения исключений. Компьютер PC / 104, который использует мой проект, имеет несколько ГБ вторичной памяти, 512 МБ основной памяти, следовательно, нет проблем с пространством для исключений - хотя наши микроконтроллеры запрограммированы на C. Моя эвристика - «если есть основной компилятор C ++ это, используйте исключения, в противном случае используйте C ".

20 голосов
/ 28 марта 2009

Измерение вещей, часть 2. Теперь у меня есть две программы. Первый находится в C и скомпилирован с gcc -O2:

#include <stdio.h>
#include <time.h>

#define BIG 1000000

int f( int n ) {
    int r = 0, i = 0;
    for ( i = 0; i < 1000; i++ ) {
        r += i;
        if ( n == BIG - 1 ) {
            return -1;
        }
    }
    return r;
}

int main() { 
    clock_t start = clock();
    int i = 0, z = 0;
    for ( i = 0; i < BIG; i++ ) {
        if ( (z = f(i)) == -1 ) { 
            break;
        }
    }
    double t  = (double)(clock() - start) / CLOCKS_PER_SEC;
    printf( "%f\n", t );
    printf( "%d\n", z );
}

Второй - C ++, с обработкой исключений, скомпилированный с g ++ -O2:

#include <stdio.h>
#include <time.h>

#define BIG 1000000

int f( int n ) {
    int r = 0, i = 0;
    for ( i = 0; i < 1000; i++ ) {
        r += i;
        if ( n == BIG - 1 ) {
            throw -1;
        }
    }
    return r;
}

int main() { 
    clock_t start = clock();
    int i = 0, z = 0;
    for ( i = 0; i < BIG; i++ ) {
        try {
         z += f(i); 
        }
        catch( ... ) {
            break;
        }

    }
    double t  = (double)(clock() - start) / CLOCKS_PER_SEC;
    printf( "%f\n", t );
    printf( "%d\n", z );
}

Я думаю, что они отвечают на всю критику моего последнего поста.

Результат: время выполнения дает версии C преимущество в 0,5% по сравнению с версией C ++ за исключением, а не 10%, о которых другие говорили (но не продемонстрировали)

Я был бы очень признателен, если бы другие могли попытаться скомпилировать и запустить код (это займет всего несколько минут), чтобы убедиться, что я нигде не допустил ужасную и очевидную ошибку. Это известно как "научный метод"!

5 голосов
/ 27 марта 2009

Я работаю в среде с низкой задержкой. (менее 300 микросекунд для моего приложения в «цепочке» производства) Обработка исключений, по моему опыту, добавляет 5-25% времени выполнения в зависимости от суммы, которую вы делаете!

Мы, как правило, не заботимся о двоичном раздутии, но если вы получаете слишком много раздувания, то вы бьетесь как сумасшедший, поэтому вам нужно быть осторожным.

Просто держите двоичный код разумным (зависит от вашей настройки).

Я выполняю довольно обширное профилирование своих систем.
Другие неприятные области:

Вход

Сохранение (мы просто не делаем это, или если мы делаем это параллельно)

4 голосов
/ 01 апреля 2009

Стоит учесть одну вещь: если вы работаете во встроенной среде, вы хотите, чтобы приложение было как можно меньшего размера. Microsoft C Runtime добавляет довольно много накладных расходов на программы. Удалив среду выполнения C как требование, я смог получить простую программу в виде exe-файла размером 2 КБ вместо 70-килобайтного файла, и это при всех включенных оптимизациях размера.

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

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

Другая проблема заключается в том, что для исключений C ++ требуется ограниченный RTTI (информация о типах времени выполнения), по крайней мере, в MSVC, что означает, что имена типов ваших исключений хранятся в исполняемом файле. Что касается пространства, это не проблема, но мне кажется, что мне не хватает этой информации в файле.

4 голосов
/ 27 марта 2009

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

У меня нет цифр. Однако для большинства встроенных разработок я видел, как люди бросали две вещи (для цепочки инструментов VxWorks / GCC):

  • Шаблоны
  • RTTI

Для обработки исключений в большинстве случаев используются оба типа, поэтому существует тенденция к их выбрасыванию.

В тех случаях, когда мы действительно хотим приблизиться к металлу, используются setjmp / longjmp. Обратите внимание, что это, возможно, не самое лучшее решение (или очень мощное), но именно это _we_ использует.

Вы можете запускать простые тесты на своем настольном компьютере с двумя версиями набора тестов с обработкой исключений и без них и получать данные, на которые вы можете положиться больше всего.

Еще одна вещь, связанная со встроенной разработкой: шаблоны избегают как чума - они вызывают слишком много вздутия. Исключения помечены вдоль шаблонов и RTTI, как объяснил Иоганн Герелл в комментариях (я предположил, что это было хорошо понято).

Опять же, это именно то, что мы делаем. Что это со всем понижением?

1 голос
/ 01 апреля 2009

Легко увидеть влияние на размер двоичного файла, просто отключите RTTI и исключения в вашем компиляторе. Вы получите жалобы на dynamic_cast <>, если вы его используете ... но мы обычно избегаем использования кода, который зависит от dynamic_cast <> в наших средах.

Мы всегда считали выигрышным отключить обработку исключений и RTTI с точки зрения двоичного размера. Я видел много разных методов обработки ошибок в отсутствие обработки исключений. Наиболее популярным представляется передача кодов ошибок в стек вызовов. В нашем текущем проекте мы используем setjmp / longjmp, но я бы посоветовал против этого в проекте C ++, поскольку они не будут запускать деструкторы при выходе из области видимости во многих реализациях. Если честно, я думаю, что это был плохой выбор, сделанный первоначальными разработчиками кода, особенно если учесть, что наш проект - C ++.

1 голос
/ 27 марта 2009

По моему мнению, обработка исключений не является чем-то приемлемым для встраиваемой разработки.

Ни в GCC, ни в Microsoft нет обработки исключений "без накладных расходов". Оба компилятора вставляют операторы пролога и эпилога в каждую функцию, которая отслеживает объем выполнения. Это приводит к ощутимому увеличению производительности и объема памяти.

Разница в производительности составляет примерно 10% в моем опыте, что для моей области работы (графика в реальном времени) огромно. Затраты памяти были гораздо меньше, но все же значительны - я не помню, чтобы эта цифра была в стороне, но с GCC / MSVC легко компилировать вашу программу в обоих направлениях и измерять разницу.

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

Я бы также держался подальше от RTTI для разработки встраиваемых систем, хотя мы используем его в сборках отладки для проверки работоспособности при снижении результатов.

0 голосов
/ 02 апреля 2009

Определить «встроенный». На 8-битном процессоре я бы точно не работал с исключениями (я бы точно не работал с C ++ на 8-битном процессоре). Если вы работаете с платой типа PC104, которая достаточно мощна, чтобы быть настольным компьютером несколько лет назад, то вам это может сойти с рук. Но я должен спросить - почему есть исключения? Обычно во встроенных приложениях что-либо похожее на возникновение исключений немыслимо - почему эта проблема не была решена в ходе тестирования?

Например, это в медицинском устройстве? Небрежное программное обеспечение в медицинских устройствах убило людей. Это неприемлемо для чего-то незапланированного, точка. Все режимы сбоя должны быть учтены, и, как сказал Джоэл Спольски, исключения подобны операторам GOTO, за исключением того, что вы не знаете, откуда они вызываются. Итак, когда вы обрабатываете свое исключение, что не удалось и в каком состоянии находится ваше устройство? Из-за вашего исключения ваш аппарат лучевой терапии застрял в FULL и готовит кого-то живым (это произошло в IRL)? В какой момент произошло исключение в ваших 10 000+ строках кода. Конечно, вы можете сократить это до, возможно, 100 строк кода, но знаете ли вы значение каждой из этих строк, вызывающих исключение?

Без дополнительной информации я бы сказал, НЕ планируйте исключения во встроенной системе. Если вы добавите их, будьте готовы планировать режимы отказа КАЖДОЙ ЛИНИИ КОДОВ, которые могут вызвать исключение. Если вы делаете медицинское устройство, тогда люди умирают, если вы этого не делаете. Если вы делаете портативный DVD-плеер, значит, вы сделали плохой портативный DVD-плеер. Что это?

...