Насколько значительная часть обработки исключений С++ добавляет

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

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

  • Что такое средний размер вашего компилятора для обработки исключений (если у вас такие измерения)?
  • Является ли обработка исключений действительно более дорогой (многие говорят, что) с точки зрения двоичного размера вывода, чем другие стратегии обработки ошибок?
  • Какую стратегию обработки ошибок вы предложите для встроенной разработки?

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

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

Ответ 1

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

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

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

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

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

Ответ 2

Измерение вещей, часть 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 );
}

Второй - это С++, с обработкой исключений, скомпилированный с 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% по сравнению с версией С++ с исключениями, а не 10%, о которых говорили другие (но не продемонстрированные)

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

Ответ 3

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

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

  • Шаблоны
  • RTTI

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

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

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

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

Опять же, это именно то, что мы делаем. Что это такое при любом downvoting?

Ответ 4

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

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

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

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

Logging

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

Ответ 5

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

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

Вы можете спорить просто динамически, но в моем случае это было непрактично.

Еще одна проблема заключается в том, что исключения С++ требуют ограниченного RTTI (информация о типе времени выполнения), по крайней мере, на MSVC, что означает, что имена типов ваших исключений сохраняются в исполняемом файле. Пространственно, это не проблема, но она просто "чувствует" меня чище, чтобы не иметь эту информацию в файле.

Ответ 6

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

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

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

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

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

Ответ 7

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

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

Ответ 8

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

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

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