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

如何高效实现__int128与GMP库mpz_class/mpz_t的加法?

问题

在g++环境下,需要实现128位无符号整数unsigned __int128(别名tw_limb_t)与GMP库mpz_class的加法操作:

typedef unsigned __int128 tw_limb_t;

mpz_class a = "1234567890123456789012345678901234567890";
tw_limb_t b = ...;
a += b;

当前实现通过先将__int128转换为mpz_class再执行加法,但性能表现不佳:

typedef unsigned __int128 tw_limb_t;

inline mpz_class& operator+=(mpz_class &lhs, const tw_limb_t &rhs) {
    if (sizeof(tw_limb_t) != 2 * sizeof(mp_limb_t)) {
        throw std::runtime_error("sizeof(tw_limb_t) is not twice sizeof(mp_limb_t)");
    }

    const int LIMB_BITLEN = sizeof(mp_limb_t) * 8;

    mpz_class rhs_mpz = (mp_limb_t) (rhs >> LIMB_BITLEN);
    rhs_mpz <<= LIMB_BITLEN;
    rhs_mpz += (mp_limb_t) rhs;

    lhs += rhs_mpz;

    return lhs;
}

如何提升该操作的性能?


优化方案

1. 直接操作GMP底层limb数组,避免临时对象开销

GMP的mpz_class底层基于mpz_t结构,其中包含指向limb数组的指针。我们可以直接将__int128拆分为两个mp_limb_t,调用GMP的底层多精度加法函数(如mpn_add_1)完成操作,完全避免创建临时mpz_class或mpz_t对象,这是性能提升最显著的方式。

示例代码:

#include <gmp.h>
#include <stdexcept>

typedef unsigned __int128 tw_limb_t;

inline __attribute__((always_inline)) mpz_class& operator+=(mpz_class& lhs, const tw_limb_t& rhs) {
    // 编译期检查大小匹配,避免运行时开销
    static_assert(sizeof(tw_limb_t) == 2 * sizeof(mp_limb_t), 
                  "tw_limb_t must be exactly two mp_limb_t in size");

    mpz_t& lhs_mpz = lhs.get_mpz_t();
    const mp_limb_t rhs_low = static_cast<mp_limb_t>(rhs);
    const mp_limb_t rhs_high = static_cast<mp_limb_t>(rhs >> (sizeof(mp_limb_t) * 8));

    mp_size_t lhs_size = mpz_size(lhs_mpz);
    // 确保limb数组有足够空间存储结果
    mp_limb_t* lhs_limbs = mpz_limbs_write(lhs_mpz, std::max(lhs_size, static_cast<mp_size_t>(2)));

    // 先加低limb,获取进位
    mp_limb_t carry = mpn_add_1(lhs_limbs, lhs_limbs, lhs_size, rhs_low);

    // 处理高limb及进位
    if (carry != 0 || lhs_size < 2) {
        if (lhs_size >= 2) {
            carry = mpn_add_1(lhs_limbs + 1, lhs_limbs + 1, lhs_size - 1, rhs_high + carry);
        } else {
            lhs_limbs[1] = rhs_high + carry;
            carry = 0;
        }
    } else {
        carry = mpn_add_1(lhs_limbs + 1, lhs_limbs + 1, lhs_size - 1, rhs_high);
    }

    // 处理最终进位,更新mpz_t的大小
    if (carry != 0) {
        lhs_limbs[lhs_size] = carry;
        mpz_limbs_finish(lhs_mpz, lhs_size + 1);
    } else {
        mpz_limbs_finish(lhs_mpz, std::max(lhs_size, static_cast<mp_size_t>(2)));
    }

    return lhs;
}

2. 用GMP单limb加法函数简化操作(次优但更易维护)

如果不想直接操作底层limb数组,可以利用GMP的mpz_add_ui(单limb加法)和位运算,减少临时对象的创建开销。这种方式比原代码高效,同时代码更简洁:

typedef unsigned __int128 tw_limb_t;

inline __attribute__((always_inline)) mpz_class& operator+=(mpz_class& lhs, const tw_limb_t& rhs) {
    static_assert(sizeof(tw_limb_t) == 2 * sizeof(mp_limb_t), 
                  "tw_limb_t must be two mp_limb_t in size");

    const int LIMB_BITS = sizeof(mp_limb_t) * 8;
    mp_limb_t low = static_cast<mp_limb_t>(rhs);
    mp_limb_t high = static_cast<mp_limb_t>(rhs >> LIMB_BITS);

    // 先加低limb
    mpz_add_ui(lhs.get_mpz_t(), lhs.get_mpz_t(), low);
    // 将高limb左移对应位数后加到结果中
    mpz_t high_mpz;
    mpz_init_set_ui(high_mpz, high);
    mpz_mul_2exp(high_mpz, high_mpz, LIMB_BITS);
    mpz_add(lhs.get_mpz_t(), lhs.get_mpz_t(), high_mpz);
    mpz_clear(high_mpz);

    return lhs;
}

3. 编译期替代运行时检查

原代码中用运行时if检查sizeof匹配,改用static_assert可以在编译期就验证平台兼容性,避免运行时的分支和异常抛出开销,因为目标平台的limb大小是固定的,编译期即可确定。

4. 强制内联减少函数调用开销

在g++中,除了inline关键字,添加__attribute__((always_inline))可以强制编译器内联该函数,消除函数调用的栈帧开销,进一步提升性能。


内容的提问来源于stack exchange,提问作者Daniel S.

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.21 12:33:17