Как оптимизировать производительность, не нарушая принцип DRY (Don't-Repeat-Yourself) в этом сценарии?

Предположим, я использую C ++. Теперь у меня есть такой код:

int flag;
// ...
while (!TimeToExitLoop()) {
    Func();
}

Цикл while будет выполняться огромное количество раз, а Func критичная по времени функция, например:

void Func() {
    // some big stuff ...
    if (flag > 1) {
        // logic 1 ...
    }
    else {
        // logic 2 ...
    }
}

Кроме того, значение flag не изменится в пределах цикла while. Поэтому лучше переместить условный оператор if из цикла while и определить две отдельные функции для этих двух условий, например:

int flag;
// ...
if (flag > 1) {
    while (!TimeToExitLoop()) {
        Func_FlagBiggerThanOne();
    }
}
else {
    while (!TimeToExitLoop()) {
        Func_FlagNoBiggerThanOne();
    }
}

Однако это приведет к повторению "больших вещей" в Func на Func_FlagBiggerThanOne и Func_FlagNoBiggerThanOne:

void Func_FlagBiggerThanOne() {
    // big stuff ...
    // logic 1 ...
}

void Func_FlagNoBiggerThanOne() {
    // big stuff ...
    // logic 2 ...
}

что нарушит принцип «Не повторяйся». Мы не можем поместить этот «большой материал» в какую-либо функцию, потому что вызов этой функции займет больше времени, чем исходный оператор if. Одно из решений - определить макрос для этого большого материала, но что, если «логика 1» и «логика 2» будут использовать переменные, определенные в «большом материале»? Хотя макрос все еще работает, что может привести к некрасивому коду, читатель программы может подумать: «А где, черт возьми, эти переменные определены?»


person SSHuv    schedule 24.11.2010    source источник
comment
используйте компилятор / компоновщик, который может выключить условное выражение из цикла ... но этот компилятор / компоновщик должен быть довольно забавным, если Func находится в другом файле, потому что flag явно глобален.   -  person lijie    schedule 24.11.2010
comment
«Мы не можем поместить такой объемный материал в какую-либо функцию, потому что вызов этой функции займет больше времени, чем исходный оператор if. »Разве вы не можете его встроить?   -  person Zecc    schedule 24.11.2010
comment
Я думаю, что хороший программист не должен ничего предполагать от компилятора / компоновщика. Кроме того, Zecc, я полагаю, встроенный не будет работать, потому что это очень большой материал.   -  person SSHuv    schedule 25.11.2010


Ответы (2)


Мы не можем поместить этот "большой материал" в какую-либо функцию, потому что вызов этой функции займет больше времени, чем исходный оператор if.

У меня есть любимая древнеанглийская идиома: «Глупый фунт глупости» (в чем я виноват). Другими словами, «не переживайте по мелочам». Хотел бы я найти оригинальную цитату на SO: «подстричься, чтобы похудеть».

Сделайте код чистым, и, если вы можете разумно следовать DRY, сделайте это. Затем сделайте настройку производительности. Вот как я это делаю. Я был бы очень удивлен если стоимость вызова этой функции или стоимость оператора IF даже вырастают на радар.

person Mike Dunlavey    schedule 24.11.2010
comment
Спасибо, Майк. Я только что прочитал вашу ссылку Вот как я это делаю. На самом деле в своем проекте я следовал тем же шагам, которые вы предложили. Теперь я, кажется, наткнулся на стену. Согласно моему эксперименту, стоимость вызова функции в Func действительно занимает много времени. Я еще не тестировал эффект вывода IF из Func. Теперь я просто оставляю это там, чтобы код оставался чистым. Но мне любопытно, что делать, если я хочу пойти на крайние меры. Предположим, что цикл WHILE выполняется 10 ^ 10 раз, тогда инструкция IF будет стоить около 5 секунд на процессоре с тактовой частотой 2 ГГц. - person SSHuv; 25.11.2010
comment
Ой ... если оператор IF занимает всего 5 секунд, на большие дела уйдет гораздо больше времени. Так что не стоит тратить много времени на перемещение IF из Func. Единственная причина для этого - перфекционизм и любопытство. - person SSHuv; 25.11.2010
comment
@Zhixiang: Я думаю, что это правильно. Лучше думать в относительных терминах, чем в абсолютных. Когда вы настроили большие вещи настолько хорошо, что они перестали быть большими, а IF и Func занимают достаточно большой процент, чтобы иметь значение, тогда ваш код быстрее, чем ошпаренная собака, и вы отличный программист :-) - person Mike Dunlavey; 25.11.2010
comment
@Zhixiang: Если вы действительно достигли этой точки, а IF и / или Func потребляют 10% или более, и вы вызываете их триллионы раз, значит, вам действительно нужно сжать больше циклов , затем непременно выбросьте СУХОЙ за борт, потому что все, что он делает, упрощает обслуживание. Именно тогда мне известно, что я использую макрос для сохранения СУХОЙ, если я не хотел доверять компилятору для его встраивания. И тогда я бы хотел подумать, стоит ли мне использовать другой подход, например, предварительную компиляцию. - person Mike Dunlavey; 25.11.2010

Я просто думаю о решении. Основная идея - использовать #ifdef:

#define FUNC()  do {  \
    // big stuff ...
#ifdef FLAG_BIGGER_THAN_ONE  \
    // logic 1 ...  \
#else  \
    // logic 2 ...  \
#endif  \
} while(0)

Итак, мы можем написать основную логику так:

int flag;            
// ...            
if (flag > 1) {            
    while (!TimeToExitLoop()) {
#define FLAG_BIGGER_THAN_ONE            
        FUNC();
#undef FLAG_BIGGER_THAN_ONE            
    }            
}            
else {            
    while (!TimeToExitLoop()) {            
        FUNC();            
    }            
}

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

person SSHuv    schedule 26.11.2010
comment
На самом деле этот способ не работает. Макрос FUNC () не компилируется. - person SSHuv; 20.12.2010