首页 | 新闻 | 新品 | 文库 | 方案 | 视频 | 下载 | 商城 | 开发板 | 数据中心 | 座谈新版 | 培训 | 工具 | 博客 | 论坛 | 百科 | GEC | 活动 | 主题月 | 电子展
返回列表 回复 发帖

使用 OpenCL 加速 Web 应用程序(2)编写 WebCL 应用程序

使用 OpenCL 加速 Web 应用程序(2)编写 WebCL 应用程序

编写 WebCL 应用程序WebCL 技术对新手来说会很神秘,所以牢记以下总目标非常重要:
  • 向设备递交一个函数。
  • 在设备上执行函数。
  • 从设备向主机传输输出
显示了这一过程。
图 1. WebCL 应用程序的操作WebCL 类和函数要使用 Nokia 的工具集构建一个 WebCL 应用程序,则需要熟悉七个 JavaScript 类。表 1 列出每个类及其重要的函数。
表 1. 重要的 WebCL 类和函数类用途函数IWebCL包含访问平台和设备的静态函数getPlatformIDs()

createContext(properties, devices)

createContextFromType(properties, device_type)IWebCLPlatform代表一个安装在主机上的 OpenCL SDKgetDeviceIDs(device_type)IWebCLDevice代表一个符合 OpenCL 标准的物理设备getDeviceInfo(name)IWebCLContext管理程序、内存对象和命令队列createProgramWithSource(source_text)

createBuffer(flags, size)

createCommandQueue(device, properties)IWebCLProgram源代码包含一个或者多个内核函数buildProgram(devices, options)

createKernel(kernel_name)IWebCLKernel特殊编码函数,能够通过一个 OpenCL 编译设备执行setKernelArg(index, value, type)IWebCLCommandQueue支持主机和设备间的通信enqueueReadBuffer(mem_object, blocking, offset, size,                                 data_object, wait_list)

enqueueWriteBuffer(mem_object, blocking, offset, size,                                 data_object, wait_list)

enqueueTask(kernel, wait_list)

enqueueNDRangeKernel(kernel, dim, offset, global_size,                                 local_size, wait_list)
理解这些类如何相互配合使用需要花费一些时间,但是,一旦编码完成,只需极小改动就可以将一个 WebCL 应用程序复制粘贴到其他应用程序中。
一个简单示例示例代码(可以从  部分获取)中包含一个名为 simple.html 的文件,提供了完整的 WebCL 应用程序。JavaScript 代码和大多数 WebCL 应用程序代码一样,可以分为五个步骤:
  • WebCL 应用程序首先创建了一个 IWebCLContext 对象来管理内核部署。WebCL.createContextFromType 函数带两个变量:一个包含大量数值的数组和一个用于识别环境中设备类型的值。以下代码展示了工作原理。 清单 1.  创建了一个环境,并访问其设备
    1
    2
    3
    4
    var platforms = WebCL.getPlatformIDs();
    var ctx_props = [WebCL.CL_CONTEXT_PLATFORM, platforms[0]];
    var ctx = WebCL.createContextFromType(ctx_props, WebCL.CL_DEVICE_TYPE_GPU);
    var devices = ctx.getContextInfo(WebCL.CL_CONTEXT_DEVICES);




    CL_DEVICE_TYPE_GPU 参数表示这个环境只包含 GPU 设备。最后一行将环境设备放置在一个数组中,这个数组通常只包含一个元素:IWebCLDevice,代表主机的 GPU。
  • 环境从源代码创建了一个 IWebCLProgram。应用程序然后对程序进行编译,从其中一个内核函数中创建一个 IWebCLKernel。这可以通过以下代码完成:清单 2.  编译了程序,并创建了一个内核
    1
    2
    3
    4
    5
    6
    7
    var program_src = "__kernel void basic(__global float4* in_vec,     \
                                           __global float4* out_vec) {  \
                           out_vec[0] = in_vec[0];                      \
                       }";
    var program = ctx.createProgramWithSource(program_src);
    program.buildProgram([devices[0]], "");
    var kernel = program.createKernel("basic");




    了解程序内核 之间的差异非常重要。程序 是包含一个或者多个函数的一段代码。内核 代表程序代码中的一个单一函数。
  • 内核可以由名为 basic 的函数创建,该函数接收两个参数:in_vec 和 out_vec。这两个参数都存储在设备的寻址空间,所以应用将会创建两个 IWebCLMemoryObject(in_buff 和 out_buff),并将其变为内核参数。以下代码显示了如何完成这一工作。清单 3.  创建了内存对象,并将其变为内核参数
    1
    2
    3
    4
    var in_buff = ctx.createBuffer(WebCL.CL_MEM_READ_ONLY, 16);
    var out_buff = ctx.createBuffer(WebCL.CL_MEM_WRITE_ONLY, 16);
    kernel.setKernelArg(0, in_buff);
    kernel.setKernelArg(1, out_buff);




    createBuffer 的参数设置了内存对象属性。第一个参数将会识别内核参数是提供输入(CL_MEM_READ_ONLY)还是接收输出(CL_MEM_WRITE_ONLY)。createBuffer 的第二个参数将会识别数据大小,以字节为单位。每个内存对象包含四个 4 字节的浮点数,因此其大小被设置为 16。
  • 现在已经完成了内核配置,应用程序将创建一个 IWebCLCommandQueue,将指令从主机传输到内核。以下代码能够创建指令队列,将数据写入内核的输入参数(in_vec),然后通过调用 enqueueTask 加载内核。清单 4.  创建了指令队列,写入输入数据,并加载内核
    1
    2
    3
    4
    var queue = ctx.createCommandQueue(devices[0], 0);
    var in_data = new Float32Array([1.5, 2.5, 3.5, 4.5]);
    queue.enqueueWriteBuffer(in_buff, false, 0, 16, in_data, []);
    queue.enqueueTask(kernel, []);




    输入数据以 32 位浮点数组的形式提供。内核数据无须提供浮点类数,但它的值必须存储在相似类型的数组中,例如 Int32Array 或者 UInt16Array。
  • 为了验证内核是否被正确执行,应用程序将从内核第二个参数中读取数据,该参数作为 out_buff 内存对象提供。以下代码将数据从 out_buff 中读取到名为 out_data 的数组中,并在 Web 页面上显示数组的内容。清单 5.  从设备中读取输出数据,并将其显示在页面上
    1
    2
    3
    4
    5
    out_data = new Float32Array(4);
    queue.enqueueReadBuffer(out_buff, true, 0, 16, out_data, []);      
    var output = document.getElementById("output");
    output.innerHTML = "Output: " + out_data[0] + ", " + out_data[1] + ", ";
    output.innerHTML += out_data[2] + ", " + out_data[3];




    这些代码通过访问 HTML 文档中称之为 output 的元素来显示输出数据。想要查看页面的 HTML 内容,请下载本文的示例代码,并在 Firefox 中打开 simple.html。
工作项和 enqueueNDRangeKernel上述应用程序通过调用 enqueueTask 函数执行。在学习 WebCL 时这样没有问题,但是专业应用程序决不会使用这个函数,因为 enqueueTask 只使用了单一的线程执行内核,这有悖于大量使用 GPU 这类多并行设备的目的。
在 WebCL 中,处理线程被称为工作项。要理解如何使用它们,则需要参考 清单 6 中处理线程的嵌套循环。
清单 6. 处理线程
1
2
3
4
5
6
7
for(i=a; i<A; i++) {
   for(j=b; j<B; j++) {
      for(k=c; k<C; k++) {
         process_data(i, j, k);
      }
   }
}




process_data 函数有三个参数:i、j 和 k。每个参数都有不同的偏移量(a、b 和 c),以及不同的最大范围(A、B 和 C)。因此,参数范围就是 A - a、B - b 和 C - c。OpenCL 术语将索引范围称之为大小,指标数量称之为维数。因此,该循环有三个维度,大小分别为 A - a、B - b 和 C - c。
WebCL 的最大优势就是使用工作项执行函数迭代,例如 process_data,而不是时间密集型循环。工作项有不同维度(1、2 或者 3),它们每个都有惟一标识符,这些标识符都对应循环的特定迭代。如果工作项是使用二维执行的,那么一个工作项可能执行有惟一 ID (4, 5) 的内核,而另一个执行 ID 是 (5, 4) 的内核。请记住,这些工作项是并行执行的。
在 WebCL 中配置工作项,必须执行 enqueueNDRangeKernel,而不是 enqueueTask。这是 WebCL 编程接口中最重要的函数,它接受 6 个参数:
  • kernel:IWebCLKernel 对应所要执行的函数。
  • dim:执行函数所使用的工作项维数。
  • offsets:包含工作项标识符初始值的数组。
  • global_sizes:包含各个维中工作项总数的数组。
  • local_sizes:包含工作组中各个维度的工作项数量的数组。
  • wait_list:包含 IWebCLEvent 结构的数组,由命令等待列表组成。
作为一个例子,可以考虑 清单 7 中的循环。
清单 7.  配置了一个 enqueueNDRangeKernel
1
2
3
4
5
6
7
for(i=5; i<50; i++) {
   for(j=6; j<60; j++) {
      for(k=7; k<70; k++) {
         process_data(i, j, k);
      }
   }
}




维数是 3,偏移量是 (5, 6, 7),工作项大小是 (45, 64, 73)。要使用 WebCL 执行 process_data,可以创建一个 IWebCLKernel 函数,并调用 enqueueNDRangeKernel 执行内核,如下所示:
1
queue.enqueueTask(kernel, 3, [5, 6, 7], [45, 64, 73], [], []);




最后两个参数被设置为空数组。虽然工作组和事件处理都是 WebCL 的优势,但它们不在本文的讨论范围之内。下一节将会介绍如何编写内核函数。
返回列表