#c #code-generation #template-meta-programming #generic-programming
#c #генерация кода #шаблон-мета-программирование #generic-программирование
Вопрос:
ОБНОВЛЕНИЕ II: я написал два примера, чтобы проиллюстрировать идеи, предложенные в принятом ответе и комментариях. Первый math_cmp.cc
выполняет явно типизированные операции.
// math_cmp.cc
#include <iostream>
#include <xmmintrin.h>
using namespace std;
int main()
{
float a, b;
cin >> a >> b;
float res = (a b) * (a - b);
cout << res << endl;
__m128 a_vec, b_vec, res_vec;
a_vec = _mm_set1_ps(a);
b_vec = _mm_set1_ps(b);
res_vec = _mm_mul_ps(_mm_add_ps(a_vec, b_vec),
_mm_sub_ps(a_vec, b_vec));
float *res_ptr;
res_ptr = (float *) amp;res_vec;
for (int i = 0; i < 4; i)
cout << "RES[" << i << "]: " << res_ptr[i] << ' ';
cout << endl;
return 0;
}
Второй файл math_traits.cc
выполняет шаблон черты. Сборка, созданная при компиляции с -O3
помощью, почти идентична math_cmp.cc
сборке .
// math_traits.cc
#include <iostream>
#include <xmmintrin.h>
using namespace std;
template <typename T>
class MathOps
{
};
template <typename T>
T kernel (T a, T b)
{
T res = MathOps<T>::mul(MathOps<T>::add(a, b), MathOps<T>::sub(a, b));
return res;
}
template <>
class MathOps <float>
{
public:
static float add (float a, float b)
{
return a b;
}
static float sub (float a, float b)
{
return a - b;
}
static float mul (float a, float b)
{
return a * b;
}
};
template <>
class MathOps <__m128>
{
public:
static __m128 add (__m128 a, __m128 b)
{
return a b;
}
static __m128 sub (__m128 a, __m128 b)
{
return a - b;
}
static __m128 mul (__m128 a, __m128 b)
{
return a * b;
}
};
int main ()
{
float a, b;
cin >> a >> b;
cout << kernel<float>(a, b) << endl;
__m128 a_vec, b_vec, res_vec;
a_vec = _mm_set1_ps(a);
b_vec = _mm_set1_ps(b);
res_vec = kernel<__m128>(a_vec, b_vec);
float *res_ptr;
res_ptr = (float *) amp;res_vec;
for (int i = 0; i < 4; i)
cout << "RES[" << i << "]: " << res_ptr[i] << ' ';
cout << endl;
return 0;
}
ОБНОВЛЕНИЕ I: Я думаю, вопрос можно резюмировать следующим образом: «Существует ли современный подход C , который эквивалентен полиморфным функциям, подобным макросам?»
Интересно, можно ли запрограммировать на C для написания одного ядра с абстрактными операциями и автоматического создания специфичных для ISA кодов. Например, универсальное ядро может быть:
RET_TYPE kernel(IN_TYPE a, IN_TYPE b)
{
RET_TYPE res = ADD(a, b);
return res;
}
И ядро может быть преобразовано в обе скалярные версии:
float kernel(float a, float b)
{
float res = a b;
return res;
}
и векторизованная версия:
__m128 kernel(__m128 a, __m128 b)
{
__m128 res = _mm_add_ps(a, b);
return res;
}
На самом деле универсальные ядра были бы намного сложнее. Универсальность типов может быть обработана параметрами шаблона. Но универсальность инструкций заставила меня застрять.
Обычно такого рода проблемы решаются с помощью генерации кода, когда вы пишете программу в некотором промежуточном представлении (IR), а затем переводите выражение IR на другой целевой язык.
Однако я должен делать это в чистом и современном C , что означает отсутствие макросов C. Интересно, достижимо ли это, умело используя универсальное программирование, шаблонное метапрограммирование или ООП. Пожалуйста, помогите, если у вас есть какие-то указания!
Комментарии:
1. Исследовали ли вы существующие библиотеки для параллельного программирования на основе шаблонов в C ? Однако в вашем последнем абзаце создается впечатление, что это домашнее задание.
2. @JAB Я бы предположил, что вопрос выходит за рамки обычного домашнего задания на C . Но я могу ошибаться. Ограничение в последнем абзаце связано с существующим дизайном проекта.
3. @JAB. Добавлены примеры, иллюстрирующие идею. Дайте мне знать, если это имеет смысл. Спасибо.
Ответ №1:
Как правило, это достижимо с помощью шаблонов и признаков типа:
template <typename T>
T kernel(T a, T b)
{
return MathTraits<T>::add (a, b);
}
template <typename T>
class MathTraits
{
}
// specialization for float
template <>
class MathTraits <float>
{
float add(float a, float b)
{
return a b;
}
}
// specialization of MathTraits for __m128 etc.
Однако этот подход может привести к сбою, если вы захотите по-разному обрабатывать один и тот же тип в разных ситуациях. Но это общий предел перегрузки…
В приведенном примере на самом деле возможно напрямую специализировать функцию, но описанный способ является более распространенным, поскольку он более понятен и используется повторно.
Комментарии:
1. Спасибо @Zbynek Vyskovsky — kvr000. Похоже, это может сработать. Но я предполагаю, что производительность ухудшится из-за использования класса-оболочки
MathTraits
и накладных расходов на вызов функции. Я мог бы принудительно встроить специализированные версииadd
, но я сомневаюсь, что это сильно помогло бы.2. Я думаю, вопрос можно резюмировать следующим образом: «Существует ли современный подход C , который эквивалентен полиморфным функциям, подобным макросам?»
3. @K.G. «Существует ли современный подход C , который эквивалентен макросам, подобным полиморфным функциям?» Да, использование шаблонов и признаков типа (хотя истинный полиморфизм может быть сложно реализовать с помощью аргументов шаблона в зависимости от того, что вы пытаетесь сделать). Хотя производительность, безусловно, ухудшится из-за использования класса-оболочки / накладных расходов на вызовы функций в неоптимизированном коде, большинство современных компиляторов автоматически вставляют такие короткие фрагменты при использовании оптимизированных сборок, так что все должно быть в порядке.