Оптимизируйте if-else, который должен быть известен во время компиляции, но информация находится в другом проекте - PullRequest
0 голосов
/ 14 января 2019

Как разрешить пользователю определять «режим поведения», который шаблонная функция doIt(a,b) будет извлекать из другой определяемой пользователем переменной / функции op И , оптимизировать , если иное удалено?

Вот рабочий MCVE.
Мое решение VS имеет 2 проекта. pj1 - моя библиотека. pj2 - проект пользователя.

A_pj1.h

#pragma once
template<int T>class BoolT{public:
    static bool op;
};
template<int T> bool BoolT<T>::op=true; //by default,  true=+, false=-
template<int i> int doIt(int a,int b){
    if(BoolT<i>::op){
        return a+b;
    }else{ return a-b;}
}

A_pj2_UserDefine.h

#pragma once
#include "A_pj1.h"
inline void A_pj2_UserDefine_Reg(){
    BoolT<2>::op=false; //override default value;
}

A_pj2_main.cpp

#include "A_pj2_UserDefine.h"
#include <iostream>
int main(){
    A_pj2_UserDefine_Reg();
    int s1=doIt<1>(3,2); //= 5 (correct)
    int s2=doIt<2>(3,2); //= 1 (correct)
    std::cout << s1<<" "<<" "<<s2<<std::endl;
    int asfasd=0;
}

(правка) Вот разборка (оптимизированная версия): -

int s2=doIt<2>(3,2);
    std::cout << s1<<" "<<" "<<s2<<std::endl;
00B61620  mov         ecx,dword ptr [_imp_?cout@std@@3V?$basic_ostream@DU?$char_traits@D@std@@@1@A (0B6B0A8h)]  
    int s1=doIt<1>(3,2); //:
00B61626  xor         eax,eax  
00B61628  cmp         byte ptr [BoolT<1>::op (0B6E001h)],al  
    int s2=doIt<2>(3,2);
    std::cout << s1<<" "<<" "<<s2<<std::endl;
00B6162E  push        offset std::endl<char,std::char_traits<char> > (0B61530h)  
00B61633  push        1  
    int s1=doIt<1>(3,2); //:
00B61635  setne       al  
    A_pj2_UserDefine_Reg();
00B61638  mov         byte ptr [BoolT<2>::op (0B6E000h)],0  
    int s2=doIt<2>(3,2);
    std::cout << s1<<" "<<" "<<s2<<std::endl;
00B6163F  push        offset string " " (0B6B220h)  
00B61644  push        offset string " " (0B6B220h)  
    int s1=doIt<1>(3,2); //:
00B61649  lea         eax,[eax*4+1]  
    int s2=doIt<2>(3,2);
    std::cout << s1<<" "<<" "<<s2<<std::endl;
00B61650  push        eax  
00B61651  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0B6B0B4h)]  
00B61657  push        eax  
00B61658  call        std::operator<<<std::char_traits<char> > (0B61310h)  
00B6165D  add         esp,8  
00B61660  push        eax  
00B61661  call        std::operator<<<std::char_traits<char> > (0B61310h)  
00B61666  add         esp,8  
00B61669  mov         ecx,eax  
00B6166B  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0B6B0B4h)]  
00B61671  mov         ecx,eax  
00B61673  call        dword ptr [__imp_std::basic_ostream<char,std::char_traits<char> >::operator<< (0B6B0B8h)]  
    int asfasd=0;
}
00B61679  xor         eax,eax  
00B6167B  ret  

Проблема

doIt(a,b) вызывается очень часто (> 60000 в секунду).
Мне любопытно, можно ли оптимизировать if(BoolT<i>::op).
Условие if не подходит для вычисления конвейера.

Вот поведение моей программы, которое может помочь: -

  • Доступ-запись в BoolT<2>::op всегда происходит только в начале программы (например, A_pj2_UserDefine_Reg()).
  • Функция запроса BoolT<2>::op (как прямо, так и косвенно) почти всегда происходит только в проекте pj2.
  • Пользователь не может редактировать pj1 .
  • В реальном случае, кроме A_pj2_main.cpp, существует множество .cpp, которые вызывают pj1 'doIt<>(). Вот график включения: -

enter image description here

Мое плохое решение

B_pj1.h

#pragma once
template<int i> bool op(){return true;}
template<int i> int doIt(int a,int b){
    if(op<i>()){
        return a+b;
    }else{ return a-b;}
    // return a+b;
}

B_pj2_UserDefine.cpp

#include "B_pj1.h"
template<> bool op<2>(){return false;}

B_pj2_main.cpp

#include <iostream>
#include "B_pj1.h"
int main(){
    int s1=doIt<1>(3,2); //:
    int s2=doIt<2>(3,2);
    std::cout << s1<<" "<<" "<<s2<<std::endl;
    int asfasd=0;
}

Эта программа некорректна. (Ссылка: Хранение определений функций шаблона C ++ в файле .CPP )

Ошибка LNK2005 "bool __cdecl op <2> (void)" (?? $ op @ $ 01 @@ YA_NXZ) уже определено в B_pj2_UserDefine.obj

Ответы [ 2 ]

0 голосов
/ 14 января 2019

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

Прежде всего, убедитесь, что вы оптимизируете в MSVC то, что будет добавлять / O2 (или подобное) в командную строку.

Во-вторых, мера. Предсказатели ветвлений в процессорах действительно эффективны, вы можете даже не заметить.

Тем не менее, когда компилятор видит if (false) и не оптимизирует его, вы можете считать это ошибкой в ​​компиляторе. Постоянное свертывание, особенно при использовании SSA в качестве модели, настолько тривиально, что эти ветви должны исчезнуть довольно быстро.

Если вы сомневаетесь или когда вы хотите форсировать его во время компиляции, используйте if constexpr. Он требует C ++ 17 и доступен в MSVC2017.

Другая альтернатива - сделать вашу функцию constexpr, не всегда возможной, хотя это будет выглядеть так:

template<int i> constexpr int doIt(int a,int b){
    if(BoolT<i>::op){
        return a+b;
    }else{ return a-b;}
}

И вы можете назвать это так:

constexpr int s1=doIt<1>(3,2);
int s2=doIt<2>(3,i);

При этом ваш компилятор должен вычислять значение s1 во время компиляции. Хотя он может повторно использовать функцию для расчета s2 во время выполнения. (Обратите внимание, переменная я передаю)

Что касается вашей ошибки компоновщика, в C ++ существует ODR (одно правило определения). Вы можете обойти это, добавив inline, однако у вас должна быть одинаковая реализация везде! Я бы не рекомендовал помещать реализацию в файл CPP, поскольку это чек для UB.

0 голосов
/ 14 января 2019

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

В любом случае, обычное решение вашего вопроса - макросы препроцессора C. Технически возможно решить, например, с помощью if constexpr, но на практике это потребует от библиотеки #include чего-то из пользовательского проекта, и большинство авторов библиотеки не хотят поддерживать такой вариант использования.

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

...