HyCuda是一个创新的项目,旨在简化在不同设备上运行算法的复杂性。它通过读取程序员提供的规范文件来生成C++代码,该文件详细描述了算法的各个部分。HyCuda的核心思想是允许开发者在编译时决定哪些子程序运行在CPU上,哪些运行在GPU上,从而实现高效的算法执行。
在进行CUDA实现工作时,意识到自己不可能是唯一一个想要在不同设备配置上测试程序性能的人。想要看到当在不同的设备上运行一个或多个子程序时,程序性能会受到怎样的影响。这当然意味着需要为CPU和GPU实现相同的算法两次,并且代码需要进行多次修改以确保所有数据都位于正确的位置。这就是开始开发一个模板框架来处理这些任务的时候。然而,这个框架特别适用于需求和所工作的特定算法。然后开始思考如何以更通用的方式处理这个问题,于是就有了HyCuda。
HyCuda通过读取规范文件来工作,该文件由程序员提供,描述了算法。它包含有关子程序的信息,它们使用的数据以及这些数据的类型。然后它生成一个C++模板框架,可以在其中实例化一个类模板,模板参数包含有关想要使用的设备的信息。这允许实例化多个对象,执行相同的算法,但在不同的机器上。模板(元编程)机制在编译时显然会处理哪些数据需要移动以及何时移动。
如引言中所述,HyCuda基于描述算法的规范文件生成C++代码。该文件包含四个部分,由两个连续的百分号(%%)分隔:
下面是一个示例,取自手册的示例部分:
/* filename: spec.hycuda */
// Directives-Section
%namespace: Example
%class-name: ExampleAlgorithm
%parameters: Params {
float m1;
float m2;
}
%% // Memory-Section
vec1 (i) : float, vectorSize
vec2 (i) : float, vectorSize
vec3 : float, vectorSize
sum (o) : float, 1
%% // Routine-Section
multiplyM1 : vec1 (rw), vec2 (rw)
multiplyM2 : vec3 (rw)
addVec1Vec2 : vec1 (r), vec2 (r), vec3 (w)
sumVec3 : vec3 (r), sum (w)
%% // Routine-order
%order : $1, $3, $2, $4
这个相当简单的示例描述了一个执行一些向量操作的算法。它将生成一个名为ExampleAlgorithm的类,声明在Example命名空间中,其行为由两个名为m1和m2的float参数定义。在此过程中,将使用3个不同的向量:vec1、vec2和vec3,它们都包含vectorSize个float。前两个向量将作为输入,由(i)指定,而第三个向量将是中间产品,因此没有与之关联的输入或输出指定符。输出将被称为sum,并且只包含一个元素(再次是一个float)。
将跳过函数/内核实现的部分。这在手册中有详细描述。相反,将转到已经设置好一切并想要运行算法的部分。为了做到这一点,需要指定哪个子程序将在哪个设备上运行。这是通过传递一个名为DevicePolicies的模板参数到结果类来完成的。为了方便,HyCuda生成了一个默认策略已经设置好的头文件。这个文件看起来像下面的片段:
/*
filename: examplealgorithm.h */
#include "examplealgorithm_algorithm.h"
#include "examplealgorithm_hybrid.h"
#include "examplealgorithm_devicepolicies.h"
namespace Example {
// Specify which device to use for each of the routines (CPU/GPU)
typedef DevicePolicies<
MultiplyM1Device,
MultiplyM2Device,
AddVec1Vec2Device,
SumVec3Device
> CustomPolicy;
typedef Hybrid_ Hybrid;
typedef ExampleAlgorithm_ ExampleAlgorithm;
}
实际上在这个小片段中发生了很多事情,但主要的事情是类模板DevicePolicies的typedef。它接受多个模板参数,这些参数本身是类模板(例如MultiplyM1Device)。这些参数都是以规范文件中的例程命名的,它们自己的参数告诉算法使用哪个设备来执行每个特定的例程。如果正确实现,策略列表中使用的CPU/GPU参数的每种排列都将导致以不同的方式使用设备,但产生相同的结果。
以下是使用算法处理一些输入的主函数:
/*
examplealgorithm_main.cc */
#include "examplealgorithm.h"
using namespace Example;
using namespace std;
size_t readVectorsFromFile(char const *filename, float **v1, float **v2);
int main(int argc, char **argv) {
// Initialize parameters
Params params;
params.m1 = 2;
params.m2 = 3;
// Initialize vectors
float *v1, *v2;
size_t vectorSize = readVectorsFromFile(argv[1], &v1, &v2);
// Initialize input
Input in;
in.vec1 = {v1, vectorSize};
in.vec2 = {v2, vectorSize};
// Initialize output
float sum;
Output out;
out.sum = {∑, 1};
// Process
ExampleAlgorithm alg(params);
alg.process(in, out);
// Output
cout << "Sum: " << sum << '\n';
}
输入向量不属于用户,用户应确保它们被正确分配和初始化。输出数据(在这种情况下是sum)也是如此。然后使用参数初始化算法,并调用其成员process,将输入和输出作为其参数。当它返回时,sum将包含适当的值。
意识到如果实际上想要使用程序,本文中的信息是绝对不够的。然而,希望它能为提供HyCuda是什么以及它如何运作的品味。更多信息,请参阅项目页面:
此外,很乐意收到反馈。到目前为止,还没有编写一个适当的构建脚本来让生活更轻松。想只有在真正有人打算使用它时才需要这样做。所以,如果认为这个程序有任何潜力(或者没有),请告诉,将会更加努力。