2015-03-31 2 views
9

Я нашел a question somewhat interesting и попытался ответить на него. Автор хочет скомпилировать исходный файл (который опирается на библиотеки шаблонов) с оптимизацией AVX, а остальная часть проекта - без него.Как условно настроить оптимизацию компилятора для заголовков шаблонов

Так, чтобы посмотреть, что произойдет, я создал тестовый проект, как это:

main.cpp

#include <iostream> 
#include <string> 
#include "fn_normal.h" 
#include "fn_avx.h" 

int main(int argc, char* argv[]) 
{ 
    int number = 10; // this will come from input, but let's keep it simple for now 
    int result; 

    if (std::string(argv[argc - 1]) == "--noavx") 
     result = FnNormal(number); 
    else 
    { 
     std::cout << "AVX selected\n"; 
     result = FnAVX(number); 
    } 

    std::cout << "Double of " << number << " is " << result << std::endl; 

    return 0; 
} 

Файлы fn_normal.h и fn_avx.h содержит декларации для функций FnNormal() и FnAVX(), которые определены следующим образом:

fn_normal.cpp

#include "fn_normal.h" 
#include "double.h" 

int FnNormal(int num) 
{ 
    return RtDouble(num); 
} 

fn_avx.cpp

#include "fn_avx.h" 
#include "double.h" 

int FnAVX(int num) 
{ 
    return RtDouble(num); 
} 

А вот определение функции шаблона:

double.h

template<typename T> 
int RtDouble(T number) 
{ 
    // Side effect: generates avx instructions 
    const int N = 1000; 
    float a[N], b[N]; 
    for (int n = 0; n < N; ++n) 
    { 
     a[n] = b[n] * b[n] * b[n]; 
    }  
    return number * 2; 
} 


В конце концов, я поставил Enhanced Instruction Set в AVX для файла fn_avx.cpp под "Properties-> C/C++ -> Code Generation", в результате чего его Not Set для других источников, таким образом, он должен по умолчанию SSE2.

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

Отключение всех других оптимизаций не решает эту проблему. Также попробовал No Enhanced Instructions - /arch:IA32 вместо Not Set.

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

Мой компилятор MSVC 2013.


Дополнительная информация: файлы .obj для обоих fn_normal.cpp и fn_avx.cpp почти такой же размер в байтах. Я просмотрел сгенерированные списки сборок, и они почти одинаковы, с существенным отличием, что источник с поддержкой avx заменяет по умолчанию sse movss/mulss на vmovss и vmulss соответственно.Но шагая Повсеместно кода в целях разборки Visual Studio, (Ctrl + Alt + D), подтверждает, что fnNormal() действительно делает использование AVX специализированных инструкций.

+1

Уверены ли вы, что настройка расширенных инструкций to'AVX 'влияет на имя mangling? Похоже, что это не так, и манипуляция имени шаблона идентична в обеих единицах перевода; так как одно из определений шаблона отбрасывается как дубликат. –

+0

'template int RtDouble (double number)', который вы бы назвали 'RtDouble <0> (number)' и 'RtDouble <1> (number)' должен генерировать отдельные функции. – SleuthEye

+4

Я не думаю, что «Не задано» означает «Нет расширенного набора инструкций». – Mehrdad

ответ

3

Компилятор сгенерирует два объекта (fn_avx.obj и fn_normal.obj), которые скомпилированы с различными наборами инструкций.Как вы сказали, выводя разборку для обоих проверяет, что это делается правильно:

objdump -d fn_normal.obj:

... 
movss -0x1f5c(%ebp,%eax,4),%xmm0 
mulss -0x1f5c(%ebp,%ecx,4),%xmm0 
mov -0x1f68(%ebp),%edx 
mulss -0x1f5c(%ebp,%edx,4),%xmm0 
mov -0x1f68(%ebp),%eax 
movss %xmm0,-0xfb4(%ebp,%eax,4) 
... 

objdump -d fn_avx.obj:

... 
vmovss -0x1f5c(%ebp,%eax,4),%xmm0 
vmulss -0x1f5c(%ebp,%ecx,4),%xmm0,%xmm0 
mov -0x1f68(%ebp),%edx 
vmulss -0x1f5c(%ebp,%edx,4),%xmm0,%xmm0 
mov -0x1f68(%ebp),%eax 
vmovss %xmm0,-0xfb4(%ebp,%eax,4) 
... 

внешний вид поразительно похожи, потому что по умолчанию MSVC 2013 предположим наличие SSE2. Если вы измените набор инструкций на IA32, вы получите что-то с не-векторными инструкциями. Таким образом, это не проблема с компилятором/компиляцией.

Проблема здесь: RtDouble определена в файле заголовка как неспециализированный шаблон (совершенно законный). Компилятор предполагает, что его определение в нескольких единицах перевода будет одинаковым, но, компилируя с различными вариантами, это предположение нарушается. Это не по существу не отличается от введения дивергенции с препроцессором:

double.h:

template<typename T> 
int RtDouble(T number) 
{ 
#ifdef SUPER_BAD 
// Side effect: generates avx instructions 
const int N = 1000; 
float a[N], b[N]; 
for (int n = 0; n < N; ++n) 
{ 
    a[n] = b[n] * b[n] * b[n]; 
} 
return number * 2; 
#else 
return 0; 
#endif 
} 

fn_avx.cpp:

#include "fn_avx.h" 
#define SUPER_BAD 
#include "double.h" 

int FnAVX(int num) 
{ 
    return RtDouble(num); 
} 

FnNormal тогда просто return 0 (и вы можете убедиться в этом с разборкой нового fn_normal.obj). Компилятор счастливо выбирает один и не предупреждает вас ни о какой ситуации. Затем возникает вопрос: не так ли? Это было бы очень полезно в таких ситуациях. Тем не менее, это также замедлит связывание, поскольку ему нужно будет выполнить сравнение всех функций, которые могут существовать в нескольких единицах компиляции (например, встроенные функции).

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

+0

Когда вы говорите: «Компилятор предполагает, что его определение на нескольких единицах перевода будет одинаковым», вы имеете в виду, _linker_? –

2

В основном компилятор должен свести к минимуму пространство, не говоря уже о том, что наличие одного и того же шаблона, созданного в виде 2x, может вызвать проблемы, если бы были статические элементы. Поэтому из того, что я знаю, компилятор обрабатывает шаблон либо для каждого исходного кода, а затем выбирает одну из реализаций или откладывает фактическое генерирование кода на время ссылки. В любом случае это проблема для этого AVX. Я в конечном итоге решил его старомодным способом - с некоторыми глобальными определениями, не зависящими ни от каких шаблонов или чего-либо еще. Однако для слишком сложных приложений это может быть огромной проблемой. У Intel Compiler недавно добавлена ​​прагма (я не помню точное имя), что делает функцию реализована сразу после использования только инструкций AVX, что решит проблему. Насколько это надежно, чего я не знаю.

+1

Я отключил генерацию кода временного кода (среди многих других) при попытках решить его, но не добиться успеха. Наверное, вы, вероятно, правы в отношении другой возможности. Это решение действительно не подходит в некоторых случаях, как вы говорите ... Будем надеяться, что кто-то может показать нам, что происходит точно :) Спасибо –

2

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

В MSVC++:

template<typename T> 
__forceinline int RtDouble(T number) {...} 

GCC:

template<typename T> 
inline __attribute__((always_inline)) int RtDouble(T number) {...} 

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

3

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

Файл double.h:

template<bool avx, typename T> 
int RtDouble(T number) 
{ 
    // Side effect: generates avx instructions 
    const int N = 1000; 
    float a[N], b[N]; 
    for (int n = 0; n < N; ++n) 
    { 
     a[n] = b[n] * b[n] * b[n]; 
    }  
    return number * 2; 
} 

Файл fn_normal.cpp:

#include "fn_normal.h" 
#include "double.h" 

int FnNormal(int num) 
{ 
    return RtDouble<false>(num); 
} 

Файл fn_avx.cpp:

#include "fn_avx.h" 
#include "double.h" 

int FnAVX(int num) 
{ 
    return RtDouble<true>(num); 
} 
+0

Спасибо за ваш ответ. Я сомневался в награждении щедростью вам или MuertoExcobito, и из-за этого я взял больше времени, чем должен был присудить награду, и она получила авто-награду системой. Извини за это. Вы можете взглянуть на [мою публикацию на Meta] (http: //meta.stackoverflow.com/questions/294873/change-auto-award-bounty-after-grace-period) об этом. –

Смежные вопросы