sycl异构编程

学习了一天的sycl编程,感觉是挺好的一种技术标准,有intell的dpc++,codeplay的computecpp编译器做的不错,可以用amd和intel的gpu,但是都不支持英伟达的gpu,太可惜。

 

SYCL是一种用于编程异构系统的开放式行业标准。SYCL的设计允许编写标准的C++源代码,使其可以在异构设备或主机上运行。

用于SYCL的术语历史上继承自OpenCL,并添加了一些特定于SYCL的内容。然而,SYCL是一种通用的C++编程模型,可以在OpenCL之外的其他异构API之上进行布局。

SYCL(发音为“sickle”)是一种用于异构计算的免版税、跨平台抽象C++编程模型。SYCL基于并行API或OpenCL等标准的基本概念、可移植性和效率,同时增加了单源C++的易用性和灵活性。

使用SYCL的开发人员能够使用他们习惯的许多技术(如继承和模板)编写标准的现代C++代码。同时,开发人员可以通过SYCL库的功能访问底层实现(如OpenCL)的全部功能,必要时还可以通过API与直接使用底层实现编写的代码进行互操作。

为了减少编程工作量并提高开发人员编写代码的灵活性,SYCL以几种方式扩展了OpenCL模型等标准中的概念,超越了C++特性的一般用途:

•在异构设备上执行并行内核既方便又灵活。常见的并行模式使用简单语法进行优先排序,通过一系列C++类型,程序员可以在需要时表达额外的需求,如同步;

•当使用缓冲区和访问器时,SYCL中的数据访问与数据存储分离。通过依赖C++风格的资源获取即初始化(RAII)习惯用法来捕获设备代码块之间的数据依赖关系,运行时库可以跟踪数据移动并提供正确的行为,而无需手动管理内核实例之间的事件依赖关系,也无需程序员显式移动数据。这种方法使数据并行任务图(可能已经是执行模型的一部分)能够由SYCL程序员轻松安全地构建;

•统一共享内存(USM)为显式数据分配和移动提供了一种机制。这种方法允许在异构设备上使用基于指针的算法和数据结构,并允许跨主机和设备增加代码的重用;

•分层并行语法提供了一种以易于理解的现代C++形式表达数据并行性的方法,类似于OpenCL设备或OpenMP目标设备执行模型。它更清晰地分层并行循环和同步点,以避免代码碎片,并更有效地映射到CPU风格的架构。

SYCL保留了受OpenCL标准启发的执行模型、运行时功能集和设备功能。该标准对SYCL能够支持的所有C++功能施加了一些限制。这确保了设备代码在尽可能广泛的设备上的可移植性。因此,虽然代码可以用标准C++语法编写,并与标准C++程序互操作,但整个C++功能集在SYCL设备代码中不可用。特别是,本规范定义的SYCL设备代码不支持虚拟函数调用、一般的函数指针、异常、运行时类型信息或可能依赖于这些特性或特定主机编译器特性的全套C++库。然而,这些基本限制可以通过一些特定的Khronos或供应商扩展来缓解。

下面是三个实例

void test0()
{
	const int N = 1024;
	int data[N];  //allocate data to be worked on
	queue myQueue;  //create default queue to enqueue work

	// By wrapping all the sycl work in a {} block, we ensure
	// all sycl tasks must complete before existing the block,
	// because the destructor of resultBuf will wait
	{
		//wrap our data varibale in a buffer
		buffer<int, 1> resultBuf{ data, range<1>{N} };

		//create a command group to issue commands to the queue
		myQueue.submit([&](handler& cgh) {
			//request access to the buffer without initialization
			accessor writeResult{ resultBuf, cgh, write_only, no_init };
			
			// enqueue a parallel for task with N work items
			cgh.parallel_for(N, [=](auto idx) {
				// Initialize each buffer element with its own rank  number starting at 0
				writeResult[idx] = idx;
			});  // end of the kernel function
		});  // end of our commands for this queue
	}  // end of scope, so we wait for work producing resultBuf to complete

	// Print result
	for (int i = 0; i < N; i++) {
		std::cout << "data[" << i << "] = " << data[i] << std::endl;
	}

}

void test1()
{
	const int N = 1024;
	//Create default queue to enqueue work
	queue myQueue;

	//Allocate shared memory bound to the device and context associated to the queue
	//Replacing malloc_shared with malloc_host would yield a correct program that
	//allocated device-visible memory on the host.
	int *data = malloc_shared<int>(N,myQueue);
	myQueue.parallel_for(N, [=](id<1>idx) {
		//Initialize each buffer element with its own rank number starting at 0
		data[idx] = idx;
	}); // End of the kernel function
	myQueue.wait();//Print result
	for (int i=0;i<N;i++)
		std::cout <<"data["<<i<<"] = "<<data[i]<<std::endl;
	
}
void test2()
{
	//size of the matrices
	constexpr size_t N = 2000;
	constexpr size_t M = 3000;
	// Create a queue to work on
	queue myQueue;
	// Create some 2D buffers of float for our matrices
	buffer<float, 2>a{ range<2>{N, M} };
	buffer<float, 2>b{ range<2>{N, M} };
	buffer<float, 2>c{ range<2>{N, M} };
	// Launch an asynchronous kernel to initialize a 
	myQueue.submit([&](handler& cgh) {
		// The kernel writes a, so get a write accessor on it
		accessor A{ a, cgh,write_only };
		// Enqueue a parallel kernel iterating on a N * M 2D iteration space
		cgh.parallel_for(range<2>{N, M}, [=](id<2>index) {
			A[index] = index[0] * 2+ index[1];
			});
		});
	// Launch an asynchronous kernel to initialize b
	myQueue.submit([&](handler& cgh) {
		// The kernel writes b, so get a write accessor on it
		accessor B{ b, cgh, write_only };
		// From the access pattern above, the SYCL runtime detects that this 
		// command_group is independent from the first one and can be 
		// scheduled independently 

		// Enqueue a parallel kernel iterating on a N * M 2D iteration space
		cgh.parallel_for(range<2>{N, M}, [=](id<2>index) {
			B[index] = index[0] * 2014 + index[1] * 42;
		});
	});
	
	// Launch an asynchronous kernel to compute matrix addition c a b
	myQueue.submit([&](handler& cgh) {
		// In the kernel a and b are read, but c is written
		accessor A{ a, cgh, read_only };
		accessor B{ b, cgh, read_only };
		accessor C{ c, cgh, write_only };
		// From these accessors, the SYCL runtime will ensure that when 
		// this kernel is run, the kernels computing aand b have completed 

		// Enqueue a parallel kernel iterating on a N * M 2D iteration space
		cgh.parallel_for(range<2>{N, M}, [=](id<2>index) {
			C[index] = A[index] + B[index];
		});
	});
	// Ask for an accessor to read c from application scope.The SYCL runtime
	// waits for c to be ready before returning from the constructor
	host_accessor C{ c, read_only };

	std::cout << std::endl << "Result:" << std::endl;
	for (size_t i = 0; i < N; i++) {
		for (size_t j = 0; j < M; j++) {
			// Compare the result to the analytic value
			if (C[i][j] != i * (2 + 2014) + j * (1 + 42)) {
				std::cout << "Wrong value " << C[i][j] << "on element " << i << " "
					<< j << std::endl;
				exit(-1);
			}
		}
	}
	std::cout <<"Good computation!"<<std::endl;

}

SYCL旨在提高数据的局部性和移动效率

➢ SYCL将数据存储与数据访问分开

➢ SYCL具有用于访问不同地址空间中的数据的独立结构

➢ SYCL允许您创建数据依赖关系图

 

 

数据相关性图的好处

•允许您根据关系描述问题

•不需要排队显式复制

•可以使用RAII执行同步

•必要时自动将数据复制回主机

•消除复杂事件处理的需要

•自动构建内核之间的相关性

•允许运行时生成数据移动优化

•在内核之前预先将数据复制到设备

•避免在内核之后将不必要的数据复制回主机

posted @ 2022-08-21 10:12  Oliver2022  阅读(1196)  评论(0编辑  收藏  举报