You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何优化C++中基于PEXT的国际象棋车移动查表性能?

国际象棋AI中PEXT查表优化问题

我正在开发一款国际象棋AI,采用位棋盘(bit-boards)表示棋盘与棋子。查阅资料得知使用PEXT指令可加速滑动棋子的移动查表,但自行实现后,该方案反而比原逻辑运算实现慢约25%。

PEXT实现的车移动攻击范围代码

uint64_t Move_lookup::get_rook_moves(int index, uint64_t all_pieces) {
    int index2 = _pext_u64(all_pieces, rook_masks[index]);
    return rook_moves[index][index2];
}

查表表的定义:

std::vector<std::vector<uint64_t>> rook_moves;

原逻辑运算实现(未使用)

uint64_t Board::get_rook_moves(uint64_t* b, pieceColour colour, int n) {
    // We must perform the operation on the set the function was called with
    uint64_t rooks = *b;
    isolate_piece(&rooks, n); // This gets rid of all the 1's we are not interested in in rooks
    
    // Get all enemy pieces
    uint64_t enemy_pieces = all_pieces(invert_colour(colour));
    
    // Combined pieces for obstacle detection
    uint64_t black_white_pieces = all_pieces(colour) | enemy_pieces;
    
    uint64_t attacks = 0ULL;
    
    // We need to look in all four straight directions (N,E,S,W) until we hit the edge of the board or another piece
    // North
    uint64_t move = rooks;
    while (move & NOT_RANK_8) {  // Continue while not on the last rank
        move <<= 8;  // Move one rank up
        if (move & black_white_pieces) {  // Stop if a piece is encountered
            if (move & enemy_pieces) attacks |= move;  // If enemy piece, include in attacks
            break;
        }
        attacks |= move;  // Add square to attacks
    }
    
    // East
    move = rooks;
    while (move & NOT_H_FILE) {  // Continue while not on the H file
        move >>= 1;  // Move one file right
        if (move & black_white_pieces) {  // Stop if a piece is encountered
            if (move & enemy_pieces) attacks |= move;  // If enemy piece, include in attacks
            break;
        }
        attacks |= move;  // Add square to attacks
    }
    
    // South
    move = rooks;
    while (move & NOT_RANK_1) {  // Continue while not on the first rank
        move >>= 8;  // Move one rank down
        if (move & black_white_pieces) {  // Stop if a piece is encountered
            if (move & enemy_pieces) attacks |= move;  // If enemy piece, include in attacks
            break;
        }
        attacks |= move;  // Add square to attacks
    }
    
    // West
    move = rooks;
    while (move & NOT_A_FILE) {  // Continue while not on the A file
        move <<= 1;  // Move one file left
        if (move & black_white_pieces) {  // Stop if a piece is encountered
            if (move & enemy_pieces) attacks |= move;  // If enemy piece, include in attacks
            break;
        }
        attacks |= move;  // Add square to attacks
    }
    
    return attacks;
}

填充车攻击范围查表的代码(仅启动时执行一次)

void Move_lookup::fill_rook_moves() {
    uint64_t i = 1ULL;
    for (auto& mask : rook_masks) {

        int count = count_set(mask);
        std::vector<uint64_t> perms = bin_n_perms_asc(count); // Get all permutations of the bits
        std::vector<uint64_t> temp;

        for (const auto& perm : perms) {
            uint64_t blockers = _pdep_u64(perm, mask); // Put the permutations on to the blockers
            uint64_t attacks = 0ULL;

            // We need to look in all four straight directions (N,E,S,W) until we hit the edge of the board or a blocker
            // North
            uint64_t move = i;
            while (move & NOT_RANK_8) {  // Continue while not on the last rank
                move <<= 8;  // Move one rank up
                attacks |= move;  // Add square to attacks
                if (move & blockers) break;
            }

            // East
            move = i;
            while (move & NOT_A_FILE) {  // Continue while not on the A file
                move >>= 1;  // Move one file right
                attacks |= move;  // Add square to attacks
                if (move & blockers) break;
            }

            // South
            move = i;
            while (move & NOT_RANK_1) {  // Continue while not on the first rank
                move >>= 8;  // Move one rank down
                attacks |= move;  // Add square to attacks
                if (move & blockers) break;
            }

            // West
            move = i;
            while (move & NOT_H_FILE) {  // Continue while not on the H file
                move <<= 1;  // Move one file left
                attacks |= move;  // Add square to attacks
                if (move & blockers) break;
            }

            temp.push_back(attacks);
        }   

        rook_moves.push_back(temp);
        i <<= 1;
    }
}

我猜测查表速度慢可能是因为表的规模过大(包含超10万个元素),但所有未做尺寸优化的PEXT查表实现都需要这样的表规模。请问有什么方法可以提升我的查表速度?


优化建议

  • 优化内存布局,提升缓存命中率
    二维vector的内层容器内存不连续,会导致CPU缓存命中率低。可以将查表数据改为一维连续数组,通过index * 单位置最大排列数 + index2计算索引,让数据更好地利用CPU缓存行;也可以用std::array替代内层vector(若每个位置的排列数可提前确定),编译期固定内存布局,减少运行时内存访问开销。

  • 确保PEXT指令的硬件支持与编译优化
    确认CPU支持BMI2指令集(PEXT属于BMI2),编译时开启对应优化选项:GCC用-mbmi2,MSVC用/arch:AVX2(包含BMI2)。若未开启,编译器会用软件模拟PEXT,速度大幅下降。可通过__builtin_cpu_supports("bmi2")(GCC)或CPUID检测(MSVC)判断硬件支持,运行时选择PEXT查表或原逻辑实现的分支。

  • 减少索引计算开销
    统一使用size_t作为索引类型,避免int与容器索引类型的转换开销;提前计算每个位置掩码对应的排列数并缓存,减少运行时额外计算。

  • 预计算并静态初始化查表
    用脚本预计算所有攻击范围数据,生成头文件直接编译进程序。这样既避免启动时初始化耗时,还能让数据放在只读数据段,提升缓存效率。

  • 优化掩码设计
    检查rook_masks是否为最小必要掩码——标准滑动棋子掩码只包含棋子到棋盘边缘的关键位置,不包含棋子本身所在格子。若掩码包含无关位,会增加PEXT计算量和查表规模。

内容的提问来源于stack exchange,提问作者Michael

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.16 14:37:35