C++如何将A*寻路算法移植到CUDA上实现GPU加速

来源:苹果APP网作者:葵司头衔:网络博主
导读:本期聚焦于小伙伴创作的《C++如何将A*寻路算法移植到CUDA上实现GPU加速》,敬请观看详情,探索知识的价值。以下视频、文章将为您系统阐述其核心内容与价值。如果您觉得《C++如何将A*寻路算法移植到CUDA上实现GPU加速》有用,将其分享出去将是对创作者最好的鼓励。

A*寻路算法的核心是通过开放列表维护待探索节点,不断选取代价最小的节点扩展,直到找到目标节点。传统C++串行实现中,开放列表的节点选取、扩展操作都是顺序执行,当地图规模增大、节点数量增多时,性能会明显下降。CUDA作为NVIDIA推出的并行计算平台,能够同时调度成千上万个线程并行处理任务,非常适合优化这类存在大量可并行计算逻辑的算法。

C++如何将A*寻路算法移植到CUDA上实现GPU加速

串行A*算法的核心逻辑

在移植之前,先回顾下标准C++串行A*算法的核心实现,方便后续对比并行适配的改动点。下面的代码是简化版的串行A*实现,仅保留核心逻辑:

#include <vector>
#include <queue>
#include <unordered_map>
#include <cmath>

// 节点结构体,记录坐标、代价、父节点信息
struct Node {
    int x;
    int y;
    float g; // 起点到当前节点的实际代价
    float h; // 当前节点到目标节点的预估代价
    float f; // 总代价 g + h
    Node* parent;

    Node(int _x, int _y) : x(_x), y(_y), g(0), h(0), f(0), parent(nullptr) {}

    // 重载小于运算符,用于优先队列排序
    bool operator<(const Node& other) const {
        return f > other.f; // 小顶堆,f值小的优先
    }
};

// 计算曼哈顿距离作为启发函数
float heuristic(Node* a, Node* b) {
    return std::abs(a->x - b->x) + std::abs(a->y - b->y);
}

// 串行A*寻路实现
std::vector<Node*> astar_serial(Node* start, Node* end, const std::vector<std::vector<int>>& map) {
    std::priority_queue<Node> open_list;
    std::unordered_map<int, Node*> open_map; // 快速查找开放列表中的节点
    std::unordered_map<int, Node*> closed_map; // 已探索节点

    start->h = heuristic(start, end);
    start->f = start->g + start->h;
    open_list.push(*start);
    open_map[start->x * map[0].size() + start->y] = start;

    // 四个方向的移动偏移
    int dx[4] = {1, -1, 0, 0};
    int dy[4] = {0, 0, 1, -1};

    while (!open_list.empty()) {
        Node current = open_list.top();
        open_list.pop();
        Node* current_ptr = open_map[current.x * map[0].size() + current.y];

        // 到达目标节点,回溯路径
        if (current.x == end->x && current.y == end->y) {
            std::vector<Node*> path;
            Node* node = current_ptr;
            while (node != nullptr) {
                path.push_back(node);
                node = node->parent;
            }
            std::reverse(path.begin(), path.end());
            return path;
        }

        // 将当前节点移入关闭列表
        closed_map[current.x * map[0].size() + current.y] = current_ptr;
        open_map.erase(current.x * map[0].size() + current.y);

        // 扩展相邻节点
        for (int i = 0; i < 4; i++) {
            int nx = current.x + dx[i];
            int ny = current.y + dy[i];
            // 检查边界和障碍物
            if (nx < 0 || nx >= map.size() || ny < 0 || ny >= map[0].size() || map[nx][ny] == 1) {
                continue;
            }
            int key = nx * map[0].size() + ny;
            // 如果节点已在关闭列表,跳过
            if (closed_map.find(key) != closed_map.end()) {
                continue;
            }
            float new_g = current.g + 1.0f;
            // 如果节点不在开放列表,或者新的g值更小,更新节点
            if (open_map.find(key) == open_map.end() || new_g < open_map[key]->g) {
                Node* neighbor = open_map.find(key) == open_map.end() ? new Node(nx, ny) : open_map[key];
                neighbor->g = new_g;
                neighbor->h = heuristic(neighbor, end);
                neighbor->f = neighbor->g + neighbor->h;
                neighbor->parent = current_ptr;
                if (open_map.find(key) == open_map.end()) {
                    open_list.push(*neighbor);
                    open_map[key] = neighbor;
                }
            }
        }
    }
    return {}; // 未找到路径
}

A*算法的并行性分析

要将A*移植到CUDA,首先需要分析算法中哪些部分可以并行执行。串行A*的性能瓶颈主要在两个地方:一是每次从开放列表选取最小f值节点的操作,二是扩展当前节点时对每个相邻节点的检查逻辑。其中,相邻节点的检查是完全独立的,没有数据依赖,非常适合用GPU多线程并行处理。而开放列表的维护由于需要全局排序和去重,并行实现复杂度较高,我们可以先采用折中方案:CPU维护开放列表,GPU并行处理节点扩展的计算任务,这也是很多轻量级移植方案的常用思路。

CUDA移植的核心步骤

1. 数据结构的适配

CUDA中无法直接高效使用C++的STL容器,因此需要将节点数据、地图数据转换为C风格数组或者CUDA支持的结构。首先定义设备端可用的节点结构体,和主机端结构体保持一致,去掉不必要的运算符重载:

// 设备端节点结构体
struct DeviceNode {
    int x;
    int y;
    float g;
    float h;
    float f;
    int parent_idx; // 用索引代替指针,避免设备端指针问题
    bool valid; // 标记节点是否有效
};

// 设备端地图和辅助数据
struct DeviceMapData {
    int* map; // 地图数据,0可走1障碍
    int width;
    int height;
    int start_x;
    int start_y;
    int end_x;
    int end_y;
};

2. 节点扩展的CUDA内核函数

核心的并行逻辑是节点扩展:每个线程处理一个相邻方向的检查,计算新节点的代价,判断是否符合加入开放列表的条件。下面的内核函数实现并行扩展单个节点的四个相邻方向:

__global__ void expand_node_kernel(DeviceNode* current_node, DeviceMapData map_data, DeviceNode* neighbors, float* min_f, int* min_idx) {
    int tid = threadIdx.x;
    if (tid >= 4) return;

    // 四个方向的偏移
    int dx[4] = {1, -1, 0, 0};
    int dy[4] = {0, 0, 1, -1};

    int nx = current_node->x + dx[tid];
    int ny = current_node->y + dy[tid];
    neighbors[tid].valid = false;

    // 边界检查
    if (nx < 0 || nx >= map_data.height || ny < 0 || ny >= map_data.width) {
        return;
    }
    // 障碍物检查
    if (map_data.map[nx * map_data.width + ny] == 1) {
        return;
    }

    // 计算新节点的代价
    neighbors[tid].x = nx;
    neighbors[tid].y = ny;
    neighbors[tid].g = current_node->g + 1.0f;
    // 曼哈顿距离启发函数
    neighbors[tid].h = fabsf(nx - map_data.end_x) + fabsf(ny - map_data.end_y);
    neighbors[tid].f = neighbors[tid].g + neighbors[tid].h;
    neighbors[tid].parent_idx = current_node->x * map_data.width + current_node->y;
    neighbors[tid].valid = true;

    // 原子操作更新最小f值(简化版,实际场景需要更复杂的同步)
    float old_min = atomicMinf(min_f, neighbors[tid].f);
    if (neighbors[tid].f < old_min) {
        atomicExch(min_idx, tid);
    }
}

3. 主机端调度逻辑

主机端负责维护开放列表、调用CUDA内核、处理数据在主机和设备之间的拷贝。核心流程如下:

#include <cuda_runtime.h>
#include <vector>
#include <queue>
#include <unordered_map>

// 原子操作辅助函数,用于浮点数最小值更新
__device__ float atomicMinf(float* addr, float value) {
    float old = *addr;
    while (value < old) {
        old = atomicCAS((unsigned int*)addr, __float_as_uint(old), __float_as_uint(value));
    }
    return old;
}

// CUDA版本A*寻路主函数
std::vector<Node*> astar_cuda(Node* start, Node* end, const std::vector<std::vector<int>>& map) {
    int width = map[0].size();
    int height = map.size();
    int node_num = width * height;

    // 1. 准备主机端数据
    std::vector<int> host_map(node_num);
    for (int i = 0; i < height; i++) {
        for (int j = 0; j < width; j++) {
            host_map[i * width + j] = map[i][j];
        }
    }

    // 2. 分配设备内存
    int* dev_map;
    cudaMalloc(&dev_map, node_num * sizeof(int));
    cudaMemcpy(dev_map, host_map.data(), node_num * sizeof(int), cudaMemcpyHostToDevice);

    DeviceMapData host_map_data;
    host_map_data.map = dev_map;
    host_map_data.width = width;
    host_map_data.height = height;
    host_map_data.start_x = start->x;
    host_map_data.start_y = start->y;
    host_map_data.end_x = end->x;
    host_map_data.end_y = end->y;

    DeviceMapData* dev_map_data;
    cudaMalloc(&dev_map_data, sizeof(DeviceMapData));
    cudaMemcpy(dev_map_data, &host_map_data, sizeof(DeviceMapData), cudaMemcpyHostToDevice);

    // 设备端节点数组,存储所有生成的节点
    DeviceNode* dev_nodes;
    cudaMalloc(&dev_nodes, node_num * sizeof(DeviceNode));
    cudaMemset(dev_nodes, 0, node_num * sizeof(DeviceNode));

    // 初始化起点
    DeviceNode start_node;
    start_node.x = start->x;
    start_node.y = start->y;
    start_node.g = 0;
    start_node.h = fabsf(start->x - end->x) + fabsf(start->y - end->y);
    start_node.f = start_node.g + start_node.h;
    start_node.parent_idx = -1;
    start_node.valid = true;
    cudaMemcpy(dev_nodes + (start->x * width + start->y), &start_node, sizeof(DeviceNode), cudaMemcpyHostToDevice);

    // 主机端开放列表和关闭列表
    std::priority_queue<Node> open_list;
    std::unordered_map<int, Node*> open_map;
    std::unordered_map<int, Node*> closed_map;
    start->h = start_node.h;
    start->f = start_node.f;
    open_list.push(*start);
    open_map[start->x * width + start->y] = start;

    // 临时存储扩展结果的设备内存
    DeviceNode* dev_neighbors;
    cudaMalloc(&dev_neighbors, 4 * sizeof(DeviceNode));
    float* dev_min_f;
    cudaMalloc(&dev_min_f, sizeof(float));
    int* dev_min_idx;
    cudaMalloc(&dev_min_idx, sizeof(int));

    // 寻路主循环
    while (!open_list.empty()) {
        Node current = open_list.top();
        open_list.pop();
        Node* current_ptr = open_map[current.x * width + current.y];
        int current_key = current.x * width + current.y;

        if (current.x == end->x && current.y == end->y) {
            // 找到目标,回溯路径
            std::vector<Node*> path;
            Node* node = current_ptr;
            while (node != nullptr) {
                path.push_back(node);
                node = node->parent;
            }
            std::reverse(path.begin(), path.end());
            // 释放设备内存
            cudaFree(dev_map);
            cudaFree(dev_map_data);
            cudaFree(dev_nodes);
            cudaFree(dev_neighbors);
            cudaFree(dev_min_f);
            cudaFree(dev_min_idx);
            return path;
        }

        closed_map[current_key] = current_ptr;
        open_map.erase(current_key);

        // 准备当前节点的设备端数据
        DeviceNode dev_current;
        dev_current.x = current.x;
        dev_current.y = current.y;
        dev_current.g = current.g;
        dev_current.h = current.h;
        dev_current.f = current.f;
        dev_current.parent_idx = current.parent ? current.parent->x * width + current.parent->y : -1;
        dev_current.valid = true;

        // 初始化最小f值为最大值
        float init_min_f = INFINITY;
        int init_min_idx = -1;
        cudaMemcpy(dev_min_f, &init_min_f, sizeof(float), cudaMemcpyHostToDevice);
        cudaMemcpy(dev_min_idx, &init_min_idx, sizeof(int), cudaMemcpyHostToDevice);

        // 调用CUDA内核并行扩展节点
        expand_node_kernel<<<1, 4>>>(dev_current, *dev_map_data, dev_neighbors, dev_min_f, dev_min_idx);
        cudaDeviceSynchronize();

        // 将扩展结果拷贝回主机
        DeviceNode host_neighbors[4];
        cudaMemcpy(host_neighbors, dev_neighbors, 4 * sizeof(DeviceNode), cudaMemcpyDeviceToHost);

        // 处理扩展得到的相邻节点,更新开放列表
        for (int i = 0; i < 4; i++) {
            if (!host_neighbors[i].valid) continue;
            int key = host_neighbors[i].x * width + host_neighbors[i].y;
            if (closed_map.find(key) != closed_map.end()) continue;

            float new_g = host_neighbors[i].g;
            if (open_map.find(key) == open_map.end() || new_g < open_map[key]->g) {
                Node* neighbor = open_map.find(key) == open_map.end() ? new Node(host_neighbors[i].x, host_neighbors[i].y) : open_map[key];
                neighbor->g = new_g;
                neighbor->h = host_neighbors[i].h;
                neighbor->f = host_neighbors[i].f;
                neighbor->parent = current_ptr;
                if (open_map.find(key) == open_map.end()) {
                    open_list.push(*neighbor);
                    open_map[key] = neighbor;
                }
                // 更新设备端节点数组
                DeviceNode dev_neighbor;
                dev_neighbor.x = neighbor->x;
                dev_neighbor.y = neighbor->y;
                dev_neighbor.g = neighbor->g;
                dev_neighbor.h = neighbor->h;

CUDA A*算法 GPU加速 C++并行计算修改时间:2026-07-20 03:04:06

免责声明:​ 已尽一切努力确保本网站所含信息的准确性。网站内容多为原创整理与精心编撰,观点力求客观中立。本站旨在免费分享,内容仅供个人学习、研究或参考使用。若引用了第三方作品,版权归原作者所有。如内容涉及您的权益,请联系我们处理。
内容垂直聚焦
专注技术核心技术栏目,确保每篇文章深度聚焦于实用技能。从代码技巧到架构设计,为用户提供无干扰的纯技术知识沉淀,精准满足专业提升需求。
知识结构清晰
覆盖从开发到部署的全链路。AI、前端、编程、数据库、服务器、建站、系统层层递进,构建清晰学习路径,帮助用户系统化掌握开发与运维所需的核心技术。
深度技术解析
拒绝泛泛而谈,深入技术细节与实践难点。无论是数据库优化还是服务器配置,均结合真实场景与代码示例进行剖析,致力于提供可直接应用于工作的解决方案。
专业领域覆盖
精准对应开发生命周期。从前端界面到后端编程,从数据库操作到服务器运维,形成完整闭环,一站式满足全栈工程师和运维人员的技术需求。
即学即用高效
内容强调实操性,步骤清晰、代码完整。用户可根据教程直接复现和应用于自身项目,显著缩短从学习到实践的距离,快速解决开发中的具体问题。
持续更新保障
专注既定技术方向进行长期、稳定的内容输出。确保各栏目技术文章持续更新迭代,紧跟主流技术发展趋势,为用户提供经久不衰的学习价值。