我正在编写一个C++库,它对音频数据的向量进行一些计算。
这个库既支持GPU (使用推力,也支持GPU的类似C++ STL库),也支持CPU(使用STL)。我使用的是CUDA工具包10.2,它仅限于GCC 8(因此限制我使用C++14)。所有这些都在运行Fedora 32的amd64桌面计算机上。
库包含不同的类,每个类都有一个CPU和GPU版本。我正在寻找一种简洁的方法来定义CPU/GPU变体而不重复代码。有时候,当我修复GPU算法中的一个错误时,我忘记了在CPU算法中修复它,反之亦然。另外,如果它可以是库级的东西,那么如果我实例化"AlgorithmA-CPU",它会在内部使用“AlgorithmB”,GPU也是这样。
下面是一个简单的例子:
struct WindowCPU {
std::vector<float> window{1.0, 2.0, 3.0};
}
struct WindowGPU {
thrust::device_vector<float> window{1.0, 2.0, 3.0};
}
class AlgorithmCPU {
public:
std::vector<float> scratch_buf;
WindowCPU window;
AlgorithmCPU(size_t size) : scratch_buf(size, 0.0F) {}
void process_input(std::vector<float>& input) {
// using thrust, the code ends up looking identical
thrust::transform(input.begin(), input.end(), scratch_buf.begin(), some_functor());
}
};
class AlgorithmGPU {
public:
thrust::device_vector<float> scratch_buf;
WindowGPU window;
AlgorithmGPU(size_t size) : scratch_buf(size, 0.0F) {}
void process_input(thrust::device_vector<float>& input) {
// using thrust, the code ends up looking identical
thrust::transform(input.begin(), input.end(), scratch_buf.begin(), some_functor());
}
};这个示例过于简化,但它与我的所有算法都有相同的问题--除了不同的数据类型之外,代码是相同的-- CPU使用"std::vector",而GPU使用"thrust::device_vector“。另外,还有一种“级联”专门化-- "AlgorithmCPU“使用"WindowCPU",而GPU类似。
下面是我目前在代码中的一个实际示例,它应用于上述伪算法,以减少代码重复:
template <typename A>
static void _execute_algorithm_priv(A input, A output) {
thrust::transform(input.begin(), input.end(), output.begin(), some_functor());
}
// GPU specialization
void AlgorithmGPU::process_input(thrust::device_vector<float>& input)
{
_execute_algorithm_priv<thrust::device_vector<float>&>(
input, scratch_buf);
}
// CPU specialization
void AlgorithmCPU::process_input(std::vector<float>& input)
{
_execute_algorithm_priv<std::vector<float>&>(
input, scratch_buf);
}现在在真正的代码中,我有很多算法,有些是巨大的。我的想象力不能延伸到全球图书馆范围内的解决方案。我想到了使用枚举的东西:
enum ComputeBackend {
GPU,
CPU
}之后,我将根据枚举创建类的模板--但我需要将枚举映射到不同的数据类型:
template <ComputeBackend b> class Algorithm {
// somehow define other types based on the compute backend
if (ComputeBackend b == ComputeBackend::CPU) {
vector_type = std::vector<float>;
other_type = Ipp32f;
} else {
vector_type = thrust::device_vector<float>;
other_type = Npp32f;
}
}我读过关于"if static ()“的文章,但我不相信我能在C++14中使用它。
以下是我根据目前的答复得出的解决方案:
enum Backend {
GPU,
CPU
};
template<Backend T>
struct TypeTraits {};
template<>
struct TypeTraits<Backend::GPU> {
typedef thrust::device_ptr<float> InputPointer;
typedef thrust::device_vector<float> RealVector;
typedef thrust::device_vector<thrust::complex<float>> ComplexVector;
};
template<>
struct TypeTraits<Backend::CPU> {
typedef float* InputPointer;
typedef std::vector<float> RealVector;
typedef std::vector<thrust::complex<float>> ComplexVector;
};
template<Backend B> class Algorithm {
typedef typename TypeTraits<B>::InputPointer InputPointer;
typedef typename TypeTraits<B>::RealVector RealVector;
typedef typename TypeTraits<B>::ComplexVector ComplexVector;
public:
RealVector scratch_buf;
void process_input(InputPointer input);
};发布于 2020-09-10 08:14:33
一个可能的解决方案是使用模板并将CPU / GPU特定的内容移动到一个特性类中:
struct CPUBackendTraits {
template <typename T>
using vector_type = std::vector<T>;
};
struct GPUBackendTraits {
template <typename T>
using vector_type = thrust::device_vector<T>;
};
template <typename BackendTraits>
struct Window {
typename BackendTraits::vector_type<float> window{1.0, 2.0, 3.0};
};
template <typename BackendTraits>
class Algorithm {
typename BackendTraits::vector_type<float> scratch_buf;
Window<BackendTraits> window;
Algorithm(std::size_t size) : scratch_buf(size, 0.f) {}
void process_input(typename BackendTraits::vector_type<float>& input) {
thrust::transform(input.begin(), input.end(), scratch_buf.begin(), some_functor());
}
};typename BackendTraits::前缀可能很烦人,但是可以通过向类中添加相应的typedef或using语句来省略它。
在某些情况下,您不仅希望使用不同的类型,还可以调用不同的代码。例如,这可以通过将代码作为函数添加到特性类中来完成。然而,使用函数重载有时可以减少混淆:
void do_something(std::vector<float>& input) {
// do something std::vector specific
}
void do_something(thrust::vector<float>& input) {
// do something thrust::vector specific
}
template <typename BackendTraits>
class Algorithm {
void do_something_backend_specific() {
typename BackendTraits::vector_type<float> buf = ...;
// Call either std::vector or thrust::vector overload:
do_something(buf);
}
}与带有条件的enum相比,有一些优点:
当然,也有一些缺点:
https://softwareengineering.stackexchange.com/questions/415724
复制相似问题