C++ 中文周刊 2024-07-21 第164期

2024-07-30 15:25:01 浏览数 (2)

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

本期文章由 HNY lh_mouse 终盛 赞助

资讯

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

七月邮件列表 https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/#mailing2024-07

其中 3351 https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2024/p3351r0.html

是群友Mick的提案

群友发的就等于大家发的,都是机会滋道吧

编译器信息最新动态推荐关注hellogcc公众号 本周更新 2024-07-10 第262期

ThinkCell发布了他们的C 26参会报告 Trip Report: Summer ISO C Meeting in St. Louis, USA

https://www.think-cell.com/en/career/devblog/trip-report-summer-iso-cpp-meeting-in-st-louis-usa

另外发现了一个好玩的网站 https://highload.fun/tasks/ 各种大数据场景 各种优化trick都可以用。感觉适合做面试题

另外发现一个关于高频低延迟开发的一本小书

C design patterns for low-latency applications including high-frequency trading

https://arxiv.org/pdf/2309.04259

代码在这里 https://github.com/0burak/imperial_hft

这里总结一下

  • • cache warm

代码大概这样

代码语言:javascript复制
#include <benchmark/benchmark.h>
#include <vector>
#include <algorithm>

constexpr int kSize = 10000000;  
std::vector<int> data(kSize);
std::vector<int> indices(kSize);

static void BM_CacheCold(benchmark::State& state) {
  // Generate random indices
  for(auto& index : indices) {
    index = rand() % kSize;
  }
  for (auto _ : state) {
    int sum = 0;
    // Access data in random order
    for (int i = 0; i < kSize;   i) {
      benchmark::DoNotOptimize(sum  = data[indices[i]]);
    }
    benchmark::ClobberMemory();
  }
}

static void BM_CacheWarm(benchmark::State& state) {
  // Warm cache by accessing data in sequential order
  int sum_warm = 0;
  for (int i = 0; i < kSize;   i) {
    benchmark::DoNotOptimize(sum_warm  = data[i]);
  }
  benchmark::ClobberMemory();
 
  // Run the benchmark
  for (auto _ : state) {
    int sum = 0;
    // Access data in sequential order again
    for (int i = 0; i < kSize;   i) {
      benchmark::DoNotOptimize(sum  = data[i]);
    }
    benchmark::ClobberMemory();
  }
}

测试数据直接快十倍,之前也讲过类似的场景

  • • 利用模版和constexpr 这个就不多说了
  • • 循环展开 这个也不说了
  • • 区分快慢路径
  • • 减少错误分支
  • • prefetch

一个例子

代码语言:javascript复制
#include <benchmark/benchmark.h>
#include <vector>

// Function without __builtin_prefetch
void NoPrefetch(benchmark::State& state) {
  // Create a large vector to iterate over
  std::vector<int> data(state.range(0), 1);
  for (auto _ : state) {
    long sum = 0;
    for (const auto& i : data) {
      sum  = i;
    }
    // Prevent compiler optimization to discard the sum
    benchmark::DoNotOptimize(sum);
  }
}
BENCHMARK(NoPrefetch)->Arg(1<<20); // Run with 1MB of data (2^20 integers)


// Function with __builtin_prefetch
void WithPrefetch(benchmark::State& state) {
  // Create a large vector to iterate over
  std::vector<int> data(state.range(0), 1);
  for (auto _ : state) {
    long sum = 0;
    int prefetch_distance = 10;
    for (int i = 0; i < data.size(); i  ) {
      if (i   prefetch_distance < data.size()) {
      __builtin_prefetch(&data[i   prefetch_distance], 0, 3);
      }
      sum  = data[i];
    }
    // Prevent compiler optimization to discard the sum
    benchmark::DoNotOptimize(sum);
  }
}
BENCHMARK(WithPrefetch)->Arg(1<<20); // Run with 1MB of data (2^20 integers)

BENCHMARK_MAIN();

论文中快30%,当然编译器可以向量化的吧,不用手动展开吧

  • • 有符号无符号整数比较,慢,避免
  • • float double混用慢,避免
  • • SSE加速
  • • mutex替换成atomic (这个还是取决于应用场景)
  • • bypass

还有其他模块介绍就不谈了,比较偏HFT

文章

C Error Handling Strategies – Benchmarks and Performance

浅析Cpp 错误处理

https://johnfarrier.com/c-error-handling-strategies-benchmarks-and-performance/

https://zhuanlan.zhihu.com/p/707442177

最近不约而同有两个关于错误处理的压测

第一个文章没有体验出正确路径错误路径不同压力的表现,只测了错误路径,因此没啥代表价值。只是浅显的说了异常代价大,谁还不知道这个

问题是什么情况用异常合适?异常不是你期待的东西,如果你的错误必须处理,那就不叫异常

另外第二篇文章是群友写的,给了个50%失败错误路径的测试,结果符合直觉

结论: 异常在happy path出现的路径下收益高(错误出现非常少)

当然已经有提案说过这个事情 https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2019/p1886r0.html

我相信大多数人都没看过这个,是的我之前也没有看过

When __cxa_pure_virtual is just a different flavor of SEGFAULT https://blog.the-pans.com/pure_virtual/

有时候可能会遇到这种打印挂掉pure virtual method called

一个简单的复现代码

代码语言:javascript复制
class Base {
public:
    Base() {fn();} // thinking it would be calling the Derived's `fn()`
    // the same happens with dtor
    // virtual ~Base() {fn();}
    virtual void fn() = 0;
};

class Derived : public Base{
public:
    void fn() {}
};

int main() {
    Derived d;
    return 0;
}

简单来说基类构造的时候子类还没构造,fn没绑定,还是纯虚函数,就会挂

不要这么写,不要在构造函数中调用虚函数

What’s the point of std::monostate? You can’t do anything with it!

https://devblogs.microsoft.com/oldnewthing/20240708-00/?p=109959

就是空类型,帮助挡刀的

比如这个场景

代码语言:javascript复制
struct Widget {
    // no default constructor
    Widget(std::string const& id);
};

struct PortListener {
    // default constructor has unwanted side effects
    PortListener(int port = 80);
};

std::variant<Widget, PortListener> thingie; // can't do this

我们想让Widget当第一个,但是Widget没有默认构造,PortListener放第一个又破坏可读性,对应关系乱了

怎么办,monostate出场

代码语言:javascript复制
std::variant<std::monostate, Widget, PortListener> thingie;

帮Widget挡一下编译问题

顺便一提,monostate的hash

代码语言:javascript复制
  result_type operator()(const argument_type&) const {
    return 66740831; // return a fundamentally attractive random value.
  }

开源项目介绍

  • • https://github.com/lhmouse/asteria 一个脚本语言,可嵌入,长期找人,希望胖友们帮帮忙,也可以加群753302367和作者对线
  • • https://github.com/Psteven5/CFLAG.h/tree/main 一个类似GFLAGS的库,但是功能非常简单,口述一下原理

示例代码

代码语言:javascript复制
int main(int argc, char **argv) {
  char const *i = NULL, *o = NULL;
  bool h = false;
  CFLAGS(i, o, h);
  printf("i=%s, o=%s, h=%sn", i, o, h ? "true" : "false");
}
// ./main -i=main.js -h -o trash

主要原理就是利用c11的_Generic

介绍一下generic用法

代码语言:javascript复制
#include <math.h>
#include <stdio.h>
 
// Possible implementation of the tgmath.h macro cbrt
#define cbrt(X) _Generic((X), 
              long double: cbrtl, 
                  default: cbrt,  
                    float: cbrtf  
              )(X)
 
int main(void) {
    double x = 8.0;
    const float y = 3.375;
    printf("cbrt(8.0) = %fn", cbrt(x)); // selects the default cbrt
    printf("cbrtf(3.375) = %fn", cbrt(y)); // converts const float to float,
                                            // then selects cbrtf
}

看懂了吧,_Generic根据输入能生成自定义的语句,上面的例子根据X生成对应的函数替换

能换函数,那肯定也能换字符串,这个关键字能玩的很花哨

回到我们这个flags,和Gflags差不多,我们怎么实现?

我们考虑一个最简单的场景 CFLAGS(i),应该展开成 解析arg遍历匹配字符串i并讲对应的值赋值给i,这个赋值得通过格式化字符串复制

遍历arg好实现,通过argc argv遍历就行,i字符串话也简单 #,把argv的值赋值, sscanf,格式化字符串哪里来?generic

大概思路已经有了,怎么实现大家看代码吧

0 人点赞