C++ 中文周刊 2025-03-02 第180期

周刊项目地址

公众号

点击「查看原文」跳转到 GitHub 上对应文件,链接就可以点击了

qq群 点击进入 满了加这个 729240657

RSS

欢迎投稿,推荐或自荐文章/软件/资源等,评论区留言

本期文章没有赞助姥爷


资讯

标准委员会动态/ide/编译器信息放在这里

编译器信息最新动态推荐关注hellogcc公众号 本周更新 2025-01-08 第288期

性能周刊

文章

Getting rid of unwanted branches with __builtin_unreachable()

使用builtin_unreadchable消除分支, 比如

uint8_t sum_with_constraints(const uint8_t *data, size_t len) {
    constexpr size_t U8_VALUES_PER_YMMWORD = 32;
    if (len % U8_VALUES_PER_YMMWORD != 0)
        __builtin_unreachable(); // `len` is always a multiple of 32.
    if (len == 0)
        __builtin_unreachable(); // `len` is never zero.

    return std::accumulate(data, data + len, uint8_t(0));
}
/*
sum_with_constraints():
        vpxor   xmm0, xmm0, xmm0
        xor     eax, eax
.LBB0_1:
        vpaddb  ymm0, ymm0, ymmword ptr [rdi + rax]
        add     rax, 32
        cmp     rsi, rax
        jne     .LBB0_1
        vextracti128    xmm1, ymm0, 1
        vpaddb  xmm0, xmm0, xmm1
        vpshufd xmm1, xmm0, 238
        vpaddb  xmm0, xmm0, xmm1
        vpxor   xmm1, xmm1, xmm1
        vpsadbw xmm0, xmm0, xmm1
        vmovd   eax, xmm0
        vzeroupper
        ret
*/

不过把 data*换成vector gcc编译器下并不能优化。优化bug

Making my debug build run 100x faster so that it is finally usable

debug版本由于sanitzer特别慢,他把指令换成内置sha1指令,加速100倍

CppNow 2024 Cache Friendly + Functional + Ranges

还是SOAAOS那套,改内存布局

7 Interesting (and Powerful) Uses for C++ Iterators

介绍几个封装iterator技巧,比如

#include <iostream>
#include <vector>
#include <iterator>
#include <algorithm>

template <typename Iter, typename Func>
class transform_iterator {
    Iter it;
    Func func;
public:
    using iterator_category = std::input_iterator_tag;
    using value_type = typename std::result_of<Func(typename std::iterator_traits<Iter>::value_type)>::type;
    using difference_type = std::ptrdiff_t;
    using pointer = value_type*;
    using reference = value_type;

    transform_iterator(Iter iter, Func f) : it(iter), func(f) {}

    transform_iterator& operator++() { ++it; return *this; }
    reference operator*() const { return func(*it); }
    bool operator!=(const transform_iterator& other) const { return it != other.it; }
};

int main() {
    std::vector<int> numbers = {1, 2, 3, 4, 5};

    auto square = [](int x) { return x * x; };
    auto begin = transform_iterator(numbers.begin(), square);
    auto end = transform_iterator(numbers.end(), square);

    std::copy(begin, end, std::ostream_iterator<int>(std::cout, " "));
}

这样能延迟计算,类似range。不过感觉花里胡哨用处不大

Why Does Integer Addition Approximate Float Multiplication?

作者尝试通过加法来替换浮点数乘法 误差7%

原理就是浮点数构成,浮点数由符号位、指数和尾数组成。当两个浮点数相乘时,其指数相加,尾数相乘。通过将浮点数的二进制表示转换为整数进行加减运算,可以近似模拟这一过程

数学推理如下

  1. ​浮点数对数性质

浮点数 a 可表示为: a=(1+Ma​)⋅2^(Ea​−127) 其中 Ma 是尾数(归一化到 [0, 1)),Ea 是指数值。 乘法 a * b 的对数为: log2​(a⋅b)=log2​(1+Ma)+log2​(1+Mb)+(Ea+Eb​−254)

  1. ​线性近似简化

用一阶泰勒展开近似尾数对数: log2​(1+x)≈x(x∈[0,1)) 因此,乘法近似为: log2​(a⋅b)≈Ma​+Mb​+Ea​+Eb​−254 将尾数和指数合并为整数操作: A=(Ea​+127)⋅2^23+Ma​⋅2^23 A+B−Bias≈浮点数乘法的整数表示

Bias的值,就是存粹搜,0x3f76d000误差最小,7%

代码大概长这样

float rough_float_multiply2(float a, float b) {
    constexpr uint32_t bias = 0x3f76d000;
    uint32_t a = bit_cast<uint32_t>(a), b = bit_cast<uint32_t>(b);
    return a&b ? bit_cast<float>(a + b - bias) : 0.0f ;
}

0+0 > 0: C++ thread-local storage performance

tls对象在有类/构造函数维护+fpic共享库需要额外调用__tls_get_addr 成为性能瓶颈

优化指南

#if defined(__linux__) && defined(__x86_64__)
#  define GLIBC_TLS_PTHREAD_KEY_OFFSET 0x310
#  define GLIBC_TLS_KEY_DATA_OFFSET    (GLIBC_TLS_PTHREAD_KEY_OFFSET + 8)
#else
#  error "Unsupported platform"
#endif

__attribute__((always_inline)) 
inline void* unsafe_tls_get_first_key() {
  void* tls_base;
  __asm__ __volatile__ ("mov %%fs:0x10, %0" : "=r"(tls_base));
  return *(void**)((char*)tls_base + GLIBC_TLS_KEY_DATA_OFFSET);
}

// 初始化时确保 key0 是第一个创建的 key
pthread_key_t key0;
__attribute__((constructor)) 
void init_first_key() {
  pthread_key_create(&key0, NULL);
  assert(pthread_getspecific(key0) == unsafe_tls_get_first_key());
}


这么玩风险有点高

这篇文章讲的还是挺细节的

Exploring Undefined Behavior Using Constexpr

通过constexpr捕捉UB,比如整型溢出/未初始化/reinterpretcast

constexpr int uninitialized() {
    int a; // 未初始化:UB!
    return a;
}
constexpr int type_punning() {
    float f = 3.14f;
    return reinterpret_cast<int&>(f); // UB:违反严格别名规则
}

c++now 2023 A Deep Dive into Dispatch Techniques

他这个场景是解释器执行OP code

第一版写法就是switch,perf发现分支预测失败率太高 10%

尝试重写改成指针数组跳转,引入内存访问函数指针开销压力,寄存器压力上升,性能优点提升但不多

然后改成computed goto,所有小代码都在一块,通过goto过去,性能显著提升

然后改成强制tail call 性能有提升,需要额外分离冷代码

他的场景就是opcode执行,代码段都很短,所以优化有收益。不过不是通用常识型

Dividing unsigned 8-bit numbers

代码在这里

两种思路,一种是批量拼成uint32然后转float算完再阶段

另一种长除法

C++ coroutines: Cold-start coroutines

两种使用风格

热启动(Hot-start)​​协程创建后立即执行,直到首次挂起(如await)才暂停,控制权返回调用者。 也叫eager coroutine

​冷启动(Cold-start)​​ 协程直到被await/resume才执行,也叫 lazy coroutine

c++非常灵活,可以控制这两种用法,我全都要

冷启动,初始化返回suspend always就行了

std::suspend_always initial_suspend() noexcept { return {}; }

热启动,控制suspend_never

std::suspend_never initial_suspend() noexcept { return {}; }

Throwing Exceptions From Coroutines

协程传播异常标准不齐全,编译器实现千奇百怪,业务只好自己搞

struct throwing_eager_coro_promise_type_helper {
protected:
    std::exception_ptr m_exception;
    bool m_has_been_suspended = false;
    bool m_has_exception_before_first_suspend = false;
public:
    void unhandled_exception() {
        if (m_has_been_suspended) {
            m_exception = std::current_exception();
        }
        else {
            m_has_exception_before_first_suspend = true;
            throw;
        }
    }

    void on_suspend() noexcept {
        m_has_been_suspended = true;
    }

    void rethrow_if_exception() {
        if (m_exception) {
            std::rethrow_exception(m_exception);
        }
    }

    template <typename PT>
    static void safe_destroy_handle(const std::coroutine_handle<PT>& h) noexcept {
        static_assert(std::is_base_of_v<throwing_eager_coro_promise_type_helper, PT>);
        if (h && !h.promise().m_has_exception_before_first_suspend) {
            h.destroy();
        }
    }
};

使用

struct workaround_wrapper {
    struct promise_type : public throwing_eager_coro_promise_type_helper {
        int last_yield = no_value;

        workaround_wrapper get_return_object() {
            return workaround_wrapper{std::coroutine_handle<promise_type>::from_promise(*this)};
        }

        std::suspend_never initial_suspend() noexcept { return {}; } // eager
        std::suspend_always final_suspend() noexcept { return {}; } // preserve the final yield

        std::suspend_always yield_value(int v) noexcept {
            last_yield = v;
            on_suspend(); // manually mark suspend point
            return {};
        }

        void return_void() noexcept {}
    };

    std::coroutine_handle<promise_type> handle = nullptr;

    explicit workaround_wrapper(std::coroutine_handle<promise_type> h = nullptr) noexcept : handle(h) {}
    ~workaround_wrapper() noexcept {
        // helper destroy
        throwing_eager_coro_promise_type_helper::safe_destroy_handle(handle);
    }

    int get() {
        if (!handle || handle.done()) return -1;
        auto ret = handle.promise().last_yield;
        handle.resume();
        handle.promise().rethrow_if_exception(); // helper
        return ret;
    }
};

保险起见,加一个

#if defined(__GNUC__)
#   if defined(__clang__)
#       if __clang_major__ > 19
#           error "Clang version > 19 is not tested"
#       endif
#   elif __GNUC__ > 14
#       error "GCC version > 14 is not tested"
#   endif
#endif

代码在这里

boost unordered_flat_map极简解析

看一乐,了解一下这个设计布局,目前最快hashmap

Bit-permuting 16 u32s at once with AVX-512

利用矩阵转置加速

// 核心置换函数
__m512i permbits_16x32_weirdindex(__m512i x, __m512i p) {
    // 预定义矩阵:0x8040201008040201 用于比特转置
    const __m512i mID = _mm512_set1_epi64(0x8040201008040201);
    
    // 阶段1:字节重排+比特转置
    x = _mm512_permutexvar_epi8(s1, x);  // 按s1模式重排字节
    x = _mm512_gf2p8affine_epi64_epi8(mID, x, 0); // 比特矩阵转置

    // 阶段2:应用位置换
    x = _mm512_permutexvar_epi8(p, x);  // 使用预处理索引p进行置换

    // 阶段3:逆比特转置+字节还原
    x = _mm512_gf2p8affine_epi64_epi8(mID, x, 0); // 逆比特转置
    return _mm512_permutexvar_epi8(s2, x); // 按s2模式还原字节
}

// 索引预处理函数
__m512i permbits_16x32(__m512i x, __m256i indices) {
    // 1. 扩展256位索引到512位
    const __m512i s1 = _mm512_set_epi8(/* 重复的低32字节模式 */);
    __m512i p = _mm512_permutexvar_epi8(s1, _mm512_castsi256_si512(indices));

    // 2. 高位索引偏移调整
    const uint64_t m = 0x2020202020202020; // 偏移量32
    p = _mm512_add_epi8(p, _mm512_set_epi64(m, m, m, m, 0, 0, 0, 0));

    // 3. 调用核心置换
    return permbits_16x32_weirdindex(x, p);
}

__m512i permbits_16x32(__m512i x, __m256i indices) {
    // 1. 扩展256位索引到512位
    const __m512i s1 = _mm512_set_epi8(/* 重复的低32字节模式 */);
    __m512i p = _mm512_permutexvar_epi8(s1, _mm512_castsi256_si512(indices));

    // 2. 高位索引偏移调整
    const uint64_t m = 0x2020202020202020; // 偏移量32
    p = _mm512_add_epi8(p, _mm512_set_epi64(m, m, m, m, 0, 0, 0, 0));

    // 3. 调用核心置换
    return permbits_16x32_weirdindex(x, p);
}

很巧妙的降维计算思路

开源项目介绍


上一期

本期

下一期

看到这里或许你有建议或者疑问或者指出错误,请留言评论! 多谢! 你的评论非常重要!也可以帮忙点赞收藏转发!多谢支持! 觉得写的不错那就给点吧, 在线乞讨 微信转账