Очистка макросов предварительной обработки - PullRequest
3 голосов
/ 14 июля 2010

Это странная проблема, поэтому я должен предоставить немного фона. У меня есть проект на C ++, над которым я работаю, и я хочу немного его почистить. Основная проблема, с которой я сталкиваюсь, заставляет меня хотеть прекратить это массовое злоупотребление макросами препроцессора, которые используются в основном компоненте проекта. Есть файл с кучей #define s, которые комментируются / не комментируются перед компиляцией и использованием программы для переключения использования различных алгоритмов. Я бы предпочел иметь аргументы командной строки, чтобы сделать это, а не перекомпилировать каждый раз, когда мы хотим попробовать другой алгоритм. Проблема заключается в том, что в коде так много переплетено #ifdef, что кажется невозможным просто изменить код для каждого алгоритма.

Мне сказали, что причина этого в том, что это система реального времени, которая будет работать с миллисекундными единицами времени, а код в этом компоненте вызывается так много раз, что с * Проверка 1005 * отрицательно скажется на нашей работе. Если вы хотите попробовать другой алгоритм, вам нужно установить соответствующие флаги и перекомпилировать, чтобы оптимизировать производительность.

Так что мой вопрос к вам всем таков:

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

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

Ответы [ 5 ]

2 голосов
/ 14 июля 2010

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

Это реальная проблема.

[...] что проверка if отрицательно скажется на нашей производительности.

Это не веская причина.

Если ваш код был протестирован на производительность и оптимизирован в результате (какэто должно было быть), это будет применяться.Я не могу представить ни одного сценария, в котором вы могли бы получить значительный выигрыш в производительности, заменив ifs на #defines (если только if не были сделаны путем сравнения содержимого строки, использования последовательного поиска или чего-то такого же катастрофического с точки зрения производительности).В связи с этим я готов поспорить, что решение об использовании макросов было выбрано во время разработки, что, вероятно, привело бы к преждевременной оптимизации (« преждевременная оптимизация - корень всех макроопределений »:D)

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

Да.

Вот несколько возможных шагов (есть и другие решения, но этот вообще не использует if с:

  1. Определитьбенчмарк в вашем коде и запустить его (сохранить результаты)

  2. Найти одну область кода, которая реализована в терминах более чем одного ПОssible #define s.

  3. Перемещение определяемых функций с помощью общего интерфейса.

  4. Во время выполнения сравните параметр сконстанта и передать указатель на выбранную функцию клиентскому коду.

    Что следует избегать:

    • выполнение сравнения более одного раза;после сравнения у вас должен быть выбранный указатель на функцию;указатель этой функции должен передаваться, а не ваш параметр.
    • , выполняющий сравнение с использованием строк (или char* или чего-либо, кроме числа).Сравнение строк - или любое сравнение, не проводимое в постоянное время - губительно для кода, критичного к производительности.Вместо сравнения значения параметра с помощью if рассмотрите возможность использования его с помощью оператора switch.
    • , передающего большие структуры в качестве параметров для функций стратегии.Передача должна выполняться по (const) ссылкам или указателям.
  5. вызывать код стратегии через указатель функции, а не напрямую.

  6. Повторите тестирование, выполненное на шаге 0, и сравните производительность.

На данный момент у вас должен быть веский аргумент для представления вашему боссу / менеджеру:

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

  • ваш код будет легче поддерживать (более модульный, разделять функциональные блоки за интерфейсами, централизовать изменения и т. Д.)

  • ваш код должен быть легче расширяться (по тем же причинам, что и выше)

  • вам не нужно больше перекомпилировать вашу кодовую базу.

  • вы избавились от большой проблемы (вызванной преждевременной оптимизацией).

  • вы можете продолжить рефакторинг базы кода (и избавиться от большего количества макросов) какразработка / сопровождение продолжается в других областях, практически без изменений в функциональном поведении.

1 голос
/ 14 июля 2010

Если бы мне понадобилось использовать необязательные алгоритмы, выбирая их во время выполнения, я бы использовал какую-то хеш-таблицу. Мои алгоритмы были бы функциями с одинаковой сигнатурой, так что я мог бы создать массив указателей на функции и вызывать их по индексу в массиве (индекс может быть проанализирован как параметр командной строки). Не было бы никаких потерь производительности из-за виртуальности (как во многих шаблонах проектирования) и никаких штрафов от «если» (как при выборе алгоритмов вручную).

Код:

// type of my funcs:
typedef void (*SolverFunc)( const SolverParams &sp );

// implementation for the algorithms:
void EulerSolver( const SolverParams &sp ) { ... }

void RhungeSolver( const SolverParams &sp ) { ... }

// my array of solvers:
static SolverFunc s_solvers [] = { EulerSolver, RhungeSolver };

// parsing command line params:
int main( int argc, char** argv )
{
    int solverIndex = ParseIndex(argv);
    s_solvers[solverIndex] ( .. params .. );
    return 0;
}

Хорошо, код выполнен в стиле c, а не в стиле c ++, но идея заслуживает рассмотрения. постскриптум Я не уверен, является ли пример синтаксически правильным, извините =)

1 голос
/ 14 июля 2010

Ах, зверства, которые могут совершать люди, творческие с CPP.

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

DifferentialEquationIntegrator <:
     Runge-Kutta Integrator
     Eulers Method Integrator

и т. Д. (Читайте <: как стрелку наследования или «обеспечивает-удовлетворяет» - A <: B означает «A предоставляет поведенческую спецификацию, которой удовлетворяет B».

По сути, это механический перевод от описанной вами схемы к «правильной» схеме, но он будет сложным и расширенным и будет стоить очень дорого, как будто вы переписываете код.

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

Да, другой вариант - использовать препроцессор для помощи. Обычно вы можете запускать препроцессор для кода, в результате чего генерируется код. Например, в gcc флаг -E делает это. Результат, по историческим причинам, содержит все новые строки, которые он имел (сделайте wc -l более понятным), поэтому, если вы хотите прочитать его, запустите вывод через indent (1) или что-то подобное.

Если вы сделаете это со всеми различными наборами флагов, вы узнаете много нового об общем коде через некоторые различия.

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

1 голос
/ 14 июля 2010

Существует шаблон для решения этой проблемы: Стратегия.

Вы выбираете стратегию (алгоритм), которую хотите использовать ОДИН РАЗ, а затем передаете объект вокруг.Это, конечно, требует, чтобы Фабрика создала правильный объект Стратегии.

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

1 голос
/ 14 июля 2010

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

...