我正在嘗試使用這樣的矢量化函式來模仿 CUDA/OpenCL 作業流程:
#include <omp.h>
#include <iostream>
#include <string>
#include <functional>
#include<cmath>
template<typename Type, int Simd>
struct KernelData
{
alignas(32)
Type data[Simd];
inline void readFrom(const Type * const __restrict__ ptr) noexcept
{
for(int i=0;i<Simd;i )
{
data[i] = ptr[i];
}
}
inline void writeTo(Type * const __restrict__ ptr) const noexcept
{
for(int i=0;i<Simd;i )
{
ptr[i] = data[i];
}
}
inline const KernelData<Type,Simd> sqrt() const noexcept
{
KernelData<Type,Simd> result;
for(int i=0;i<Simd;i )
{
result.data[i] = std::sqrt(data[i]);
}
return result;
}
};
template<int mask>
struct KernelDataFactory
{
KernelDataFactory()
{
}
template<typename Type>
inline
KernelData<Type,mask> generate() const
{
return KernelData<Type,mask>();
}
};
template<int SimdWidth, typename... Args>
class Kernel
{
public:
Kernel(std::function<void(int,int, Args...)> kernelPrm)
{
kernel = kernelPrm;
}
void run(int n, Args... args)
{
const int nLoop = (n/SimdWidth);
for(int i=0;i<nLoop;i )
{
kernel(i*SimdWidth,SimdWidth, args...);
}
if((n/SimdWidth)*SimdWidth != n)
{
const int m = n%SimdWidth;
for(int i=0;i<m;i )
{
kernel(nLoop*SimdWidth i,1, args...);
}
}
}
private:
std::function<void(int,int, Args...)> kernel;
};
// cpu cycles from stackoverflow
#include <stdint.h> // <cstdint> is preferred in C , but stdint.h works.
#ifdef _MSC_VER
# include <intrin.h>
#else
# include <x86intrin.h>
#endif
inline
uint64_t readTSC() {
// _mm_lfence(); // optionally wait for earlier insns to retire before reading the clock
uint64_t tsc = __rdtsc();
// _mm_lfence(); // optionally block later instructions until rdtsc retires
return tsc;
}
int main(int argC, char** argV)
{
constexpr int simd = 16;
constexpr int n = 1003;
Kernel<simd, float *, float *> kernel([](int simdGroupId, int simdWidth, float * input, float * output){
const int id = simdGroupId;
if(simdWidth == simd)
{
const KernelDataFactory<simd> factory;
auto a = factory.generate<float>();
a.readFrom(input id);
const auto b = a.sqrt().sqrt().sqrt().sqrt().sqrt().
sqrt().sqrt().sqrt().sqrt().sqrt().
sqrt().sqrt().sqrt().sqrt().sqrt();
b.writeTo(output id);
}
else
{
const KernelDataFactory<1> factory;
auto a = factory.generate<float>();
a.readFrom(input id);
const auto b = a.sqrt().sqrt().sqrt().sqrt().sqrt().
sqrt().sqrt().sqrt().sqrt().sqrt().
sqrt().sqrt().sqrt().sqrt().sqrt();
b.writeTo(output id);
}
});
alignas(32)
float i[n],o[n];
for(int j=0;j<n;j )
i[j]=j;
auto t1 = readTSC();
for(int k=0;k<10000;k )
kernel.run(n,i,o);
auto t2 = readTSC();
for(int i=n-10;i<n;i )
{
std::cout<<"i="<<i<<" value="<<o[i]<<std::endl;
}
std::cout<<0.0001f*(t2-t1)/(float)(15*n)<<" cycles per sqrt"<<std::endl;
return 0;
}
但是用戶給出的部分必須像這樣復制:
Kernel<simd, float *, float *> kernel([](int simdGroupId, int simdWidth, float * input, float * output){
const int id = simdGroupId;
if(simdWidth == simd)
{
const KernelDataFactory<simd> factory;
auto a = factory.generate<float>();
a.readFrom(input id);
const auto b = a.sqrt().sqrt().sqrt().sqrt().sqrt().
sqrt().sqrt().sqrt().sqrt().sqrt().
sqrt().sqrt().sqrt().sqrt().sqrt();
b.writeTo(output id);
}
else
{
const KernelDataFactory<1> factory;
auto a = factory.generate<float>();
a.readFrom(input id);
const auto b = a.sqrt().sqrt().sqrt().sqrt().sqrt().
sqrt().sqrt().sqrt().sqrt().sqrt().
sqrt().sqrt().sqrt().sqrt().sqrt();
b.writeTo(output id);
}
});
唯一的區別是編譯時已知的兩個模板要產生:
KernelDataFactory<1> and KernelDataFactory<simd>
使用定義宏,很容易只復制 lambda 的函式體。我試圖在不使用任何定義宏的情況下做到這一點。有沒有一種簡單的方法可以讓用戶只給出這個:
auto a = factory.generate<float>();
a.readFrom(input id);
const auto b = a.sqrt().sqrt().sqrt().sqrt().sqrt().
sqrt().sqrt().sqrt().sqrt().sqrt().
sqrt().sqrt().sqrt().sqrt().sqrt();
b.writeTo(output id);
并且它會被實作自動復制?
當前實作的作用是:
- 需要 n
- 把它分成兩部分
- 運行矢量化代碼直到
n - (n%simd)到達點 n - (n%simd)從到運行標量代碼n
KernelDataFactory 模板引數(必須是編譯時已知的)(1 和 simd)用于讓編譯器生成矢量化代碼。(在 godbolt.org (avx512) 上,它以“每 sqrt 0.9 個周期”的速度運行,而在我的系統 (avx1) 上,它是每 sqrt 3.8 個周期。)
uj5u.com熱心網友回復:
您可以使用通用 lambda (C 14) 來實作這樣的目標。請注意,這需要您更改內核的型別Kernel::kernel并稍微更改內核的創建以允許自動型別推斷:
核心
template<int SimdWidth, typename F, typename... Args>
class Kernel
{
public:
Kernel(F&& kernelPrm)
: kernel(std::move(kernelPrm))
{
}
void run(int n, Args... args)
{
const int nLoop = (n / SimdWidth);
for (int i = 0; i < nLoop; i )
{
CallKernel(i * SimdWidth, SimdWidth, args...);
}
if ((n / SimdWidth) * SimdWidth != n)
{
const int m = n % SimdWidth;
for (int i = 0; i < m; i )
{
CallKernel(nLoop * SimdWidth i, 1, args...);
}
}
}
private:
// helper function creating the factory and passing it to kernel
void CallKernel(int simdGroupId, int simdWidth, Args... args)
{
const int id = simdGroupId;
if (simdWidth == SimdWidth)
{
const KernelDataFactory<SimdWidth> factory;
kernel(factory, id, args...);
}
else
{
const KernelDataFactory<1> factory;
kernel(factory, id, args...);
}
}
F kernel;
};
幫手
這些助手是推斷內核的第二個模板引數所必需的。
// helper for specifying the parameter pack
template<class...Args>
struct KernelArgs
{};
template<int SimdWidth, typename F, class...Args>
auto CreateKernel(F&& kernelPrm, KernelArgs<Args...> const&)
{
return Kernel<SimdWidth, F, Args...>(std::forward<F>(kernelPrm));
}
主要的
...
auto kernel = CreateKernel<simd>([](auto& factory, int const id, float* input, float* output)
{
auto a = factory.template generate<float>();
a.readFrom(input id);
const auto b = a.sqrt().sqrt().sqrt().sqrt().sqrt().
sqrt().sqrt().sqrt().sqrt().sqrt().
sqrt().sqrt().sqrt().sqrt().sqrt();
b.writeTo(output id);
}, KernelArgs<float*, float*>{});
...
轉載請註明出處,本文鏈接:https://www.uj5u.com/qiye/459558.html
