首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >创建C++14库,其中每个类都有两个相似的变体

创建C++14库,其中每个类都有两个相似的变体
EN

Software Engineering用户
提问于 2020-09-09 17:37:03
回答 1查看 250关注 0票数 5

我正在编写一个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也是这样。

下面是一个简单的例子:

代码语言:javascript
复制
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类似。

下面是我目前在代码中的一个实际示例,它应用于上述伪算法,以减少代码重复:

代码语言:javascript
复制
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);
}

现在在真正的代码中,我有很多算法,有些是巨大的。我的想象力不能延伸到全球图书馆范围内的解决方案。我想到了使用枚举的东西:

代码语言:javascript
复制
enum ComputeBackend {
    GPU,
    CPU
}

之后,我将根据枚举创建类的模板--但我需要将枚举映射到不同的数据类型:

代码语言:javascript
复制
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中使用它。

编辑

以下是我根据目前的答复得出的解决方案:

代码语言:javascript
复制
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);
};
EN

回答 1

Software Engineering用户

回答已采纳

发布于 2020-09-10 08:14:33

一个可能的解决方案是使用模板并将CPU / GPU特定的内容移动到一个特性类中:

代码语言:javascript
复制
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::前缀可能很烦人,但是可以通过向类中添加相应的typedefusing语句来省略它。

在某些情况下,您不仅希望使用不同的类型,还可以调用不同的代码。例如,这可以通过将代码作为函数添加到特性类中来完成。然而,使用函数重载有时可以减少混淆:

代码语言:javascript
复制
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相比,有一些优点:

  • 模板编程允许您使用不同的类型。
  • 不需要选择后端的if- the语句。只需传递特性类,一切都会自动使用相同的后端。
  • 添加新的后端很简单,只需添加一个新的特性类。

当然,也有一些缺点:

  • 读取和编写模板代码可能比读取和编写具有具体类型的代码更困难。
  • 所有代码都在模板中,因此,如果要对该代码使用.cpp文件,则必须添加显式模板实例化。
票数 9
EN
页面原文内容由Software Engineering提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://softwareengineering.stackexchange.com/questions/415724

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档