Skip to content

Releases: wanghenshui/cppweeklynews

C++ 中文周刊 2025-05-19 第183期

Choose a tag to compare

@wanghenshui wanghenshui released this 18 May 16:33
75c7e9a

周刊项目地址

公众号

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

qq群 753792291 答疑在这里

RSS

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


资讯

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

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

性能周刊

文章

C++26: constexpr exceptions

优化异常constexpr化,不能constexpr的编译期报错,加固代码降低UB,同时降低异常开销,好事

C++20 concepts for nicer compiler errors

concept编译模版报错更易懂

An option(al) to surprise you

enum class State {
    Initial,
    WaitingForInput,
    GotValidInput,
    GotValidInputWithData,
    GotInvalidInput
};

std::optional<const char*> Worker(State s, const char* data) {
    switch(s) {
        using enum State;
        case Initial: return std::nullopt;
        case WaitingForInput: return {};
        case GotValidInput: [[fallthrough]];
        case GotValidInputWithData: return data;
        case GotInvalidInput: return "";
    }

    return std::nullopt;
}

幽默代码 这个例子里 optional有点类似optinal<T*> 存指针也就有可能存nullptr,导致误用报错。坏实践

如何算闰年

传统算法

bool is_leap_year(uint32_t y) {
    if ((y % 4) != 0) return false;
    if ((y % 100) != 0) return true;
    if ((y % 400) == 0) return true;
    return false;
}

显然,能继续优化

bool is_leap_year1(uint32_t y) {
    if ((y % 4) != 0) return false;
    if ((y % 25) != 0) return true;
    if ((y % 16) == 0) return true;
    return false;
}

我们考虑去掉mod

bool is_leap_year2(uint32_t y) {
    if ((y & 3) != 0) return false;
    if (y * 3264175145u > 171798691u) return true;
    if ((y & 15) == 0) return true;
    return false;
}

第二行有点令人费解 我们考虑通过定点缩放来去掉mod

我们需要找到一个分数来近似表示 1/25

在32位无符号整数中,我们可以用 m/2^32 来近似 1/25

通过计算得知 2^32 / 25 = 171798691.84
所以我们取 m = 171798692 作为近似值
因此,对于任意数x:

(x * 171798692) / 2^32 近似等于 x/25
如果x是25的倍数,则结果是一个整数
如果x不是25的倍数,则结果有小数部分
在32位计算中,乘法 x * 171798692 的高32位实际上就是 (x * 171798692) / 2^32

要检查x是否为25的倍数,我们需要判断这个结果是否为整数,但实际上我们要的是相反的结果(不是25的倍数)

通过将问题转化为检查 x * 3264175145u > 171798691u,我们实际上在检查:

3264175145是2^32 - 171798692 + 1的补码
这个比较操作效果上等同于检查x不是25的倍数
为什么用3264175145而不是171798692?

这里的关键是编译器优化中使用的技巧。不是直接计算 x * 171798692 / 2^32,而是通过计算 x * (2^32 - 1) / 25 + 1 来达到同样的效果。

具体计算:
(2^32 - 1) / 25 = 171798691.96
取近似值为3264175145
当我们用x乘以这个数时,对于25的倍数,结果恰好落在某个特定范围内,使得 x * 3264175145u > 171798691u 正好对应 x % 25 != 0

但是这种编译器也能优化,属于过度优化,我们考虑分支优化

bool is_leap_year3(uint32_t y) {
    return !(y & ((y % 25) ? 3 : 15));
}

性能比上面的要好

作者考虑第二种算术优化和分支优化的结合版本

作者是用求解器硬算的,考虑构造 ((y * f) & m) <= t格式,然后求解器算出满足条件的f m t

import z3

BITS = 32
f, m, t, y = z3.BitVecs('f m t y', BITS)

def target(y):
    return z3.And((y & 3) == 0, z3.Or(z3.URem(y, 25) != 0, (y & 15) == 0))

def candidate(x):
    return z3.ULE((x * f) & m, t)

solver = z3.Solver()
solver.add(z3.ForAll(y, z3.Implies(z3.ULE(y, 400),
                                   candidate(y) == target(y))))

if solver.check() == z3.sat:
    print(f'found solution: {solver.model()}')
else:
    print('no solution found')

The case of the UI thread that hung in a kernel call

不要在进程内挂起线程。自己给自己搞死锁。感谢 YexuanXiao投稿

Load-store conflicts

非常好的文章 多版本编译器压测对比才发现问题所在

数据存在存储加载冲突,导致性能瓶颈,这种通过汇编是看不出来的,得对比压测才能看出来

视频

File IO - Past, Present and Future - Guy Davidson - ACCU 2024

大部份都在讲历史,然后讲一段#embed 和提案相关的file handle之类的设计

Understanding the Filter View to Use It Right - Nicolai M. Josuttis - ACCU 2024

filter view由于缓存性质多了很多坑,主要是讲使用细节

Narrow Contracts and noexcept are Inherently Incompatible in C++ - John Lakos - ACCU 2024

Narrow contract和noexcept不兼容

什么?你不知道narrow contract是什么?翻出contract概念复习一下

开源项目介绍

  • asteria 一个脚本语言,可嵌入,长期找人,希望胖友们帮帮忙,也可以加群753302367和作者对线

互动环节

群聊怎样才能获取新的知识/共识?观群聊吵架有感


上一期

本期

v1.8.2: * 182

Choose a tag to compare

@wanghenshui wanghenshui released this 18 May 16:31

layout: post
title: 第182期

C++ 中文周刊 2025-04-30 第182期

周刊项目地址

公众号

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

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

RSS

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

本文感谢选择公理赞助,祝您身体健康万事如意


资讯

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

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

性能周刊

文章

On the Ignorability of Attributes

作者讲的是属性的可忽略性质导致和关键字不协调,比如[[deprecated]], [[nodiscard]] 为什么不是关键字,但constinit是

又比如override可以忽略,但他不是属性,是关键字

c++26引入了trivially_relocatable_if_eligible和replaceable_if_eligible两个关键字,按理说他应该类似属性这种可忽略,保持向后兼容,但不是

属性就不应该可忽略,这样能让这些奇怪关键字回归属性来用

大家有什么见解?我觉得确实有点乱,对付用的水平,什么属性,我直接一个宏代替

Unsigned comparisons using signed types

使用有符号类型来比较无符号数

考虑溢出加法

如果将最小可能值(非常小的负数值)M与有符号整数x和y相加,则(x + M) < (y + M)等同于将x和y视为无符号整数值进行比较。

原理:

将0映射到M(0成为最小值), 将所有正值映射到范围从M+1到-1(正值变为负值), 负值都会溢出,使最后一位变为零,而其他位保持不变,从而将负值从M到-1映射到范围0到-M-1(负值变为正值)

考虑避免c++溢出问题,使用异或,因为 x + M = (x^M) + ((x&M)<<1) 后半部分无符号数就是0,所以直接比较异或 (x ^ M) < (y ^ M)

Growing Buffers to Avoid Copying Data

主要讲的mmap/xmalloc/realloc/VirtualAlloc延长空间分配行为。没啥值得看的

Programming languages should have a tree traversal primitive

挺有意思,我直接贴代码了 https://godbolt.org/z/44f9eM41W

#include <vector>
#include <string>
#include <iostream>

enum class _tree_return {
    Continue = 0,
    Prune,
    Break
};

template<typename T, typename F1, typename F2, typename F3>
_tree_return _for_tree(T initial, F1 condition, F2 branch, F3 visit) {
    _tree_return result = visit(initial);
    if(result == _tree_return::Break) return _tree_return::Break;

    if(result != _tree_return::Prune) {
        for(T subnode : branch(initial)) {
            if(condition(subnode)) {
                _tree_return result = _for_tree(subnode, condition, branch, visit);
                if(result == _tree_return::Break) return _tree_return::Break;
            }
        }
    }
    return _tree_return::Continue;
}

#define tree_break return _tree_return::Break
#define tree_prune return _tree_return::Prune
#define tree_continue return _tree_return::Continue

                                                          //v-- semicolon to not allow you to get the return value here
#define for_tree(XName, Xinitial, Condition, Branch, Visit) ;_for_tree(Xinitial, \
[&](decltype(Xinitial) XName){ return Condition; }, \
[&](decltype(Xinitial) XName){ return std::vector<decltype(Xinitial)>Branch; }, \
[&](decltype(Xinitial) XName){ Visit; return _tree_return::Continue; })
//excuse the use of a std::vector in there, I guess you cant return an initialize_list from a lambda
//that wouldn't really be an issue if this was implemented at the language level instead of hacked together from lambdas and macros

struct Node {
    Node* left = NULL;
    Node* right = NULL;
    std::string value;
    Node(std::string value):value(value){}
};

int main() {
    //syntax is a little uglier than it could be if it was native
    
    //imperative tree sample
    for_tree(x, std::string(""), x.size()<=3, ({x+"a", x+"b", x+"c"}), {
        std::cout << x << std::endl;
    });
    
    //tree structure sample
    Node mytree("root");
    mytree.left = new Node("left");
    mytree.right = new Node("right");
    mytree.left->left = new Node("leftleft");
    
    for_tree(x, &mytree, x != NULL, ({x->left, x->right}), {
        std::cout << x->value << std::endl;
    });

    return 0;
}

The correct way to do type punning in C++ - The second act

struct ConfigValues {
  uint32_t                  chksum;
  std::array<uint32_t, 128> values;
};

bool ProcessData(std::span<unsigned char> bytes)
{
  if(bytes.size() < sizeof(ConfigValues)) { return false; }

  //UB 类型双关
  ConfigValues* cfgValues = reinterpret_cast<ConfigValues*>(bytes.data());

  return HandleConfigValues(cfgValues);
}

用bit_cast复制一份可能开销太大,怎么办?

struct ConfigValues {
  uint32_t                  chksum;
  std::array<uint32_t, 128> values;
};

bool ProcessData(std::span<unsigned char> bytes)
{
  if(bytes.size() < sizeof(ConfigValues)) { return false; }

  A Using std::start_lifetime_as
  ConfigValues* cfgValues = std::start_lifetime_as<ConfigValues>(bytes.data());

  return HandleConfigValues(cfgValues);
}

6 usability improvements in GCC 15

注释错误更好看,模版报错格式化,输出报告SARIF更文档化,可折叠, 默认-std=gnu23,以及提供gdiagnostic api(c,py)方便分析报告

总算现代了点

Detect control characters, quotes and backslashes efficiently using ‘SWAR’

使用SWAR高效检测控制字符(小于32)、引号和反斜杠

直接贴代码,看变量名字就能懂

bool has_json_escapable_byte(uint64_t x) {
    uint64_t is_ascii = 0x8080808080808080ULL & ~x;
    uint64_t lt32 =
        (x - 0x2020202020202020ULL);
    uint64_t sub34 = x ^ 0x2222222222222222ULL;
    uint64_t eq34 = (sub34 - 0x0101010101010101ULL);
    uint64_t sub92 = x ^ 0x5C5C5C5C5C5C5C5CULL;
    uint64_t eq92 = (sub92 - 0x0101010101010101ULL);
    return ((lt32 | eq34 | eq92) & is_ascii) != 0;
}

优化版本

bool has_json_escapable_byte(uint64_t x) {
    uint64_t is_ascii = 0x8080808080808080ULL & ~x;
    uint64_t xor2 = x ^ 0x0202020202020202ULL;
    uint64_t lt32_or_eq34 = xor2 – 0x2121212121212121ULL;
    uint64_t sub92 = x ^ 0x5C5C5C5C5C5C5C5CULL;
    uint64_t eq92 = (sub92 - 0x0101010101010101ULL);
    return ((lt32_or_eq34 | eq92) & is_ascii) != 0;
}

合并了32/34计算

Beware when moving a std::optional!

场景是你想掏空optional

template<typename T>
T do_something() {
    std::optional<T> opt = some_oracle<T>();

    if (!opt) {
        std::cerr << "Something terrible happened\n";
        std::exit(EXIT_FAILURE);
    }

    return *opt; // equivalent to .value(), but doesn't throw an exception
}

这样写会复制,这也是常见的用optional错误

比如大家会这样写

std::optional<T> opt = some_good_oracle<T>(); /* assume opt.has_value() */

if (opt.has_value())
    f1( std::move(opt.value()) ); // move the value to avoid copying
                                  // from here onwards opt doesn't have a value

if (opt.has_value())  // true, unexpected!
    f2( std::move(opt.value()) ); // move again!
                                  // in reality f2 got an empty/garbage T

容易用错,我们需要掏空optional

// Bad
// auto x = std::move(opt.value());

// Good
auto x = std::move(opt).value();

std::variant std::pair std::tuple std::any std::expected std::optional 使用注意事项

接着上一个,move这些类型,非常容易用错, 代码例子如下

#include <format>
#include <exception>
#include <variant>
#include <iostream>

class LifeTime {
	int a_ = 0;
	public:
    LifeTime(int a):a_(a){
        std::cout << "LifeTime(): " << a_ << std::endl;
    }

    ~LifeTime() {
        std::cout << "~LifeTime(): " << a_ << std::endl;
    }

	LifeTime(const LifeTime&) {
        std::cout << "LifeTime(const LifeTime&): " << a_ << std::endl;
    }

	LifeTime(LifeTime&& other) noexcept {
		this->a_ = other.a_;
        std::cout << "LifeTime(LifeTime&&): " << a_ << std::endl;
    }

	LifeTime& operator=(const LifeTime&) {
        std::cout << "LifeTime& operator=(const LifeTime&): " << a_ << std::endl;
        return *this;
    }

	LifeTime& operator=(LifeTime&&) noexcept {
        std::cout << "LifeTime& operator=(LifeTime&&): " << a_ << std::endl;
        return *this;
    }
};

//break RVO
std::optional<LifeTime> create_v1() {
	LifeTime a{ 1 };
	return a;
}

//break RVO
std::optional<LifeTime> create_v2() {
	return LifeTime{2};
}

//break RVO
std::optional<LifeTime> create_v3() {
	std::optional<LifeTime> opt;
	opt = LifeTime{ 3 };
	return opt;
}

std::optional<LifeTime> create_v4() {
	std::optional<LifeTime> opt;
	opt.emplace(4);
	return opt;
}

std::optional<LifeTime> create_v5() {
	return std::optional<LifeTime>{5};
}

std::optional<LifeTime> create_v6() {
	return std::optional<LifeTime>{std::in_place, 6};
}

Exploiting Undefined Behavior in C/C++ Programs for Optimization: A Study on the Performance Impact

说大部分依靠UB的优化都可以通过编译器优化/LTO捡回来。只能说仁者见仁吧。

Link-Time Optimization of Dynamic Casts in C++ Program

llvm ir级别重写dynamiccast带来收益,效果堪比dyn_cast。但使用条件苛刻,继承必须树形结构

核心思想就是既然是树形结构那就拍扁到一个vtable上,大家用地址+offset比较,跳过typeinfo比较

看一乐,有点思路借鉴

AoS vs SoA in practice: particle simulation

数组结构体怎么改成结构体数组,利用tuple,我照着他的思路抄了一个,很有意思 https://godbolt.org/z/8PGP3n58q

#include <vector>

template <size_t...>
struct IndexSequence
{
};

//#if __has_builtin(__type_pack_element)

////////////////////////////////////////////////////////////
#define SFML_BASE_TYPE_PACK_ELEMENT(N, ...) __type_pack_element<N, __VA_ARGS__>



//#if __has_builtin(__integer_pack)

//////...
Read more

C++ 中文周刊 2025-04-13 第181期

Choose a tag to compare

@wanghenshui wanghenshui released this 15 Apr 06:02
9da9282

公众号

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

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

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


资讯

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

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

性能周刊

文章

Announcing Guidelines Support Library v4.2.0

主要改动就是gsl span性能提升,以前相比std::span很慢(边界检查)

How can I choose a different C++ constructor at runtime?

考虑我们想要调用不同的基类

看代码

struct WidgetBase
{
    // local mode
    WidgetBase();

    // remote mode
    WidgetBase(std::string const& server);

    // The mutex makes this non-copyable, non-movable
    std::mutex m_mutex;
};

struct WidgetOptions
{
    ⟦ random stuff ⟧
};

struct Widget : WidgetBase
{
    Widget(WidgetOptions const& options) :
        // 不好使                
        CanBeLocal(options)                 
            ? WidgetBase()                  
            : WidgetBase(GetServer(options))
    {}

    static bool CanBeLocal(WidgetOptions const&);
    static std::string GetServer(WidgetOptions const&);
};

怎么搞?通过create函数+返回值优化来转发

struct Widget : WidgetBase
{
    Widget(WidgetOptions const& options) :
        WidgetBase(ChooseWidgetBase(options))
    {}

    static bool CanBeLocal(WidgetOptions const&);
    static std::string GetServer(WidgetOptions const&);

private:
    static WidgetBase ChooseWidgetBase(           
        WidgetOptions const& options)             
    {                                             
        if (CanBeLocal(options)) {                
            return WidgetBase();                  
        } else {                                  
            return WidgetBase(GetServer(options));
        }                                         
    }                                             
};

Troubleshooting between C++ Module and NVCC

分享一下失敗的經驗, 還想不到什麼好方法讓Module跟非Module溝通..

目前不推荐使用module

Improving on std::count_if()'s auto-vectorization

他的场景是这样的,检查一组uint8数组判断偶数个数,并且已经确认偶数在0-255之间

简单代码示这样的

auto count_even_values_v1(const std::vector<uint8_t> &vec)
{
    return std::count_if(
        vec.begin(),
        vec.end(),
        [](uint8_t x) { return x % 2 == 0; }
    );
}

观察汇编

.LCPI0_1:
    .byte   1
count_even_values_v1():
;   ............
    vpbroadcastb    xmm1, byte ptr [rip + .LCPI0_1]
.LBB0_6:
    vmovd   xmm2, dword ptr [rsi + rax]
    vpandn  xmm2, xmm2, xmm1
    vpmovzxbq       ymm2, xmm2
    vpaddq  ymm0, ymm0, ymm2
    add     rax, 4
    cmp     r8, rax
    jne     .LBB0_6

不知道为什么扩展成64位了。

原因在于countif的返回值difference_type,

但我们结果明显小于255,可以改成uint8

所以代码改成这样

template<typename Acc, typename It, typename Pred>
Acc custom_count_if(It begin, It end, Pred pred)
{
    Acc result = 0;
    for (auto it = begin; it != end; it++)
    {
        if (pred(*it))
            result++;
    }
    return result;
}

auto count_even_values_v2(const std::vector<uint8_t> &vec)
{
    return custom_count_if<uint8_t>(
        vec.begin(),
        vec.end(),
        [](uint8_t x) { return x % 2 == 0; }
    );
}

再看汇编

.LCPI0_2:
    .byte   1
count_even_values_v2():
;   ............
    vpbroadcastb    ymm1, byte ptr [rip + .LCPI0_2]
.LBB0_11:
    vmovdqu ymm2, ymmword ptr [rdx + rax]
    vpandn  ymm2, ymm2, ymm1
    vpaddb  ymm0, ymm2, ymm0
    add     rax, 32
    cmp     rdi, rax
    jne     .LBB0_11

可以看到之前的汇编是dword

每次处理4个8位值(dword),通过vpandn提取最低位并取反,再通过vpmovzxbq将结果零扩展为64位累加

现在是ymmword

每次处理32个8位值(ymmword),使用vpaddb直接累加8位结果

测试显示速度快两倍以上 godbolt https://godbolt.org/z/vvfeTsfKx

这个例子让我想起之前聊过的64*64计算问题

如果计算64 x 64 -> 128 不要提前转128 计算途中转就可以,编译器知道你想干嘛,提前转128会导致编译器认为你想生成256的数去截断 128,导致多余的mul

他这个优化就是先写出坑的代码,再改汇编改进

能确定结果集的优化会更立竿见影

通过调整布局跳过序列化反序列化

如果压缩就最外层zstd/lz4
不考虑字段兼容性问题,只为了快

游戏场景pb接受不了的场景

https://www.youtube.com/watch?v=agRbVcMkqTY

看一乐

Advanced C++ Optimization Techniques for High-Performance Applications — Part 1

讲了分支预测like/unlike,缓存优化,SIMD

缓存优化讲了

  • 局部性
  • prefetch
  • cache分块 loop tiling
    • 基本上BLAS库都会有这个优化,LLM场景有一个flashattention,同样的原理

一个loop tiling举例

const int BLOCK = 1024;
for (int start = 0; start < N; start += BLOCK) {
    int end = std::min(start + BLOCK, N);
    for (int i = start; i < end; ++i) {
        result[i] += compute(A[i], B[i]);
    }
}

Advanced C++ Optimization Techniques for High-Performance Applications — Part 2

讲了循环展开/向量化,函数内联和指令缓存效应,返回值优化,链接优化(LTO/WPO)内存对齐/填充,SOA/AOS

  • 循环展开通常没啥用,O3会帮你做。真要做,测试
  • 函数内联,可能加快,但是可能二进制膨胀
    • 注意指令缓存icache L1i 32k 热点代码做了反而会溢出导致触发指令读,需要观察itlb miss l1i miss

其他的没啥说的,讲过多次

Advanced C++ Optimization Techniques for High-Performance Applications — Part 3

讲了无锁编程,线程亲和,numa

false sharing问题,对象在同一个内存行导致互相影响

比如

struct PaddedCounters {
    alignas(64) std::atomic<int> c1;
    alignas(64) std::atomic<int> c2;
};

其他没啥有用的东西

Performance Engineering — Part 1

检查过多的上下文切换,如何判定

  • vmstat 看cs 万级别
  • pidstat 看 nvcswch/nivcswch 资源切换/非自愿切换
  • top/htop看负载
  • perf stat -a -e cs -e migrations -e faults

缓解

  • 线程池
  • 亲和性

检测内存碎片

  • vmstat si/so
  • /proc/meminfo Slab数量 MemAvailable多但MemFree少
  • smem -t
  • cat /proc/buddyinfo 看高等级空闲块数量,没有说明碎片多
  • perf record观察 compact_zone migrate_pages

缓解

  • 调整分配器的配置
  • 内存池
  • malloc_trim
  • 换容器,vector -> deque

Polymorphic, Defaulted Equality

案发现场

struct Base {
    virtual ~Base() = default;
    virtual auto operator==(Base const&) const -> bool = 0;
};

struct Derived : Base {
    int m1, m2;
    bool operator==(Base const& rhs) const override {
        if (typeid(rhs) == typeid(Derived)) {  // 确保类型严格匹配
            return *this == static_cast<Derived const&>(rhs); // 调用默认比较
        }
        return false;
    }
    bool operator==(Derived const&) const = default;  // 默认生成
};

默认生成会生成

auto Derived::operator==(Derived const& rhs) const -> bool {
    return static_cast<Base const&>(*this) == static_cast<Base const&>(rhs)
       and m1 == rhs.m1
       and m2 == rhs.m2;
}

调用基类,基类再调用子类,无限循环

解决方法就是这种接口(operator ==)别虚函数

Bypassing the branch predictor

考虑这么个场景

struct Transaction;
bool should_send(Transaction *t);
void send(Transaction *t);
void abandon(Transaction *t);

void resolve(Transaction *t)
{
    if (should_send(t)) {
        send(t);
    } else {
        abandon(t);
    }
}

在金融交易系统中,大部分交易请求会被放弃(abandon()),仅有少数需要发送(send())。由于分支预测器倾向于预测进入高频路径(abandon()),当实际需要执行低频的 send() 时,会导致以下性能问题

  • 分支预测错误​:产生约20个时钟周期的惩罚。
  • ​指令缓存未命中​:send() 相关代码因执行频率低,可能未被加载到指令缓存。
  • 流水线中断​:无法预取 send() 的后续指令。

这种场景是likely这种东西无法搞定的,需要的是硬件写死的那种需求

不是说likely不行,你加上likely没法改变运行状态中分支预测器的行为,只是改变汇编

有一种玩法就是cache warm,长时间跑假的执行的数据骗他。不过CPU肯定上升就是了


上一期

本期

下一期

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

Choose a tag to compare

@wanghenshui wanghenshui released this 15 Apr 06:01
0ccb87f

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 成为性能瓶颈

优化指南

  • TLS对象尽可能合并
  • 不要为TLS写构造函数
  • 为频繁访问的对象使用__attribute__((visibility("hidden")))
  • 为关键变量使用__attribute__((tls_model("initial-exec")))
  • 如果不是共享库,不要使用fpic (静态库链接到二进制,可以去掉)
  • 考虑使用编译器加速 比如-mtls-dialect=gnu2
  • 使用pthread key分配+自定义pthread_getspecifc绕过
#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进行置...
Read more

C++ 中文周刊 2025-02-24 第179期

Choose a tag to compare

@wanghenshui wanghenshui released this 23 Feb 17:04
e6273ca

周刊项目地址

公众号

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

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

RSS

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

本期文章由 YellyHornby,qlql 赞助 在此表示感谢 祝老板身体健康事业顺利


资讯

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

c++26 最新进展!Hagenberg会议之前微信过,这里补一下

本文来自前线记者Mick

Hagenberg (2025-02)总结
这次会议是C++26周期第六次会议,也是C++26 Feature Freeze;到这一步,所有没有进入Stage 3 (wording)的提案都只能推迟到C++29了,因此我们也确定了C++26所可能拥有的特性的最大范围。

这次会议同样是整个周期最重要的会议之一,我们迎来了C++20之后第一个T0级语言特性的通过,迎来了多个历经十年长跑的提案的修成正果,也迎来了C++在安全问题上迈出的第一大步。作为一个不在主场进行的会议,本次会议的参会人数只有200人(比上次少20人左右),但是依然迎来了大量重要的进展。

先来看看这次会议通过进入C++26的提案。语言方面,最重要的提案无疑是P2900 Contracts,这一重量级特性是对C assert()宏的正式承认和发展,加入了新的contract_assert宏和pre/post指示来明确实现函数的前置/后置条件。与assert宏依赖的NDEBUG不同,这些新的Contracts指示将会通过编译选项(-fcontract-semantic=...)指示,并提供了更加细致的四种不同语义(ignore,observe,quick_enforce,enforce)来满足各种场景的需求。Contracts是一个非常大的话题,在这里也没法用几句话囊括它的一切,但作为C++最重要的安全特性之一后面应该会有大量的教程来讲解这个特性

另一个重要的通过的语言特性是P2786 trivially relocatable。平凡迁移指的是那些“移动+析构=memcpy”的类型,而满足这个条件的类型出乎意料地多(例如三家里有两家的vector和string都能平凡迁移)有了语言层面的探测平凡迁移类型的手段之后,vector::resize之类的函数就可以将元素数组整体memcpy过去而不是一个个移动了。遗憾的是,在EWG的长期争吵之后,P2786最终选择了trivially_relocatable_if_eligible和replaceable_if_eligible这两个超级长的关键字名字作为标记一个类型可以平凡迁移的手段,并且为了安全这些关键字只有当所有成员都能平凡迁移的情况下才有效,极大限制了它们的可用性。

除此之外,这次会议还通过了P1976 #embed。这是一个C23的新预处理宏,允许将任意文件作为逗号分隔的列表引入程序。这是对于传统的#include "some_data.txt"的更高级解决方案,速度更快,也没有撞到字符串极限的可能性。它的功能也更强,可以通过#embed "something" limit(5)来限制最大可引入的元素数。

这次会议还通过了P3475(废除没人能懂的memory_order::consume),P2841(允许template<template concept C>和template<template bool B>等构造)

标准库方面,这次会议通过的最重要特性无疑是P3471 Standard Library Hardening,在标准中规定了“强化实现”的概念。在强化实现中,标准库中的各个operator[]和其他常见函数将会使用Contracts强制检查参数没有出界,从而能够有效避免一些最常见的越界UB的发生。和P2900结合,这两个提案是对近期爆发的对C++安全性的批评的有力回应。在P3100/P3599得到实现后,在语言和库方面我们终于对于常见UB有了有效的解决方案。

另一个重要提案是P0447 std::hive。这是一个悲情的提案,初版提出于2016年,经过9年争吵,整整29个revision,以及改名(colony -> hive),以及在2023年险些被直接拒绝,最终还是在这次会议修成正果。这个新的标准顺序容器是一个很有意思的容器,利用特殊的跳表+链状数组结构实现了所有元素的绝对迭代器稳定性(只要不被删除就永远有效)+均摊O(1)的插入/删除/迭代这样看似不可能达成的复杂度。这一特性使得它非常适合在各类游戏项目中储存entity;这些游戏元素很多时候会被不断插入和删除,但是既有的元素的位置不能变。

第三组重要的通过提案是P3372 constexpr容器和P3378 constexpr异常类型。在上次会议允许了编译期抛异常之后,这次将大多数标准异常类型(比如out_of_range,invalid_argument)完成了constexpr化,从而让编译期抛出这些异常成为可能。P3372也让list map deque等容器在编译期的使用成为可能,不过遗憾的是非透明编译期内存分配仍然没有任何解决的希望,从而这些容器的constexpr使用总是让人觉得有些别扭。

除此之外,这次会议还通过了多个SIMD bugfix提案,P3137 views::to_input,P2846 ranges::reserve_hint等Ranges增强;另外P3019 indirect and polymorphic在上次通过后发现了一些问题,这次会议再次通过了一下。

在各特性的进展方面,这次会议是Stage 2 -> 3的deadline,现在还没forward的就都被推迟到29周期去了。

EWG方面,这次会议P2996 静态反射的review已经接近尾声,基本确定将会在下次会议进入Plenary。这次会议通过了几个针对P2996的bugfix和扩展,包括P3547 access_context来限制反射体的获取可能性,以及P3096函数参数的反射。除此之外,BS和HS提出的重量级安全特性Profiles(其实就是写[[profiles::enforce(xxx)]]然后禁掉一些不安全特性,reinterpret_cast之类的)在讨论了一天半之后最终决定不进入C++26,而是改为发布一个白皮书(轻量级TS)来更快推进。(被忽略好几年的TM TS可能也会转换成白皮书)

其他大特性方面,模式匹配最终小比分落败,没能进入C++26,但是在最后一天EWG保住了P1306 Expansion Statement这个重要的反射的补充特性,也算保住了底线吧。接下来CWG将会有比较艰巨的任务,完成反射这个巨大提案的review的同时还被塞了30+篇小提案,最后可能不得不要掉出去一堆了。

LEWG方面,这次会议通过了大量S&R的附属提案,包括P3149 async_scope和P3296 let_async_scope这两个重量级的Structured Concurrency提案(遗憾的是std::task/lazy依然没有太大进展)和一个标准化的系统调度器(P2079)。除此之外,几个著名的提案包括P2988 std::optional<T&>,P3179 Ranges化的多线程算法,P2019线程名称与与栈大小,以及P0260多线程队列也得到了forward的机会,从而有机会进入C++26。接下来LWG的任务较为轻松,只要把那几个S&R的提案搞定其实就差不多了。

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

性能周刊

文章

API design note: Beware of adding an “Other” enum value

考虑这种枚举定义

enum class WidgetFlavor
{
    Vanilla,
    Chocolate,
    Strawberry,
    Other,
};

有个Other,也许有一天,你新添加了一个Flavor,Mint,那么这个Other会不会包含这个新加的Mint?

永远不要用这种歧义的玩意,将来会引入维护性问题

Investigating an argument-dependent lookup issue and working around it

现场长这样

namespace winrt::impl
{
    ⟦ ... ⟧

    template <typename Delegate, typename... Arg>
    bool invoke(Delegate const& delegate, Arg const&... args) noexcept;

    ⟦ ... ⟧

    template <typename Derived, typename AsyncInterface, typename TProgress = void>
    struct promise_base : implements<Derived, AsyncInterface, Windows::Foundation::IAsyncInfo>
    {
        ⟦ ... ⟧

        void set_completed() noexcept
        {
            async_completed_handler_t<AsyncInterface> handler;
            AsyncStatus status;

            ⟦ ... ⟧

            if (handler)
            {
                invoke(handler, *this, status);
            }
        }
    }
}

没有调用内部的invoke,被ADL引入了std::invoke

加上内部命名空间解决

The Weirdest MSVC Address Sanitizer Bug

#include <memory>
#include <type_traits>

template <typename stream>
struct io {
    io(stream) : b(false) {}
    alignas(64) bool b;
};

int main() {
    auto pi = std::make_unique<int>(543);
    using iio = io<int>;
    io x(7);
    static_assert(std::is_same_v<decltype(x), iio>);
}

msvc用 /fsanitize=address会误报。

ODR, libc++ hardening, Profiles and Contracts

简单介绍一下 WG21 和安全profile constract可能对ODR规则造成影响,对于标准库实现存在挑战

Eliminating redundant bound checks

跳转表可能会有边界检查没有优化掉

#include <array>
#include <cstdint>
#include <cstdlib>

static constexpr std::array<size_t, 256> TABLE = {
    798, 553, 541, 345, 276, 698, 861, 448,  898,  588, 678, 593, 265, 611, 915, 835, 893, 3,   411,
    769, 792, 115, 526, 836, 356, 454, 279,  876,  924, 644, 449, 682, 727, 267, 665, 889, 75,  825,
    667, 222, 603, 43,  376, 655, 221, 811,  35,   182, 550, 112, 660, 374, 241, 589, 993, 428, 747,
    673, 11,  954, 296, 181, 842, 91,  764,  805,  155, 916, 431, 380, 860, 970, 998, 982, 445, 144,
    199, 587, 400, 356, 283, 645, 575, 288,  521,  861, 175, 289, 616, 766, 48,  29,  265, 492, 832,
    412, 766, 827, 22,  924, 766, 559, 1012, 826,  391, 254, 871, 347, 615, 529, 789, 606, 259, 4,
    924, 988, 2,   833, 582, 366, 402, 186,  328,  181, 58,  124, 478, 380, 782, 582, 993, 536, 790,
    657, 558, 829, 637, 129, 177, 72,  847,  916,  236, 398, 37,  932, 844, 938, 580, 784, 58,  713,
    490, 12,  680, 525, 940, 30,  241, 345,  1019, 0,   742, 169, 660, 253, 187, 545, 288, 333, 137,
    587, 731, 660, 600, 128, 389, 44,  109,  401,  195, 147, 631, 690, 191, 614, 797, 744, 28,  946,
    348, 851, 889, 279, 521, 724, 897, 92,   773,  635, 212, 339, 978, 639, 282, 414, 691, 365, 706,
    953, 754, 976, 482, 727, 257, 673, 443,  99,   341, 540, 247, 427, 312, 713, 943, 815, 595, 611,
    191, 827, 179, 827, 432, 472, 237, 163,  218,  35,  475, 800, 299, 185, 779, 27,  924, 981, 11,
    504, 160, 9,   342, 938, 69,  745, 575,  791,
};

static size_t first_idx_to_second_idx(uint8_t first_idx)
{
    switch (first_idx) {
    case 0: return 798; case 1: return 553; case 2: return 541; case 3: return 345;
    case 4: return 276; case 5: return 698; case 6: return 861; case 7: return 448;
    case 8: return 898; case 9: return 588; case 10: return 678; case 11: return 593;
    case 12: return 265; case 13: return 611; case 14: return 915; case 15: return 835;
    case 16: return 893; case 17: return 3; case 18: return 411; case 19: return 769;
    case 20: return 792; case 21: return 115; case 22: return 526; case 23: return 836;
    case 24: return 356; case 25: return 454; case 26: return 279; case 27: return 876;
    case 28: return 924; case 29: return 644; case 30: return 449; case 31: return 682;
    case 32: return 727; case 33: return 267; case 34: return 665; case 35: return 889;
    case 36: return 75; case 37: return 825; case 38: return 667; case 39: return 222;
    case 40: return 603; case 41: return 43; case 42: return 376; case 43: return 655;
    case 44: return 221; case 45: return 811; case 46: return 35; case 47: return 182;
    case 48: return 550; case 49: return 112; case 50: return 660; case 51: return 374;
    case 52: return 241; case 53: return 589; case 54: return 993; case 55: return 428;
    case 56: return 747; case 57: return 673; case 58: return 11; case 59: return 954;
    case 60: return 296; case 61: return 181; case 62: return 842; case 63: return 91;
    case 64: return 764; case 65: return 805; case 66: return 155; case 67: return 916;
    case 68: return 431; case 69: return 380; case 70: return 860; case 71: return 970;
    case 72: return 998; case 73: return 982; case 74: return 445; case 75: return 144;
    case 76: return 199; case 77: return 587; case 78: return 400; case 79: return 356;
    case 80: return 283; case 81: return 645; case 82: return 575; case 83: return 288;
    case 84: return 521; case 85: return 861; case 86: return 175; case 87: return 289;
    case 88: return 616; case 89: return 766; case 90: return 48; case 91: return 29;
    case 92: return 265; case 93: return 492; case 94: return 832; case 95: return 412;
    case 96: return 766; case 97: return 827; case 98: return 22; case 99: return 924;
    case 100: return 766; case 101: return 559; case 102: return 1012; case 103: return 826;
    case 104: return 391; case 105: return 254; case 106: return 871; case 107: return 347;
    case 108: return 615; case 109: return 529; case 110: return 789; case 111: return 606;
    case 112: return 259; case 113: return 4; case 114: return 924; case 115: return 988;
    case 116: return 2; case 117: return 833; case 11...
Read more

C++ 中文周刊 2025-02-09 第178期

Choose a tag to compare

@wanghenshui wanghenshui released this 09 Feb 15:30
27e807a

周刊项目地址

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

RSS

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

本期文章由 ZIRQ 赞助 在此表示感谢 祝老板发大财身体健康永远不死


资讯

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

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

性能周刊

文章

shared_ptr overuse in C++

省流 不共享不要滥用

Falsehoods programmers believe about null pointers

列举一些对空指针的误解,比如访问空指针会挂,c/c++语言设定如此,其他语言会捕获异常特殊处理

额我觉得还是不要知道的好

C++26: erroneous behaviour

c++的未定义行为涉及的面太广,有必要收敛一些场景,比如没有初始化读就读这种场景,归纳为EB

如果真的需要这种行为,主动标记[[indeterminiate]] 这种标记下没初始化就使用才被归纳为UB

比如

void foo() {
  int d [[indeterminate]];  // d has an indeterminate value
  bar(d); // that's undefined behaviour!
}

不过目前为止只是提案 P2795,没有编译器支持实现

Thread-safe memory copy

代码鉴赏 安全的memcpy

inline void Relaxed_Memcpy(volatile Atomic8* dst, volatile const Atomic8* src,
                           size_t bytes) {
  constexpr size_t kAtomicWordSize = sizeof(AtomicWord);
  while (bytes > 0 &&
         !IsAligned(reinterpret_cast<uintptr_t>(dst), kAtomicWordSize)) {
    Relaxed_Store(dst++, Relaxed_Load(src++));
    --bytes;
  }
  if (IsAligned(reinterpret_cast<uintptr_t>(src), kAtomicWordSize) &&
      IsAligned(reinterpret_cast<uintptr_t>(dst), kAtomicWordSize)) {
    while (bytes >= kAtomicWordSize) {
      Relaxed_Store(
          reinterpret_cast<volatile AtomicWord*>(dst),
          Relaxed_Load(reinterpret_cast<const volatile AtomicWord*>(src)));
      dst += kAtomicWordSize;
      src += kAtomicWordSize;
      bytes -= kAtomicWordSize;
    }
  }
  while (bytes > 0) {
    Relaxed_Store(dst++, Relaxed_Load(src++));
    --bytes;
  }
}

每个byte都原子store load 线程安全了

对于V8来说,安全比较重要。即使这玩意慢三四十倍

Richard Szabo - Traps with Smart Pointers (Lightning Talk)

省流 shared ptr一律使用make_shared构造

使用alias 构造 搭配weak ptr使用存在问题

如何避免类只能通过make_shared构造?构造函数tag + 静态函数匹配。看嗲吗

class A: std::enable_shared_from_this<A> {
 private:
  struct Private {};
 public:
  A(Private dummy, int member) : member_(member) {}
  template <typename... ArgsT>
  static std::shared_ptr<A> create(ArgsT&&... args) {
    return std::make_shared<A>(Private(), std::forward<ArgsT>(args)...);
  }
 private:
  int member_ = 1;
};

int main() {
  std::cout << "The Start\n\n";
  auto a_sptr = A::create(42);
  std::cout << std::endl << "The End!";
  return 0;
}

Data Storage in Entity Component Systems

介绍数据局部性有利的数据结构 Dense/Sparse Array

简单说就是这个德行

#include <vector>
#include <cassert>

template<typename T>
class DenseSparseArray {
public:
    // 插入元素(假设 entity 是唯一标识)
    void insert(uint32_t entity, const T& value) {
        if (entity >= sparse.size()) {
            sparse.resize(entity + 1, -1); // -1 表示无效索引
        }
        
        if (sparse[entity] == -1) {
            sparse[entity] = dense.size();
            dense.push_back({entity, value});
        }
    }

    // 删除元素
    void erase(uint32_t entity) {
        if (contains(entity)) {
            size_t dense_idx = sparse[entity];
            auto& last = dense.back();

            // 将要删除的元素与最后一个元素交换
            std::swap(dense[dense_idx], last);
            sparse[last.entity] = dense_idx;

            dense.pop_back();
            sparse[entity] = -1;
        }
    }

    // 访问元素
    T& operator[](uint32_t entity) {
        assert(contains(entity));
        return dense[sparse[entity]].value;
    }

    // 判断是否存在
    bool contains(uint32_t entity) const {
        return entity < sparse.size() && sparse[entity] != -1;
    }

    // 迭代器支持
    auto begin() { return dense.begin(); }
    auto end() { return dense.end(); }

private:
    struct Element {
        uint32_t entity;
        T value;
    };

    std::vector<int> sparse;  // 稀疏数组(存储索引)
    std::vector<Element> dense; // 密集数组(实际数据)
};

enseSparseArray<int> arr;

arr.insert(100, 42);  // 插入 entity=100
arr.insert(200, 77);  // 插入 entity=200

std::cout << arr[100]; // 输出 42

arr.erase(100);       // 删除 entity=100

for (auto& elem : arr) { // 遍历所有有效元素
    std::cout << elem.entity << ": " << elem.value << "\n";
}

deepseek帮我写的

这种玩法比较方便遍历且不失访问速度,对于小数据集是非常有用的

另外PPT里还提到了一些优化。感兴趣可以看EnTT代码

Optimizing Mulithreading Performance

简单介绍MESI那套东西,多线程 cache局部性非常重要,另外介绍一些影响性能的场景

  1. 数据竞争,比如atomic fetch add
  2. 数据局部性影响,多个线程访问一份数据
  3. 线程过多,上下文切换开销重
  4. False Sharing,不同数据在同一块cacheline造成互相干扰
    1. 使用std::harware_destructive_interference_size alignas
    2. 典型场景
      1. 线程数组,每个线程访问数组的一个元素,没padding大概率互相干扰
      2. 类似,矩阵计算分块,分的不够开导致互相干扰
      3. 结构体字端访问,没有pading导致互相干扰
      4. 动态分配的小对象,存在可能
        1. 分配大内存专属使用
        2. 尽可能对齐

Class layout

比较常规

介绍了基本的字段大小,继承影响,对齐影响,EBO,no_unique_address, 以及分析工具

clang++ -cc1 -fdump-record-layouts # (or -Xclang -fdump-record-layouts)
g++ -fdump-lang-class (or -fdump-class-hierarchy) # (dumps to a file, so not usable in Compiler explorer)
msvc /d1reportAllClassLayout

simd库横向对比(2023)

vc是c++26simd前身。感觉不是很好用。

业界还是用google highway多一些。

另外社区xmind很火(或者说炒作很火)但是这里没收录,可能是太新了

TypeSanitizer

cat example_AliasViolation.c
int main(int argc, char **argv) {
  int x = 100;
  float *y = (float*)&x;
  *y += 2.0f;          // Strict aliasing violation
  return 0;
}

#Compile and link

clang++ -g -fsanitize=type example_AliasViolation.cc

很好用。想体验的可以试一下

cmake -DCMAKE_BUILD_TYPE=Release -DLLVM_ENABLE_PROJECTS="clang" -DLLVM_ENABLE_RUNTIMES="compiler-rt" <path to source>/llvm

代码鉴赏:成员字段级RAII

来源

godbolt

#include <iostream>
#include <type_traits>
#include <cstddef>

using VoidPtrFn = void (*)(void*);

struct MemberScopeGuard
{
    VoidPtrFn fn;
    ~MemberScopeGuard() { fn(this); }
};

#define TOKEN_PASTE_IMPL(x, y) x##y
#define TOKEN_PASTE(x, y)      TOKEN_PASTE_IMPL(x, y)

#define MEMBER_SCOPE_GUARD(...)                                                                           \
    MemberScopeGuard TOKEN_PASTE(memberScopeGuard, __LINE__)                                              \
    {                                                                                                     \
        ([]<auto Fn>(auto* xThis) -> VoidPtrFn                                                            \
        {                                                                                                 \
            return [](void* x) -> void                                                                    \
            {                                                                                             \
                using T = std::remove_pointer_t<decltype(xThis)>;                                         \
                Fn(*(reinterpret_cast<T*>(                                                                \
                    reinterpret_cast<char*>(x) - offsetof(T, TOKEN_PASTE(memberScopeGuard, __LINE__))))); \
            };                                                                                            \
        }).template operator()<[](auto& self) __VA_ARGS__>(this)                                          \
    }

struct parent {
    int x = 10;
    MEMBER_SCOPE_GUARD({
        std::cout << "Parent pointer is " << &self << '\n';
        std::cout << "Parent x is " << self.x << '\n';
    });

    int y = 25;
    MEMBER_SCOPE_GUARD({
        std::cout << "Parent pointer is " << &self << '\n';
        std::cout << "Parent y is " << self.y << '\n';
    });
};

int main() {
    parent x;
    std::cout << "Address of parent is " << &x << '\n';
}

除了感叹c++真夸张之外没什么用。

另外这个字段guard必须得放在成员后面,不能使用guard后面的字段(反向析构顺序:)

感谢天失败投稿以及原作者 Vittorio Romeo

开源项目介绍

  • asteria 一个脚本语言,可嵌入,长期找人,希望胖友们帮帮忙,也可以加群753302367和作者对线

互动环节

街霸6不知火舞联动上线了,非常好玩


上一期

本期

下一期

C++ 中文周刊 2025-02-01 第177期

Choose a tag to compare

@wanghenshui wanghenshui released this 01 Feb 14:22
a9e2798

周刊项目地址

公众号

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

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

RSS

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

本期文章由 F.v.S 祥子 Jared 赞助 在此表示感谢

祝大家新年快乐。又要返工了


资讯

标准委员会动态/ide/编译器信息放在这里 c++26最新进展可以看这个帖子

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

性能周刊

文章

C++26: attributes in structured bindings

结构化绑定的参数可以标记属性attr,比如

std::map<int, std::string> m{
        {1, "one"}, {2, "two"} {3, "three"}};

for(const auto& [ [[maybe_unused]] k, v]: m) {
        DEBUG(k) // only used in debug builds
    std::cout << v << '\n';
}

A pattern for obtaining a single value while holding a lock

通过返回值复制,而不是手动赋值。规避潜在的多余构造

std::mutex m_mutex;
Widget m_widget;

// Get a copy of the widget
Widget widget;
{
    auto guard = std::lock_guard(m_mutex);
    widget = m_widget;
}

这是常规写法,不好。倾向于通过返回值一次赋值

// Get a copy of the widget
Widget widget = [&] {
    auto guard = std::lock_guard(m_mutex);
    return m_widget;
}();

这种lambda用的多了你也可以通过封装函数来返回

Widget CopySavedWidget()
{
    auto guard = std::lock_guard(m_mutex);
    return m_widget;
}

类似的保存-销毁需求

Widget widget = [&] {
    auto guard = std::lock_guard(m_mutex);
    return std::exchange(m_widget, {});
}();

也可以这样封装,更泛化一点

template<typename T>
Widget ExchangeSavedWidget(T&& value)
{
    auto guard = std::lock_guard(m_mutex);
    return std::exchange(m_widget, std::forward<T>(value)):
}

PPSSPP or psp? Uncovering bugs from the past

鉴赏几个PPSSPP项目的bug

static int sceNetAdhocctlGetAddrByName(const char *nickName,
                                       u32 sizeAddr, u32 bufAddr)
{
  ....
  // Copied to null-terminated var to prevent unexpected behaviour on Logs
  memcpy(nckName, nickName, ADHOCCTL_NICKNAME_LEN); 

  ....
  if (netAdhocctlInited)
  {
    // Valid Arguments
    if (nickName != NULL && buflen != NULL)
    {
      ....
    }
    ....
  }
}

如果if里的nickName存在等于nullptr的可能,那么memcpy的行为会有UB,那么这个代码必然有问题了

int internal_profiler_find_cat(const char *category_name, bool create_missing)
{
  int i;
  for (i = 0; i < MAX_CATEGORIES; i++)
  {
    const char *catname = categories[i].name;
    if (!catname)
      break;
#ifdef UNIFIED_CONST_STR
    if (catname == category_name)
    {
#else
    if (!strcmp(catname, category_name))                          // <=
    {
#endif
      return i;
    }
  }

  if (i < MAX_CATEGORIES && category_name && create_missing)      // <=
  {
    ....
  }     
}

没有检查category_name是不是nullptr就放到strcmp里,潜在bug

static void __GameModeNotify(u64 userdata, int cyclesLate)
{
  ....
  if (gameModeSocket < 0)
  {
    // ReSchedule
    CoreTiming::ScheduleEvent(usToCycles(GAMEMODE_UPDATE_INTERVAL) - cyclesLate,
                              gameModeNotifyEvent, userdata);
    return;
  }

  auto sock = adhocSockets[gameModeSocket - 1];
  ....
}

数组索引可能是-1

SoftGPU::SoftGPU(GraphicsContext *gfxCtx, Draw::DrawContext *draw)
  : GPUCommon(gfxCtx, draw)
{
  ....
  drawEngine_ = new SoftwareDrawEngine();
  if (!drawEngine_)
    return;
  ....
}

这个指针检查屁用没有,new不出来会抛异常。如果在意就用catch兜住啊

static std::vector<MicWaitInfo> waitingThreads;
....
static void __MicBlockingResume(u64 userdata, int cyclesLate)
{
  ....
  int count = 0;
  for (auto waitingThread : waitingThreads)
  {
    if (waitingThread.threadID == threadID)
    {
      ....
      if (Microphone::isHaveDevice())
      {
        if (Microphone::getReadMicDataLength() >= waitingThread.needSize)
        {
          ....
          waitingThreads.erase(waitingThreads.begin() + count);    // <=
        }
        else
        {
          ....
        }
      } 
      else
      {
        ....
        waitingThreads.erase(waitingThreads.begin() + count);      // <=
        readMicDataLength += waitingThread.needSize;
      }
    }

    ++count;
  }
}

经典问题,循环中删除

void Int_VecDo3(MIPSOpcode op)
{
  ....
  u32 lastsat = (currentMIPS->vfpuCtrl[VFPU_CTRL_DPREFIX] & 3) << (n + n - 2);
  ....
}

n + n -2可能是负数 <<就UB了

inline float Float16ToFloat(float16 ix)
{
    float x;
    memcpy(&x, &ix, sizeof(float));
    return x;
}

复制越界

void Jit::Comp_SVQ(MIPSOpcode op)
{
  CONDITIONAL_DISABLE(LSU_VFPU);

  int imm = (signed short)(op&0xFFFC);
  int vt = (((op >> 16) & 0x1f)) | ((op&1) << 5);
  MIPSGPReg rs = _RS;

  CheckMemoryBreakpoint(0, rs, imm);

  switch (op >> 26)
  {
    case 53: //lvl.q/lvr.q
    {
      if (!g_Config.bFastMemory)
      {
        DISABLE;
      }

      DISABLE;
  ....
}

代码语义重复 DISABLE无论如何都能走到

void BlockAllocator::Block::DoState(PointerWrap &p)
{
  char tag[32];  
....
  size_t tagLen = strlen(tag);
  if (tagLen != sizeof(tag))
    memset(tag + tagLen, 0, sizeof(tag) - tagLen);
  DoArray(p, tag, sizeof(tag));
}

多余的if分支,永远true

void netAdhocValidateLoopMemory()
{
  // Allocate Memory if it wasn't valid/allocated
  // after loaded from old SaveState
  if (   !dummyThreadHackAddr
      || (   dummyThreadHackAddr
          && strcmp("dummythreadhack",
                    kernelMemory.GetBlockTag(dummyThreadHackAddr)) != 0))
  {
  ....
}

条件重复

void QueueCallback(void (*func)(VulkanContext *vulkan, void *userdata),
                   void *userdata)
{ 
  callbacks_.push_back(Callback(func, userdata));
}
void VulkanRenderManager::EndCurRenderStep()
{
  for (VKRGraphicsPipeline *pipeline : pipelinesToCheck_)
  {
    if (!pipeline)
    {
      // Not good, but let's try not to crash.
      continue;
    }

    if (!pipeline->pipeline[(size_t)rpType])
    {
      pipeline->pipeline[(size_t)rpType] = Promise<VkPipeline>::CreateEmpty();
      _assert_(renderPass);
      compileQueue_.push_back(CompileQueueEntry(pipeline,
            renderPass->Get(vulkan_, rpType, sampleCount),rpType, sampleCount));
      needsCompile = true;
    }
  }
}

push_back不如emplace_back

C stdlib isn’t threadsafe and even safe Rust didn’t save us

省流 setenv/getenv不是线程安全的。涉及到多个语言runtime读写env了。倒霉赶上了属于是

How to Simplify Object Comparisons with Ties in C++11/14

省流,tie

struct Person
{
    std::string first_name;
    std::string last_name;
    std::uint8_t age;

    auto tied() const
    {
        return std::tie(first_name, last_name, age);
    }

    bool operator==(const Person& other) const
    {
        return tied() == other.tied();
    }

    bool operator<(const Person& other) const
    {
        return tied() < other.tied();
    }
};

开源项目介绍


上一期 下一期

C++ 中文周刊 2025-01-25 第176期

Choose a tag to compare

@wanghenshui wanghenshui released this 25 Jan 17:04

周刊项目地址

公众号

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

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

RSS

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


资讯

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

安全问题还在吵架

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

性能周刊

文章

Measuring code size and performance

他的场景 不用异常处理错误,还是快的。虽然这些年来异常已经进化了快了一些

Reminder: When a C++ object fails to construct, the destructor does not run

注意你手写的guard类 构造函数不能失败,否则构造失败不析构就泄漏了

Regular expressions can blow up!

省流,fuck std::regex

#include <iostream>
#include <regex>

int main() {
    std::string text = "Everyone loves Lucy.";
    std::regex pattern(R"(.*+s}}@w)"); 
    // Perform regex search
    std::smatch match;
    bool found = std::regex_search(text, match, pattern);
    std::cout << "Regex search result: " 
          << (found ? "Match found" : "No match") << std::endl;
    return 0;
}

这段代码运行七分钟

std::nontype_t: What is it, and Why

抽象type 一个tag 重载 帮助function ref匹配constexpr函数

Protecting Coders From Ourselves: Better Mutex Protection

省流,boost::synchronized_value

folly也有synchronizedwith<T>

感觉这个周刊写的越多越发现重复。。评论区不知道的说一下,不行就folly组件挨个介绍

借助 Windsurf Sonnet Debug 过程一例

虽然和c++没啥关系,分享只是感叹AI太强了,程序员真的有点可有可无了

Parsing JSON in C & C++: Singleton Tax

省流 池化加速助力 解析加快 局部性功劳

Pipeline architectures in C++ - Boguslaw Cyganek - Meeting C++ 2024

他讲的不是CPU那个pipeline,也不是任务调度那个pipeline,讲的是这个比玩意

template < typename InT, typename InE, typename Function >
requires std::invocable< Function, std::expected< InT, InE > >
			&& is_expected< typename std::invoke_result_t< Function, std::expected< InT, InE > > >
constexpr auto operator | ( std::expected< InT, InE > && ex, Function && f ) -> typename std::invoke_result_t< Function, std::expected< InT, InE > >
{
	return std::invoke( std::forward< Function >( f ), /***/ std::forward< std::expected< InT, InE > >( ex ) );
}

....

	// ----------------------------------------------------------------------------------------------------------------
	// Here we create our CUSTOM PIPE
	auto res = 	PayloadOrError { Payload { "Start string ", 42 } } | Payload_Proc_1 | Payload_Proc_2 | Payload_Proc_3 ;
	// ----------------------------------------------------------------------------------------------------------------
	 

就是类似range的pipe语法传播

如何评价,光顾着耍帅了有点

C++ programmer's guide to undefined behavior: part 12 of 11

继续介绍坑点

std::reserve和std::resize区别

resize会初始化0, size == capacity

reserve不会 size == 0

但没有resize_with_overwrite这种跳过填0的操作,只有string有

resize reserve非常容易用错

注意无符号数取反问题

struct Element {
  size_t width; // original non-scaled width
  ....
};

// You are using smart component system that uses
// IDs to refer to elements.
using ElementID = uint64_t; 

// Positions in OpenGL/DirectX/Vulkan worlds are floats
struct Offset {
  float x;
  float y;
};

size_t get_width(ElementID);
float screen_scale();
void move_by(ElementID, Offset);

void on_unchecked(ElementID el) {
  auto w = get_width(el);
  move_by(el, Offset {
    -w * screen_scale() * 0.3f,
    0.0f
  });
}

这个-w必有问题,另外编译选项查不出来

对齐和隐式创建引发的问题

#pragma pack(1)
struct Record {
  long value;
  int data;
  char status;
};

int main() {
  Record r { 42, 42, 42};
  static_assert(sizeof(r) == sizeof(int) + sizeof(char) + sizeof(long));
  std::cout <<
    std::format("{} {} {}", r.data, r.status, r.value); // 42 - '*'
}

看这没问题?

int main() {
  Record records[] = { { 42, 42, 42}, { 42, 42, 42}  };
  static_assert(sizeof(records) ==
                2 * ( sizeof(int) + sizeof(char) + sizeof(long) ));
  for (const auto& r: records) {
    std::cout << std::format("{} {} {}", r.data, r.status, r.value); // 42 - '*'
  }
}

改成两个就会炸

问题来自赋值隐式创建int 但实际上不是int,是pack bit

怎么解决?复制一份

int main() {
  Record records[] = { { 42, 42, 42}, { 42, 42, 42}  };
  for (const auto& r: records) {

    // C++23 has wonderful auto() for this purpose
    std::cout << std::format("{} {} {}",
      auto(r.data), auto(r.status), auto(r.value)); 

    // In C++20,
    auto data = r.data; auto status = r.status; auto value = r.value;
    std::cout << std::format("{} {} {}", data, status, value); 

    // Or completely ugly and unstable to type changes
    std::cout << std::format("{} {} {}", static_cast<int>(r.data), 
                                         static_cast<char>(r.status), 
                                         static_cast<long>(r.value>));
  }
}

但实际上编译器应该阻止这种行为

后面还有coroutine的问题,特长。我准备拆开单独写一下

一个cmakelist warn设置

set (MY_CXX_FLAGS
"-Wall \
-Wextra \
-Werror \
-Wsuggest-override \
-Wno-unknown-warning-option \
-Wno-array-bounds \
-pedantic-errors" )
set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${MY_CXX_FLAGS}")

开源项目介绍


上一期 下一期

C++ 中文周刊 2025-01-12 第175期

Choose a tag to compare

@wanghenshui wanghenshui released this 12 Jan 16:40
78aca9d

周刊项目地址

公众号

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

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

RSS

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

本期文章由 MSK 赞助 老板大气祝老板永远不死


资讯

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

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

文章

Counting the digits of 64-bit integers

其实比较常规,就是查表

int int_log2(uint64_t x) { return 63 - __builtin_clzll(x | 1); } // c++20可以用bit_width
int digit_count(uint64_t x) {
  static uint64_t table[] = {9,
                             99,
                             999,
                             9999,
                             99999,
                             999999,
                             9999999,
                             99999999,
                             999999999,
                             9999999999,
                             99999999999,
                             999999999999,
                             9999999999999,
                             99999999999999,
                             999999999999999ULL,
                             9999999999999999ULL,
                             99999999999999999ULL,
                             999999999999999999ULL,
                             9999999999999999999ULL};
  int y = (19 * int_log2(x) >> 6);
  y += x > table[y];
  return y + 1;
}

或者更极端一点

int alternative_digit_count(uint64_t x) {
static uint64_t table[64][2] = {
    { 0x01, 0xfffffffffffffff6ULL },
    { 0x01, 0xfffffffffffffff6ULL },
    { 0x01, 0xfffffffffffffff6ULL },
    { 0x01, 0xfffffffffffffff6ULL },
    { 0x02, 0xffffffffffffff9cULL },
    { 0x02, 0xffffffffffffff9cULL },
    { 0x02, 0xffffffffffffff9cULL },
    { 0x03, 0xfffffffffffffc18ULL },
    { 0x03, 0xfffffffffffffc18ULL },
    { 0x03, 0xfffffffffffffc18ULL },
    { 0x04, 0xffffffffffffd8f0ULL },
    { 0x04, 0xffffffffffffd8f0ULL },
    { 0x04, 0xffffffffffffd8f0ULL },
    { 0x04, 0xffffffffffffd8f0ULL },
    { 0x05, 0xfffffffffffe7960ULL },
    { 0x05, 0xfffffffffffe7960ULL },
    { 0x05, 0xfffffffffffe7960ULL },
    { 0x06, 0xfffffffffff0bdc0ULL },
    { 0x06, 0xfffffffffff0bdc0ULL },
    { 0x06, 0xfffffffffff0bdc0ULL },
    { 0x07, 0xffffffffff676980ULL },
    { 0x07, 0xffffffffff676980ULL },
    { 0x07, 0xffffffffff676980ULL },
    { 0x07, 0xffffffffff676980ULL },
    { 0x08, 0xfffffffffa0a1f00ULL },
    { 0x08, 0xfffffffffa0a1f00ULL },
    { 0x08, 0xfffffffffa0a1f00ULL },
    { 0x09, 0xffffffffc4653600ULL },
    { 0x09, 0xffffffffc4653600ULL },
    { 0x09, 0xffffffffc4653600ULL },
    { 0x0a, 0xfffffffdabf41c00ULL },
    { 0x0a, 0xfffffffdabf41c00ULL },
    { 0x0a, 0xfffffffdabf41c00ULL },
    { 0x0a, 0xfffffffdabf41c00ULL },
    { 0x0b, 0xffffffe8b7891800ULL },
    { 0x0b, 0xffffffe8b7891800ULL },
    { 0x0b, 0xffffffe8b7891800ULL },
    { 0x0c, 0xffffff172b5af000ULL },
    { 0x0c, 0xffffff172b5af000ULL },
    { 0x0c, 0xffffff172b5af000ULL },
    { 0x0d, 0xfffff6e7b18d6000ULL },
    { 0x0d, 0xfffff6e7b18d6000ULL },
    { 0x0d, 0xfffff6e7b18d6000ULL },
    { 0x0d, 0xfffff6e7b18d6000ULL },
    { 0x0e, 0xffffa50cef85c000ULL },
    { 0x0e, 0xffffa50cef85c000ULL },
    { 0x0e, 0xffffa50cef85c000ULL },
    { 0x0f, 0xfffc72815b398000ULL },
    { 0x0f, 0xfffc72815b398000ULL },
    { 0x0f, 0xfffc72815b398000ULL },
    { 0x10, 0xffdc790d903f0000ULL },
    { 0x10, 0xffdc790d903f0000ULL },
    { 0x10, 0xffdc790d903f0000ULL },
    { 0x10, 0xffdc790d903f0000ULL },
    { 0x11, 0xfe9cba87a2760000ULL },
    { 0x11, 0xfe9cba87a2760000ULL },
    { 0x11, 0xfe9cba87a2760000ULL },
    { 0x12, 0xf21f494c589c0000ULL },
    { 0x12, 0xf21f494c589c0000ULL },
    { 0x12, 0xf21f494c589c0000ULL },
    { 0x13, 0x7538dcfb76180000ULL },
    { 0x13, 0x7538dcfb76180000ULL },
    { 0x13, 0x7538dcfb76180000ULL },
    { 0x13, 0x7538dcfb76180000ULL },
};
  int log = int_log2(x);
  uint64_t low = table[log][1];
  uint64_t high = table[log][0];
  return (x + low < x ) + high;
}

评论区大哥给了一个更快的

inline uint8_t digitCounts64[]{ 19, 19, 19, 19, 18, 18, 18, 17, 17, 17, 16, 16, 16, 16, 15, 15, 15, 14, 14, 14, 13, 13, 13, 13, 12, 12, 12, 11, 11, 11, 10, 10, 10,
	10, 9, 9, 9, 8, 8, 8, 7, 7, 7, 7, 6, 6, 6, 5, 5, 5, 4, 4, 4, 4, 3, 3, 3, 2, 2, 2, 1, 1, 1, 1, 1 };

inline uint64_t digitCountThresholds64[]{ 0ull, 9ull, 99ull, 999ull, 9999ull, 99999ull, 999999ull, 9999999ull, 99999999ull, 999999999ull, 9999999999ull,
	99999999999ull, 999999999999ull, 9999999999999ull, 99999999999999ull, 999999999999999ull, 9999999999999999ull, 99999999999999999ull, 999999999999999999ull,
	9999999999999999999ull };

inline uint64_t fastDigitCount(const uint64_t inputValue) {
	const uint64_t originalDigitCount{ digitCounts64[__builtin_clzll(inputValue)] };
	return originalDigitCount + (inputValue > digitCountThresholds64[originalDigitCount]);
}

另外这个大哥写了个json库很快。和glaze有一拼 https://github.com/RealTimeChris/Jsonifier/

C++26: a placeholder with no name

总算不用使用 decltype(std::ignore) _;了 c++26支持了

Inside STL: Waiting for a std::atomic<std::shared_ptr> to change, part 1 Part2

省流std::atomic<std::shared_ptr>::notify_one linux平台由于futex的问题,没有很好的实现

windows有waitonaddress

constexpr std::string的一个好处

可以写编译期测试 static_assert

constexpr std::string
decode(std::span<const unsigned char> payload)
{
  static constexpr unsigned char MASK{0x7F};
  std::string                    result{};

  int           shift{};
  unsigned char mask{MASK};
  for(unsigned char scratch{}; auto c : payload) {
    const unsigned char realChar =
      ((c & mask) << shift) | scratch;
    scratch = (c & (~mask)) >> (7 - shift);

    ++shift;
    mask >>= 1;

    result += static_cast<char>(realChar);

    if(7 == shift) {
      result += static_cast<char>(scratch);
      shift   = 0;
      mask    = MASK;
      scratch = 0;
    }
  }

  return result;
}

constexpr auto make_array(auto... values)
{
  return std::array<unsigned char, sizeof...(values)>{
    static_cast<unsigned char>(values)...};
}

void Use()
{
  constexpr auto payload{make_array(0xd3,
                                    0x74,
                                    0x1b,
                                    0xce,
                                    0x2e,
                                    0x83,
                                    0xa6,
                                    0xcd,
                                    0x29,
                                    0x88,
                                    0x5e,
                                    0xc6,
                                    0xd3,
                                    0x5d)};
  constexpr auto payload2{make_array(0xc8,
                                     0x32,
                                     0x9b,
                                     0xfd,
                                     0x66,
                                     0x81,
                                     0x86,
                                     0xab,
                                     0x55,
                                     0x08,
                                     0x44,
                                     0x45,
                                     0xa7,
                                     0xe7,
                                     0xa0,
                                     0xf4,
                                     0x1c,
                                     0x34,
                                     0x46,
                                     0x97,
                                     0xc7,
                                     0xeb,
                                     0xb4,
                                     0xfb,
                                     0x0c,
                                     0x0a,
                                     0x83,
                                     0xe6,
                                     0x74,
                                     0xb2,
                                     0x4e,
                                     0x37,
                                     0xa7,
                                     0xcb,
                                     0xd3,
                                     0xee,
                                     0x33,
                                     0x28,
                                     0x4c,
                                     0x07,
                                     0x8d,
                                     0xdf,
                                     0x6d,
                                     0x78,
                                     0x9a,
                                     0x5d,
                                     0x06,
                                     0xd1,
                                     0xd3,
                                     0xed,
                                     0x72,
                                     0x08,
                                     0xe4,
                                     0x4c,
                                     0x8f,
                                     0xcb,
                                     0x2c,
                                     0x50,
                                     0x7a,
                           ...
Read more

C++ 中文周刊 2024-12-29 第174期

Choose a tag to compare

@wanghenshui wanghenshui released this 12 Jan 16:39
c0e91ee

周刊项目地址

公众号

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

qq群 点击进入

RSS

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

本期文章由 Amnesia wyhqaq HNY 赞助 在此表示感谢

老板大气祝老板永远不死


资讯

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

十二月邮件列表

[编译器信息最新动态推荐关注hellogcc公众号 本周更新 2024-01-04 第286期](OSDT Weekly 2024-12-25 第286期 )

文章

Retrofitting spatial safety to hundreds of millions of lines of C++

google在 c++安全上的实践,主要是采用libc++ harden mode

非常推荐使用。我之前搞了个安全的介绍,感兴趣可以看一下

Structured Binding Upgrades in C++26

支持字段级别设置attr了 p0609

auto [it, inserted [[maybe_unused]] ] = map.try_emplace(key, value);

支持在条件中展开,类似c++17那个条件中赋值

if (auto [n] = f()) { ... }

while (auto [header, body] = receive_packet()) { ... }

展开绑定到tuple p1061

template <class T>
auto tie_as_tuple(T& x) {
    auto& [...xs] = x;
    return std::tie(xs...);
}

如果这个能支持,真是有点改变相关工具生态了 magic get/boost pfr可是做了好多脏活

How to Hash Objects Without Repetition: std::hash can be DRY

介绍hash combine和 tie组合的。

#include <string>
#include <tuple>
#include <cassert>
#include <cstdint>
#include <boost/functional/hash.hpp>
#include <iostream>

namespace some_lib {
    template <typename T>
    constexpr void hash_combine(size_t& seed, const T& value)
    {
        seed ^= std::hash<T>{}(value) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
    }
}

template <typename... TValues>
constexpr auto combined_hash(const TValues&... values)
{
    size_t seed{};
    (..., some_lib::hash_combine(seed, values));

    return seed;
}

struct HashableForTiedMembers
{ };

template <typename T>
constexpr bool hashing_for_tied_members = false; 

template <typename T>
concept HashableForTied = std::derived_from<T, HashableForTiedMembers> || hashing_for_tied_members<T>;

template <HashableForTied T>
struct std::hash<T>
{
    size_t operator()(const T& value) const
    {
        auto hasher = [](const auto&... args) {
            return combined_hash(args...);
        };
        return std::apply(hasher, value.tied());
    }
};

template <typename T>
concept Hashable = requires {
    { std::hash<T>{}(std::declval<T>()) } -> std::convertible_to<size_t>;
};

/////////////////////////////////////////////////////////////////////////
// Person class - opt-in through inheritance

struct Person : HashableForTiedMembers
{
    std::string first_name;
    std::string last_name;
    std::uint8_t age;

    Person(std::string first_name, std::string last_name, std::uint8_t age)
        : first_name{std::move(first_name)}
        , last_name{std::move(last_name)}
        , age{age}
    {
    }

    auto tied() const
    {
        return std::tie(first_name, last_name, age);
    }

    bool operator==(const Person& other) const
    {
        return tied() == other.tied();
    }

    auto operator<=>(const Person& other) const
    {
        return tied() == other.tied();
    }
};

static_assert(Hashable<Person>);

//////////////////////////////////////////////////////////////////////////
// AggregatePerson - opt-in through variable template specialization
struct AggregatePerson
{
    std::string first_name;
    std::string last_name;
    std::uint8_t age;

    auto tied() const
    {
        return std::tie(first_name, last_name, age);
    }
};

template <>
constexpr bool hashing_for_tied_members<AggregatePerson> = true;

int main()
{
    Person p1{"John", "Doe", 33};
    Person p2{"John", "Doe", 33};
    Person p3{"John", "Don", 44};

    assert(p1 == p2);
    assert(p2 < p3);

    assert(std::hash<Person>{}(p1) == std::hash<Person>{}(p2));
    assert(std::hash<Person>{}(p1) == std::hash<Person>{}(p3));

    AggregatePerson ap1{"John", "Doe", 30};
	AggregatePerson ap2{"John", "Doe", 30};
	AggregatePerson ap3{"John", "Dog", 30};

	assert(std::hash<AggregatePerson>{}(ap1) == std::hash<AggregatePerson>{}(ap2));
	assert(std::hash<AggregatePerson>{}(ap1) != std::hash<AggregatePerson>{}(ap3));

    return std::hash<Person>{}(p1);
}

其实他这个设计就是hash_append

    template <class HashAlgorithm>
    friend void hash_append(HashAlgorithm& h, X const& x) noexcept
    {
        //
    }

他这个写法就是把hash算法和x拆出来,x本身转pack 方便归一计算,用的tie 避免开销

combined_hash其实就是hash_append 只不过算法h不能定制

不知道读者们了不了解n3980 hash_append 不了解没关系。就是函数接口定制和这里的代码一个意思

Measuring std::unordered_map Badness

非常搞笑的场景,同样的hashmap float 做key和 int做key冲突率不同 因为值域不同。float表达式导致的

针对float做key要注意hash算法

Inside STL: The atomic shared_ptr

和shared_ptr差不多,为了原子 使用tag pointer 用特殊字段做spinlock 代码在这里

	  while (!_M_val.compare_exchange_strong(__current,
						 __current | _S_lock_bit,
						 __o,
						 memory_order_relaxed))
	    {
	      _GLIBCXX_TSAN_MUTEX_TRY_LOCK_FAILED(&_M_val);
#if __glibcxx_atomic_wait
	      __detail::__thread_relax();
#endif
	      __current = __current & ~_S_lock_bit;
	      _GLIBCXX_TSAN_MUTEX_TRY_LOCK(&_M_val);
	    }
	  _GLIBCXX_TSAN_MUTEX_LOCKED(&_M_val);
	  return reinterpret_cast<pointer>(__current);
	}

Type-safe Enum Class Bit Flags

直接贴代码

#include <bitset>
#include <ostream>
#include <type_traits>
#include <utility>

// Helper class for bitwise flag-like operations on scoped enums.
//
// This class provides a way to represent combinations of enum values without
// directly overloading operators on the enum type itself. This approach
// avoids ambiguity in the type system and allows the enum type to continue
// representing a single value, while the BitFlags can hold a combination
// of enum values.
//
// Example usage:
//
// enum class MyEnum { FlagA = 1 << 0, FlagB = 1 << 1, FlagC = 1 << 2 };
//
// BitFlags<MyEnum> flags = { MyEnum::FlagA, MyEnum::FlagC };
// flags.Unset(MyEnum::FlagA);
// if (flags.IsSet(MyEnum::FlagC)) {
//   // ...
// }
//
// flags |= MyEnum::FlagB;
// BitFlags<MyEnum> new_flags = ~flags;
template <typename T>
class BitFlags {
    using UnderlyingT = std::underlying_type_t<T>;

   public:
    constexpr BitFlags() : flags_(static_cast<UnderlyingT>(0)) {}
    constexpr explicit BitFlags(T v) : flags_(ToUnderlying(v)) {}
    constexpr BitFlags(std::initializer_list<T> vs) : BitFlags() {
        for (T v : vs) {
            flags_ |= ToUnderlying(v);
        }
    }

    // Checks if a specific flag is set.
    constexpr bool IsSet(T v) const {
        return (flags_ & ToUnderlying(v)) == ToUnderlying(v);
    }
    // Sets a single flag value.
    constexpr void Set(T v) { flags_ |= ToUnderlying(v); }
    // Unsets a single flag value.
    constexpr void Unset(T v) { flags_ &= ~ToUnderlying(v); }
    // Clears all flag values.
    constexpr void Clear() { flags_ = static_cast<UnderlyingT>(0); }

    constexpr operator bool() const {
        return flags_ != static_cast<UnderlyingT>(0);
    }

    friend constexpr BitFlags operator|(BitFlags lhs, T rhs) {
        return BitFlags(lhs.flags_ | ToUnderlying(rhs));
    }
    friend constexpr BitFlags operator|(BitFlags lhs, BitFlags rhs) {
        return BitFlags(lhs.flags_ | rhs.flags_);
    }
    friend constexpr BitFlags operator&(BitFlags lhs, T rhs) {
        return BitFlags(lhs.flags_ & ToUnderlying(rhs));
    }
    friend constexpr BitFlags operator&(BitFlags lhs, BitFlags rhs) {
        return BitFlags(lhs.flags_ & rhs.flags_);
    }
    friend constexpr BitFlags operator^(BitFlags lhs, T rhs) {
        return BitFlags(lhs.flags_ ^ ToUnderlying(rhs));
    }
    friend constexpr BitFlags operator^(BitFlags lhs, BitFlags rhs) {
        return BitFlags(lhs.flags_ ^ rhs.flags_);
    }

    friend constexpr BitFlags& operator|=(BitFlags& lhs, T rhs) {
        lhs.flags_ |= ToUnderlying(rhs);
        return lhs;
    }
    friend constexpr BitFlags& operator|=(BitFlags& lhs, BitFlags rhs) {
        lhs.flags_ |= rhs.flags_;
        return lhs;
    }
    friend constexpr BitFlags& operator&=(BitFlags& lhs, T rhs) {
        lhs.flags_ &= ToUnderlying(rhs);
        return lhs;
    }
    friend constexpr BitFlags& operator&=(BitFlags& lhs, BitFlags rhs) {
        lhs.flags_ &= rhs.flags_;
        return lhs;
    }
    friend constexpr BitFlags& operator^=(BitFlags& lhs, T rhs) {
        lhs.flags_ ^= ToUnderlying(rhs);
        return lhs;
    }
    friend constexpr BitFlags& operator^=(BitFlags& lhs, BitFlags rhs) {
        lhs.flags_ ^= rhs.flags_;
        return lhs;
    }

    friend constexpr BitFlags operator~(const BitFlags& bf) {
        return BitFlags(~bf.flags_);
    }

    friend constexpr bool operator==(const BitFlags& lhs, const BitFlags& rhs) {
        return lhs.flags_ == rhs.flags_;
    }
    friend constexpr bool operator!=(const BitFlags...
Read more