Skip to content

Latest commit

 

History

History
357 lines (250 loc) · 11 KB

178.md

File metadata and controls

357 lines (250 loc) · 11 KB
layout title
post
第178期

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

周刊项目地址

公众号

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

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

RSS

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

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


资讯

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

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

性能周刊

文章

省流 不共享不要滥用

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

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

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

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

比如

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

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

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来说,安全比较重要。即使这玩意慢三四十倍

省流 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;
}

介绍数据局部性有利的数据结构 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代码

简单介绍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. 尽可能对齐

比较常规

介绍了基本的字段大小,继承影响,对齐影响,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

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

业界还是用google highway多一些。

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

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不知火舞联动上线了,非常好玩


上一期

本期

下一期