oneAPI GPU 优化指南 - 原子操作

本文探讨了SYCL中的原子操作,包括不同数据类型(整型和浮点数)的处理,以及在全局和本地地址空间中的应用。强调了硬件支持和性能差异,以及如何使用VTune和Advisor工具进行性能分析和优化。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

本章节翻译by chenshusmail@163.com 原文:Atomic Operations (intel.com)

目录

原子操作中的数据类型

全局和本地地址空间中的原子操作

基于 USM 数据的原子操作


原子操作允许多个 work-item 通过内存进行任何跨 work-item 通信。 SYCL 原子类似于 C++ 原子,保证被原子保护的资源的访问作为单个单元执行。 以下因素影响原子操作的性能和合法性:

  • 数据类型

  • 本地与全局地址空间

  • 主机端、共享和设备端分配的 USM

原子操作中的数据类型

下面的 kernel 展示了在 SYCL 中实现归约(reduction)操作的方法,其中每个 work-item 原子地更新全局累加器。 这种加法的输入数据类型和应用此 reduction 操作的向量是整型数。与用于 reduction 的其他技术(如阻塞 blocking)相比, 此 kernel 的性能更为合理。

    q.submit([&](auto &h) {
      sycl::accessor buf_acc(buf, h, sycl::read_only);
      sycl::accessor sum_acc(sum_buf, h, sycl::write_only, sycl::no_init);

      h.parallel_for(data_size, [=](auto index) {
        size_t glob_id = index[0];
        auto v = sycl::atomic_ref<int, sycl::memory_order::relaxed,
                                  sycl::memory_scope::device,
                                  sycl::access::address_space::global_space>(
            sum_acc[0]);
        v.fetch_add(buf_acc[glob_id]);
      });
    });

如果向量的数据类型是浮点数或双精度浮点数,如下面的 kernel 所示,由于某些加速器缺乏对浮点数或双精度原子的硬件支持, 对应的性能会受到影响。基于硬件是否支持原子操作,下面两个 kernel 演示了原子加操作(add)的执行时间有巨大差异。

//
int VectorInt(sycl::queue &q, int iter) {
  VectorAllocator<int> alloc;
  AlignedVector<int> a(array_size, alloc);
  AlignedVector<int> b(array_size, alloc);

  InitializeArray<int>(a);
  InitializeArray<int>(b);
  sycl::range num_items{a.size()};
  sycl::buffer a_buf(a);
  sycl::buffer b_buf(b);
  auto start = std::chrono::steady_clock::now();
  for (int i = 0; i < iter; i++) {
    q.submit([&](sycl::handler &h) {
      // InpuGt accessors
      sycl::accessor a_acc(a_buf, h, sycl::read_write);
      sycl::accessor b_acc(a_buf, h, sycl::read_only);

      h.parallel_for(num_items, [=](auto i) {
        auto v = sycl::atomic_ref<int, sycl::memory_order::relaxed,
                                  sycl::memory_scope::device,
                                  sycl::access::address_space::global_space>(
            a_acc[0]);
        v += b_acc[i];
      });
    });
  }
  q.wait();
  auto end = std::chrono::steady_clock::now();
  std::cout << "Vector int completed on device - took " << (end - start).count()
            << " u-secs\n";
  return ((end - start).count());
}

在使用原子操作时,必须注意确保硬件支持并且能够有效执行。 在 Gen9 和 Intel® Iris® Xe 集成显卡上不支持浮点数或双精度数据类型上的原子操作, VectorDouble 的性能将非常差。

//
int VectorDouble(sycl::queue &q, int iter) {
  VectorAllocator<double> alloc;
  AlignedVector<double> a(array_size, alloc);
  AlignedVector<double> b(array_size, alloc);

  InitializeArray<double>(a);
  InitializeArray<double>(b);
  sycl::range num_items{a.size()};
  sycl::buffer a_buf(a);
  sycl::buffer b_buf(b);

  auto start = std::chrono::steady_clock::now();
  for (int i = 0; i < iter; i++) {
    q.submit([&](sycl::handler &h) {
      // InpuGt accessors
      sycl::accessor a_acc(a_buf, h, sycl::read_write);
      sycl::accessor b_acc(a_buf, h, sycl::read_only);

      h.parallel_for(num_items, [=](auto i) {
        auto v = sycl::atomic_ref<double, sycl::memory_order::relaxed,
                                  sycl::memory_scope::device,
                                  sycl::access::address_space::global_space>(
            a_acc[0]);
        v += b_acc[i];
      });
    });
  }
  q.wait();
  auto end = std::chrono::steady_clock::now();
  std::cout << "Vector Double completed on device - took "
            << (end - start).count() << " u-secs\n";
  return ((end - start).count());
}

通过使用 VTune Profiler 分析这些 kernel, 我们可以测出硬件是否支持原子操作的影响。 您可以看到 VectorInt kernel 比 VectorDouble 和 VectorFloat 快得多。

VTune 动态指令

VTune Profiler 动态指令的分析让我们看到当没有原子操作的硬件支持时,指令计数会发生巨大变化。

这是我们 VectorInt kernel 的汇编代码。

VTune 原子整型数

与 VectorDouble 的汇编代码相比,当我们执行 VectorDouble kernel 时,需要多执行 3300 万个 GPU 指令。

VTune 原子双精度数

Intel® Advisor 工具有推荐窗格栏,可提供有关如何提高 GPU kernel 性能的建议。

Advisor 推荐窗格

Intel® Advisor 提供的一项建议是“存在低效原子操作”。当硬件中不支持原子操作时,它们会由软件来模拟。 这可以被检测到, Intel® Advisor 会给出可能解决方案的建议。

Advisor 低效原子操作

全局和本地地址空间中的原子操作

标准 C++ 内存模型假定应用程序在具有单个地址空间的单个设备上执行。但是,这两个假设都不适用于 SYCL 应用程序: 应用程序的不同部分在不同设备上执行(即主机端和一个或多个加速器); 每个设备都有多个地址空间(即私有、本地和全局);并且每个设备的全局地址空间可能是不相交的(取决于 USM 的支持)。

在全局地址空间中使用原子操作时,同样要小心,因为全局更新比本地更新慢得多。

#include <CL/sycl.hpp>
#include <iostream>
int main() {
  constexpr int N = 256 * 256;
  constexpr int M = 512;
  int total = 0;
  int *a = static_cast<int *>(malloc(sizeof(int) * N));
  for (int i = 0; i < N; i++)
    a[i] = 1;
  sycl::queue q({sycl::property::queue::enable_profiling()});
  sycl::buffer<int> buf(&total, 1);
  sycl::buffer<int> bufa(a, N);
  auto e = q.submit([&](sycl::handler &h) {
    sycl::accessor acc(buf, h);
    sycl::accessor acc_a(bufa, h, sycl::read_only);
    h.parallel_for(sycl::nd_range<1>(N, M), [=](auto it) {
      auto i = it.get_global_id();
      sycl::atomic_ref<int, sycl::memory_order_relaxed,
                       sycl::memory_scope_device,
                       sycl::access::address_space::global_space>
          atomic_op(acc[0]);
      atomic_op += acc_a[i];
    });
  });
  sycl::host_accessor h_a(buf);
  std::cout << "Reduction Sum : " << h_a[0] << "\n";
  std::cout
      << "Kernel Execution Time of Global Atomics Ref: "
      << e.get_profiling_info<sycl::info::event_profiling::command_end>() -
             e.get_profiling_info<sycl::info::event_profiling::command_start>()
      << "\n";
  return 0;
}

您可以重构代码以使用本地内存空间,如下面的示例所示。

#include <CL/sycl.hpp>
#include <iostream>
int main() {
  constexpr int N = 256 * 256;
  constexpr int M = 512;
  constexpr int NUM_WG = N / M;
  int total = 0;
  int *a = static_cast<int *>(malloc(sizeof(int) * N));
  for (int i = 0; i < N; i++)
    a[i] = 1;
  sycl::queue q({sycl::property::queue::enable_profiling()});
  sycl::buffer<int> global(&total, 1);
  sycl::buffer<int> bufa(a, N);
  auto e1 = q.submit([&](sycl::handler &h) {
    sycl::accessor b(global, h);
    sycl::accessor acc_a(bufa, h, sycl::read_only);
    auto acc = sycl::local_accessor<int, 1>(NUM_WG, h);
    h.parallel_for(sycl::nd_range<1>(N, M), [=](auto it) {
      auto i = it.get_global_id(0);
      auto group_id = it.get_group(0);
      sycl::atomic_ref<int, sycl::memory_order_relaxed,
                       sycl::memory_scope_device,
                       sycl::access::address_space::local_space>
          atomic_op(acc[group_id]);
      sycl::atomic_ref<int, sycl::memory_order_relaxed,
                       sycl::memory_scope_device,
                       sycl::access::address_space::global_space>
          atomic_op_global(b[0]);
      atomic_op += acc_a[i];
      it.barrier(sycl::access::fence_space::local_space);
      if (it.get_local_id() == 0)
        atomic_op_global += acc[group_id];
    });
  });
  sycl::host_accessor h_global(global);
  std::cout << "Reduction Sum : " << h_global[0] << "\n";
  int total_time =
      (e1.get_profiling_info<sycl::info::event_profiling::command_end>() -
       e1.get_profiling_info<sycl::info::event_profiling::command_start>());
  std::cout << "Kernel Execution Time of Local Atomics : " << total_time
            << "\n";
  return 0;
}

基于 USM 数据的原子操作

在独立 GPU 上,

  • 不支持在主机端分配的 USM (sycl::malloc_host)上进行原子操作。

  • 不支持从主机端和设备端对共享 USM 位置(sycl::malloc_shared) 进行并行访问。

我们建议使用设备端分配的 USM(sycl::malloc_device)内存进行原子操作和使用具有原子操作的设备端算法。

上一章                                         主目录    上级目录                                                               下一章

<think>好的,我现在需要帮助用户解答关于在Linux实时内核环境下进行PCIE驱动开发的问题。首先,我需要理解用户的具体需求。用户可能是一名开发者,正在尝试在实时操作系统(RTOS)环境下开发或优化PCIE设备的驱动程序。实时内核通常用于需要严格时间约束的应用,比如工业自动化、机器人或汽车系统,因此驱动程序的实时性能、低延迟和可靠性是关键。 接下来,我应该考虑Linux实时内核与标准内核的主要区别。实时内核如PREEMPT_RT补丁的主要特点是更快的上下文切换、更可预测的中断响应时间,以及更细粒度的抢占机制。这对驱动开发意味着需要特别注意中断处理、锁的使用和定时器管理,以避免引入不可预测的延迟。 然后,关于PCIE驱动开发的基础部分,用户可能需要了解如何编写基本的PCIE驱动程序框架,包括设备探测、资源分配(如内存区域和中断请求)、DMA操作等。在实时环境中,这些操作可能需要特别处理,比如使用实时安全的锁机制或避免长时间的非抢占区。 同时,用户提到的引用内容可能与传感器融合或定时器回调有关,这可能暗示用户的应用场景涉及多传感器数据采集或实时数据处理。因此,驱动开发中可能需要处理高频率的数据传输,或者与硬件定时器紧密集成,确保数据处理的及时性。 接下来,我需要整理开发步骤。首先,搭建实时内核环境,包括获取和编译PREEMPT_RT补丁,这可能涉及内核配置选项的调整。然后,理解PCIE子系统的工作原理,包括设备树(Device Tree)的配置,特别是在嵌入式系统中常见的使用情况。此外,实时驱动开发的关键点如中断处理例程(ISR)的设计,应尽量简短,将耗时任务推迟到线程化处理,使用实时互斥锁等。 调试和优化部分同样重要,特别是在实时系统中,使用ftrace或RTLA工具来分析中断延迟和调度行为。此外,确保驱动代码符合实时内核的要求,比如避免内存分配在原子上下文中,使用适当的内存预分配策略。 用户提供的引用中有关于AMD GPU执行多个内核的内容,这可能与PCIE设备的并发处理有关,提示在驱动开发中需要考虑多核处理或并行任务处理。另一个引用涉及定位模块的定时器回调和多传感器融合,这可能意味着用户需要处理多个PCIE设备的数据同步,或者高精度定时器的使用,这对实时性要求更高。 最后,生成相关问题时要覆盖用户可能进一步感兴趣的方面,如实时内核的配置细节、中断处理优化、调试工具的使用、与标准驱动的兼容性,以及实际应用案例。这些问题可以帮助用户深入理解各个方面的挑战和解决方案。</think>### Linux实时内核环境下PCIE驱动开发指南 #### 一、开发环境搭建 1. **实时内核选择** 建议使用主线内核集成PREEMPT_RT补丁的版本(如Linux 5.15+),通过`CONFIG_PREEMPT_RT`配置选项启用实时抢占特性[^1]。 编译命令示例: ```bash make menuconfig # 选择General Setup -> Preemption Model -> Fully Preemptible Kernel (RT) make -j$(nproc) bindeb-pkg ``` 2. **硬件准备** PCIE设备需支持MSI/MSI-X中断,建议使用FPGA开发板(如Xilinx Zynq)或专用测试卡验证驱动功能。 #### 二、PCIE驱动核心开发步骤 1. **驱动框架初始化** 使用`pci_driver`结构体注册设备,关键函数包括`probe()`和`remove()`: ```c static struct pci_driver my_pcie_driver = { .name = "my_pcie", .id_table = my_pci_ids, .probe = my_pcie_probe, .remove = my_pcie_remove }; ``` 2. **实时中断处理** 采用线程化中断模式提升实时性: ```c // 注册中断时指定IRQF_ONESHOT和IRQF_NO_THREAD标志 ret = request_threaded_irq(pci_irq_vector(pdev, 0), my_isr, my_threaded_handler, IRQF_ONESHOT, "my_pcie", dev); ``` 3. **DMA与内存管理** 使用`dma_alloc_coherent()`分配一致性内存,并通过`dmaengine`API实现零拷贝传输: ```c dma_addr_t dma_handle; void *cpu_addr = dma_alloc_coherent(&pdev->dev, BUF_SIZE, &dma_handle, GFP_KERNEL); ``` #### 三、实时性优化关键技术 1. **优先级继承互斥锁** 使用`rt_mutex`替代普通互斥锁,防止优先级反转: ```c #include <linux/rtmutex.h> struct rt_mutex my_lock; rt_mutex_init(&my_lock); ``` 2. **时间敏感型IO处理** 通过`hrtimers`(高分辨率定时器)实现精确时序控制: ```c static enum hrtimer_restart timer_callback(struct hrtimer *timer) { // 执行周期任务 hrtimer_forward_now(timer, ns_to_ktime(1000000)); // 1ms周期 return HRTIMER_RESTART; } ``` 3. **NUMA感知设计** 在多核系统中采用`numa_node_id()`进行本地内存分配: ```c page = alloc_pages_node(dev_to_node(&pdev->dev), GFP_KERNEL | __GFP_ZERO, order); ``` #### 四、调试与测试方法 1. **实时性分析工具** 使用`cyclictest`测量延迟: ```bash cyclictest -m -p95 -n -h 1000 -l 100000 ``` 典型实时内核延迟应小于50μs。 2. **PCIE链路验证** 通过`lspci -vvv`查看链路状态,确认速度达到PCIe Gen3 x8(约7.877 GB/s)。 3. **故障注入测试** 使用`echo 1 > /sys/bus/pci/devices/xxxx:xx:xx.x/remove`模拟设备热插拔事件。 #### 五、应用场景示例 在自动驾驶系统中,PCIE驱动需保证激光雷达数据的实时传输(如Velodyne VLP-16每秒产生2,200,000个点云数据),此时驱动需满足: - 中断响应延迟 < 10μs - DMA传输带宽 > 5GB/s - 错误恢复时间 < 1ms ```mermaid graph TD A[PCIE设备枚举] --> B[资源配置] B --> C{实时性要求?} C -->|是| D[使用RT Mutex/Spinlock] C -->|否| E[标准同步机制] D --> F[线程化中断处理] E --> F F --> G[DMA引擎初始化] G --> H[用户空间接口] ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值