公众号
点击「查看原文」跳转到 GitHub 上对应文件,链接就可以点击了
qq群 点击进入 满了加这个 729240657
欢迎投稿,推荐或自荐文章/软件/资源等,评论区留言
本期文章没有赞助姥爷
标准委员会动态/ide/编译器信息放在这里
编译器信息最新动态推荐关注hellogcc公众号 本周更新 2025-01-08 第288期
使用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
debug版本由于sanitzer特别慢,他把指令换成内置sha1指令,加速100倍
还是SOAAOS那套,改内存布局
介绍几个封装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。不过感觉花里胡哨用处不大
作者尝试通过加法来替换浮点数乘法 误差7%
原理就是浮点数构成,浮点数由符号位、指数和尾数组成。当两个浮点数相乘时,其指数相加,尾数相乘。通过将浮点数的二进制表示转换为整数进行加减运算,可以近似模拟这一过程
数学推理如下
浮点数 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)
用一阶泰勒展开近似尾数对数: 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 ;
}
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());
}
这么玩风险有点高
这篇文章讲的还是挺细节的
通过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:违反严格别名规则
}
他这个场景是解释器执行OP code
第一版写法就是switch,perf发现分支预测失败率太高 10%
尝试重写改成指针数组跳转,引入内存访问函数指针开销压力,寄存器压力上升,性能优点提升但不多
然后改成computed goto,所有小代码都在一块,通过goto过去,性能显著提升
然后改成强制tail call 性能有提升,需要额外分离冷代码
他的场景就是opcode执行,代码段都很短,所以优化有收益。不过不是通用常识型
两种思路,一种是批量拼成uint32然后转float算完再阶段
另一种长除法
两种使用风格
热启动(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 {}; }
协程传播异常标准不齐全,编译器实现千奇百怪,业务只好自己搞
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
看一乐,了解一下这个设计布局,目前最快hashmap
利用矩阵转置加速
// 核心置换函数
__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);
}
很巧妙的降维计算思路