Skip to content

Releases: wanghenshui/cppweeklynews

C++ 中文周刊 2024-08-31 第167期

01 Sep 05:51
Compare
Choose a tag to compare

本期文章由 HNY 赞助


资讯

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

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

文章

C++20 Modules 在阿里云的大规模应用

看一乐

MySQL 编译(打包

看一乐 感谢恒星投稿

Visualizing boost::unordered_map in GDB, with pretty-printer customization points

给boost unordered实现gdb pretty print

gdb使用pretty print很简单

第一步

(gdb) set print pretty on

第二步,如果你的脚本在二进制的section中

(gdb) add-auto-load-safe-path path/to/executable

如果没有,有脚本,可以加载脚本

(gdb) source path/to/boost/libs/unordered/extra/boost_unordered_printers.py

其实脚本内容和放进二进制section内容是一样的,怎么放进二进制?可以学习这个 https://github.com/boostorg/outcome/blob/master/include/boost/outcome/outcome_gdb.h

我相信大部分读者是第一次知道gdb printer可以放到二进制里的

接下来是如何实现gdb printer

很简单,接口就这样

class BoostUnorderedFcaPrinter:
    def __init__(self, val):
        self.val = val
    
    def to_string(self):
        return f"This is a {self.val.type}"

目标,实现to_string

注册也非常简单

def boost_unordered_build_pretty_printer():
    pp = gdb.printing.RegexpCollectionPrettyPrinter("boost_unordered")
    add_template_printer = lambda name, printer: pp.add_printer(name, f"^{name}<.*>$", printer)

    add_template_printer("boost::unordered::unordered_map", BoostUnorderedFcaPrinter)
    add_template_printer("boost::unordered::unordered_multimap", BoostUnorderedFcaPrinter)
    add_template_printer("boost::unordered::unordered_set", BoostUnorderedFcaPrinter)
    add_template_printer("boost::unordered::unordered_multiset", BoostUnorderedFcaPrinter)
    return pp

gdb.printing.register_pretty_printer(gdb.current_objfile(), boost_unordered_build_pretty_printer())

继续,咱们展开成员

def maybe_unwrap_foa_element(e):
    element_type = "boost::unordered::detail::foa::element_type<"
    if f"{e.type.strip_typedefs()}".startswith(element_type):
        return e["p"]
    else:
        return e

简单吧,现在你学会了gdb.Value.type.strip_typedefs

咱们进化一下

class BoostUnorderedFcaPrinter:
    def __init__(self, val):
        self.val = val
        self.name = f"{self.val.type.strip_typedefs()}".split("<")[0]
        self.name = self.name.replace("boost::unordered::", "boost::")
        self.is_map = self.name.endswith("map")

    def to_string(self):
        size = self.val["table_"]["size_"]
        return f"{self.name} with {size} elements"

这样打印

(gdb) print my_unordered_map
$1 = boost::unordered_map with 3 elements
(gdb) print my_unordered_multiset
$2 = boost::unordered_multiset with 5 elements

考虑遍历成员

def display_hint(self):
    return "map"

def children(self):
    def generator():
        # ...
        while condition:
            value = # ...
            if self.is_map:
                first = value["first"]
                second = value["second"]
                yield "", first
                yield "", second
            else:
                yield "", count
                yield "", value
    return generator()

后面就不展开了

完整代码

python api可以看这里

gdb python的玩法还是非常多的

PS: 吴乎提问: 放进二进制section这个,strip时不知道是跟着debug符号走,还是跟着binary走

笔者查了一下,使用的section debug_gdb_scripts也是debug symbol,strip会被删,
这种建议先strip后objcopy把debug_gdb_scripts搞回来 详情点击这个SO
gdb section可以看这里

SIMD Matters

图形生成算法使用simd性能提升显著。代码就不贴了

Honey, I shrunk {fmt}: bringing binary size to 14k and ditching the C++ runtime

介绍了fmtlib在减小二进制上的探索,比如查表优化

auto do_count_digits(uint32_t n) -> int {
// An optimization by Kendall Willets from https://bit.ly/3uOIQrB.
// This increments the upper 32 bits (log10(T) - 1) when >= T is added.
#  define FMT_INC(T) (((sizeof(#T) - 1ull) << 32) - T)
  static constexpr uint64_t table[] = {
      FMT_INC(0),          FMT_INC(0),          FMT_INC(0),           // 8
      FMT_INC(10),         FMT_INC(10),         FMT_INC(10),          // 64
      FMT_INC(100),        FMT_INC(100),        FMT_INC(100),         // 512
      FMT_INC(1000),       FMT_INC(1000),       FMT_INC(1000),        // 4096
      FMT_INC(10000),      FMT_INC(10000),      FMT_INC(10000),       // 32k
      FMT_INC(100000),     FMT_INC(100000),     FMT_INC(100000),      // 256k
      FMT_INC(1000000),    FMT_INC(1000000),    FMT_INC(1000000),     // 2048k
      FMT_INC(10000000),   FMT_INC(10000000),   FMT_INC(10000000),    // 16M
      FMT_INC(100000000),  FMT_INC(100000000),  FMT_INC(100000000),   // 128M
      FMT_INC(1000000000), FMT_INC(1000000000), FMT_INC(1000000000),  // 1024M
      FMT_INC(1000000000), FMT_INC(1000000000)                        // 4B
  };
  auto inc = table[__builtin_clz(n | 1) ^ 31];
  return static_cast<int>((n + inc) >> 32);
}


template <typename T> constexpr auto count_digits_fallback(T n) -> int {
  int count = 1;
  for (;;) {
    // Integer division is slow so do it for a group of four digits instead
    // of for every digit. The idea comes from the talk by Alexandrescu
    // "Three Optimization Tips for C++". See speed-test for a comparison.
    if (n < 10) return count;
    if (n < 100) return count + 1;
    if (n < 1000) return count + 2;
    if (n < 10000) return count + 3;
    n /= 10000u;
    count += 4;
  }
}

这两种用法,查表就是要多一堆符号的,如果业务要小二进制,就不用查表

另外就是 -nodefaultlibs -fno-exceptions 这种场景下 new delete基本也得去掉 最后减小到14K,如果去除main6k fmt整体小于10k

Faster random integer generation with batching

批量生成随机数相当于给一堆数打散,第一反应就是shuffle

void shuffle(mytype *storage, uint64_t size) {
  for (uint64_t i = size; i > 1; i--) {
    uint64_t nextpos = random(i); // random value in [0,i)
    std::swap(storage[i - 1], storage[nextpos]);
  }
}

显然这个shuffle中的random是瓶颈,我们常规的实现就是%i,有没有更快的做法?

uint64_t random_bounded(uint64_t range) {
  __uint128_t random64bit, multiresult;
  uint64_t leftover;
  uint64_t threshold;
  random64bit = rng(); // 64-bit random integer
  multiresult = random64bit * range;
  leftover = (uint64_t)multiresult;
  if (leftover < range) {
    threshold = -range % range;
    while (leftover < threshold) {
      random64bit = rng();
      multiresult = random64bit * range;
      leftover = (uint64_t)multiresult;
    }
  }
  return (uint64_t)(multiresult >> 64); // [0, range)
}
/ Fisher-Yates shuffle
void shuffle(uint64_t *storage, uint64_t size, uint64_t (*rng)(void)) {
    uint64_t i;
    for (i = size; i > 1; i--) {
        uint64_t nextpos = random_bounded(i, rng);
        uint64_t tmp = storage[i - 1];
        uint64_t val = storage[nextpos];
        storage[i - 1] = val;
        storage[nextpos] = tmp;
    }
}

这实际上也是gcc的实现,我们能不能拆成批量shuffle?

考虑一个场景,你需要多个shuffle,显然每次都执行random_bound代价大

能不能一个random_bound把多个shuffle一起计算?当然可以

然后多个shuffle组合成一个shuffle就是修改index的问题了是不是?

比如拆成两个

// product_bound can be any integer >= range1*range2
// it may be updated to become range1*range2
std::pair<uint64_t, uint64_t> 
 random_bounded_2(uint64_t range1, uint64_t range2,
                   uint64_t &product_bound) {
  __uint128_t random64bit, multiresult;
  uint64_t leftover;
  uint64_t threshold;
  random64bit = rng(); // 64-bit random integer
  multiresult = random64bit * range1;
  leftover = (uint64_t)multiresult;
  uint64_t result1 = (uint64_t)(multiresult >> 64); // [0, range1)
  multiresult = leftover * range2;
  leftover = (uint64_t)multiresult;
  uint64_t result2 = (uint64_t)(multiresult >> 64); // [0, range2)
  if (leftover < product_bound) {
    product_bound = range2 * range1;
    if (leftover < product_bound) {
      threshold = -product_bound % product_bound;
      while (leftover < threshold) {
        random64bit = rng();
        multiresult = random64bit * range1;
        leftover = (uint64_t)multiresult;
        result1 = (uint64_t)(multiresult >> 64); // [0, range1)
        multiresult = leftover * range2;
        leftover = (uint64_t)multiresult;
        result2 = (uint64_t)(multiresult >> 64); // [0, range2)
      }
    }
  }
  return std::make_pair(result1, result2);
}
void shuffle_2(mytype *storage, uint64_t size) {
  uint64_t i = size;
  for (; i > 1 << 30; i--) {
    uint64_t index = random_bounded(i, g); 
    // index is in [0, i-1] 
    std::swap(storage[i - 1], storage[index]);
  }

  // Batches of 2 for sizes up to 2^30 elements
  uint64_t product_bound = i * (i - 1);
  for (; i > 1; i -= 2) {
    auto [index1, index2] = random_bounded_2(i, i - 1, 
         product_bound, g);
    // index1 is in [0, i-1]
    // index2 is in [0, i-2]
    std::swap(storage[i - 1], storage[index1]);
    std::swap(storage[i - 2], storage[index2]);
  }
}

测试linux gcc快30%

Parsing tiny and very large floating-point values: a programming-language comparison

处理无限大无限小,各种语言的差别

python

>>> float("1e-1000")
0.0
>>> float("1e1000")
inf

golang

package main

import (
    "fmt"
    "strconv"
)

func main() {
    f, err := strconv.ParseFloat("1e-1000"...
Read more

C++ 中文周刊 2024-08-18 第166期

18 Aug 16:48
35bf1bd
Compare
Choose a tag to compare

本期文章由 HNY 赞助

资讯

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

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

文章

彻底理解 C++ ABI

今天群聊提到了一个场景,unique_ptr传值加move 并不能完美优化掉,看代码

void consume(int* ptr);

void foo(int* ptr) {
    consume(ptr);
}

/*
foo:
        jmp     consume@PLT
*/
void consume(unique_ptr<int> ptr);

void foo(unique_ptr<int> ptr) {
    consume(std::move(ptr));  //
}

/*
foo(std::unique_ptr<int, std::default_delete<int> >):
        push    rbx
        sub     rsp, 16
        mov     rax, QWORD PTR [rdi]
        mov     QWORD PTR [rdi], 0
        lea     rdi, [rsp+8]
        mov     QWORD PTR [rsp+8], rax
        call    consume(std::unique_ptr<int, std::default_delete<int> >)
        mov     rdi, QWORD PTR [rsp+8]
        test    rdi, rdi
        je      .L1
        mov     esi, 4
        call    operator delete(void*, unsigned long)
.L1:
        add     rsp, 16
        pop     rbx
        ret
        mov     rbx, rax
        jmp     .L3
foo(std::unique_ptr<int, std::default_delete<int> >) [clone .cold]:
*/

主要原因是 函数实参在 caller 方析构, unique_ptr没有彻底优化掉。感觉可以优化掉

改成传引用,传&&甚至改成not_null都能省掉

void consume(not_null<int> ptr); //std::unique_ptr<int> && 也可以

void foo(not_null<int> ptr) {
    consume(ptr);  //
}
/*
foo(not_null<int>):
        jmp     consume(not_null<int>)
*/

感谢anms nugine ni fvs zwuis 讨论

godbolt https://godbolt.org/z/fbqEa4M1r

noexcept Can (Sometimes) Help (or Hurt) Performance

使用noexcept需要保证没有异常,否则生成的代码代价更高

通常来说noexcept是给move用的

另外有一个搞笑的场景

noexcept affects libstdc++’s unordered_set

libstdc++的 unordered set 对于noexcept限定 针对hash函数有特化

如果hash函数是noexcept 认为函数计算很轻,不额外保存key hash,否则会缓存key hash加速

这就导致一个尴尬的场景,对于int,这种优化是对的,对于string hash接口使用noexcept会弄巧成拙速度更慢

标准库对于noexcept限定应该给用户端保留余地,不要影响效果,如果影响,最好给出api约定,比如transparent compare

这种莫名其妙的限制很坑,可能喜欢秀一下用noexcept正好掉坑里

libstdc++ 说明 在这里

Temporarily dropping a lock: The anti-lock pattern

异步lock暂时解锁的组件。代码

template<typename Mutex>
struct anti_lock
{
    anti_lock() = default;

    explicit anti_lock(Mutex& mutex)
    : m_mutex(std::addressof(mutex)) {
        if (m_mutex) m_mutex->unlock();
    }

private:
    struct anti_lock_deleter {
        void operator()(Mutex* mutex) { mutex->lock(); }
    };

    std::unique_ptr<Mutex, anti_lock_deleter> m_mutex;
};

winrt::fire_and_forget DoSomething()
{
    auto guard = std::lock_guard(m_mutex);

    step1();

    // All co_awaits must be under an anti-lock.
    int cost = [&] {
        auto anti_guard = anti_lock(m_mutex);
        return co_await GetCostAsync();
    }();

    step2(cost);
}

Surprisingly Slow NaNs

https://voithos.io/articles/surprisingly-slow-nans/

代码存在0除0导致NAN NAN导致性能下降

规避?isnan判定 DCHECK

### What's so hard about class types as non-type template parameters?

NTTP 支持类实例的困难原因 无法判定相等

有operator template()提案和反射提案的加持下可能有解

Reflection-based JSON in C++ at Gigabytes per Second

反射给普通库带来压倒性序列化速度,十倍以上!使用的是P2996实现

反射快来吧

互动环节

上周熬夜看了街霸6比赛 直接给我看的不困了,尤其是肯打aki那场,看的我心率110,真刺激

不过熬夜的后果就是一周都缓不过来。睡眠问题非常大,累计起来了,石油杯这个比赛作息太抽象了

时间真快啊,转眼夏天就过去了我靠,感觉啥也没干

东西太多根本看不过来,时间真是不够用

上一期

C++ 中文周刊 2024-07-27 第165期

02 Aug 08:59
1d83217
Compare
Choose a tag to compare

本期文章由 Amniesia HNY Damon 赞助

最近的热门是windows蓝屏事件了,其实国内外安全都有关系户

本期内容不多


资讯

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

编译器信息最新动态推荐关注hellogcc公众号 本周更新 264期

文章

Safer code in C++ with lifetime bounds

llvm和msvc支持生命周期检查,返回string_view有概率悬空,用错

std::string_view my_get_host(std::string_view url_string) {
  auto url = ada::parse(url_string).value();
  return url.get_host();
}

比如这种用法明显就是错的,加上编译检查能抓出来

#ifndef __has_cpp_attribute
    #define ada_lifetime_bound
#elif __has_cpp_attribute(msvc::lifetimebound)
    #define ada_lifetime_bound [[msvc::lifetimebound]]
#elif __has_cpp_attribute(clang::lifetimebound)
    #define ada_lifetime_bound [[clang::lifetimebound]]
#elif __has_cpp_attribute(lifetimebound)
    #define ada_lifetime_bound [[lifetimebound]]
#else
    #define ada_lifetime_bound
#endif

...

std::string_view get_host() const noexcept ada_lifetime_bound;

编译报错

fun.cpp:8:10: warning: address of stack memory associated with local variable 'url' returned [-Wreturn-stack-address]
    8 |   return url.get_host();

想要了解可以看这里 https://clang.llvm.org/docs/AttributeReference.html#lifetimebound

strlcpy and how CPUs can defy common sense strlcpy and how CPUs can defy common sense

strlcpy 实现openbsd和glibc实现不同,openbsd是这样的

size_t strlcpy(char *dst, const char *src, size_t dsize)
{
    const char *osrc = src;
    size_t nleft = dsize;

    if (nleft != 0) while (--nleft != 0) { /* Copy as many bytes as will fit. */
        if ((*dst++ = *src++) == '\0')
            break;
    }

    if (nleft == 0) { /* Not enough room in dst, add NUL and traverse rest of src. */
        if (dsize != 0) *dst = '\0'; /* NUL-terminate dst */
        while (*src++) ;
    }

    return(src - osrc - 1);	/* count does not include NUL */
}

能看到是一边复制一边移动的,没有提前算出src边界,而glibc是用strlen先计算src长度的,相当于重复计算了

所以openbsd版本应该比glibc版本快是不是?并不

考虑到strlen和memcpy有可能优化,咱们手写一个版本

size_t bespoke_strlcpy(char *dst, const char *src, size_t size)
{
    size_t len = 0;
    for (; src[len] != '\0'; ++len) {} // strlen() loop

    if (size > 0) {
        size_t to_copy = len < size ? len : size - 1;
        for (size_t i = 0; i < to_copy; ++i) // memcpy() loop
            dst[i] = src[i];
        dst[to_copy] = '\0';
    }
    return len;
}

编译使用 -fno-builtin避免strlen memcpy优化

这个也比openbsd快

实际上没有长度信息 每次都要判断\0,严重影响优化,循环出现依赖,没法彻底优化

What's so hard about constexpr allocation?

讨论constexpr vector难做的原因,先从unique_ptr开始讨论,constexpr导致相关的传递语义发生变化,不好优化

考虑引入新关键字propconst 标记常量传递 讨论的还是比较有深度的,感兴趣的可以读一下

Does C++ allow template specialization by concepts?

用require实现函数偏特化

template <typename T>
void clear(T & t);


template <typename T>
concept not_string =
!std::is_same_v<T, std::string>;


template <>
void clear(std::string & t) {
  t.clear();
}


template <class T>
void clear(T& container) requires not_string<T> {
  for(auto& i : container) {
    i = typename T::value_type{};
  }
}

看一乐

Scan HTML even faster with SIMD instructions (C++ and C#)

实现特殊版本find_first_of 向量化。代码不贴了,感兴趣的看一下

开源项目介绍

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

互动环节

看了死侍金刚狼 还可以。现在漫威太垃圾了,这还算能看的

实际剧情和银河护卫队差不多,不能细想反派,看个乐呵

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

02 Aug 08:58
Compare
Choose a tag to compare

本期文章由 HNY lh_mouse 终盛 赞助


资讯

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

七月邮件列表

其中 3351是群友Mick的提案

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

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

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

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

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

C++ design patterns for low-latency applications including high-frequency tradin

代码在这里

这里总结一下

  • cache warm

代码大概这样

#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

一个例子

#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 错误处理

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

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

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

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

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

当然已经有提案说过这个事情

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

这个话题可以展开讲一下,这里标记一个TODO

When __cxa_pure_virtual is just a different flavor of SEGFAULT

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

一个简单的复现代码

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!

就是空类型,帮助挡刀的

比如这个场景

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出场

std::variant<std::monostate, Widget, PortListener> thingie;

帮Widget挡一下编译问题

顺便一提,monostate的hash

  result_type operator()(const argument_type&) const {
    return 66740831; // return a fundamentally attractive random value.
  }

开源项目介绍

示例代码

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=%s\n", i, o, h ? "true" : "false");
}
// ./main -i=main.js -h -o trash

主要原理就是利用c11的_Generic

介绍一下generic用法

#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) = %f\n", cbrt(x)); // selects the default cbrt
    printf("cbrtf(3.375) = %f\n", 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

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

互动环节

周末看了抓娃娃,和西虹市首富差不多,结尾马马虎虎。还算好笑

好久没去电影院的椅子全变成带按摩的了,离谱,被赚了20


C++ 中文周刊 164期补充

昨天更新关于HFT的内容有误

  • cache warm 效果有限,加热icache缺乏其他优化验证修复,比如pgo,比如调大tlb。当然cache warm对于可以观测数据集预估业务的场景来说,简单粗暴,不过对于优化而言,很难说问题的根因在哪里,PGO应该是最直观的,cache warm给人一种野路子歪打正着的感觉,需要进一步分析。对于不可预估后端场景,cache warm就相当于CPU做无用功了,一定要测试,测试,测试

  • 其他例子,比如prefetch等,例子粗糙,缺少系统视角,如果缺乏这个知识需要科普,看这个小册子,反而可能造成误导

需要系统了解可以看现代cpu性能分析与优化 有中文版本

英文版 https://book.easyperf.net/perf_book

中文版本可能比较旧,但对于科普系统学习知识也足够,京东77应该是涨价了,我买的时候是50

公开课可以学一下mit 6.172 b站有视频 ppt可以这里下载 https://ocw.mit.edu/courses/6-172-performance-engineering-of-software-systems-fall-2018/

实际上优化相关知识广,碎,杂,需要系统整体视角

本文感谢崔博武 Anien 指正

C++ 中文周刊 2024-07-06 第163期

02 Aug 08:57
Compare
Choose a tag to compare

本期文章由 HMY lhmouse赞助


资讯

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

最近陆陆续续又有很多c++26 St Louis参会报告出来,这里列一下

Mateusz Pusz

Arthur O’Dwyer

草药老师

reddit网友

另外上周也介绍了mick的报告

另外Ykiko也写了一个 St. Louis WG21 会议回顾

另外安全方面,openssh重大漏洞

OpenSSH 鉴权超时终止会话时信号竞态条件漏洞,可远程攻击,可拿root shell

2020年10月到24年5月之间的版本全部中招,请及时打补丁

本台记者lhmouse报道

openssh cve问题回溯

我们在 OpenSSH 的服务器(sshd)中发现了一个漏洞(信号处理器竞赛条件):如果客户端没有在 LoginGraceTime 秒(默认为 120 秒,旧版本为 600 秒)内进行身份验证,那么 sshd 的 SIGALRM 处理器就会被异步调用,但这个信号处理器会调用各种非异步信号安全的函数(例如 syslog())。这种竞赛条件会影响默认配置下的 sshd。

经调查,我们发现该漏洞实际上是 CVE-2006-5051 的回归("4.4 之前 OpenSSH 中的信号处理器竞赛条件允许远程攻击者导致拒绝服务(崩溃),并可能执行任意代码"),该漏洞由 Mark Dowd 于 2006 年报告。

这一回归是在 2020 年 10 月(OpenSSH 8.5p1)的 752250c 号提交("修订 OpenSSH 的日志基础架构")中引入的,该提交意外删除了 sigdie() 中的 "#ifdef DO_LOG_SAFE_IN_SIGHAND",而 sigdie() 是 sshd 的 SIGALRM 处理程序直接调用的函数。换句话说

如果未针对 CVE-2006-5051 进行回传修补,或未针对 CVE-2008-4109 进行修补(CVE-2008-4109 是针对 CVE-2006-5051 的不正确修补),OpenSSH < 4.4p1 就容易受到此信号处理器竞赛条件的影响;
4.4p1 <= OpenSSH < 8.5p1 不会受到此信号处理器竞赛条件的影响(因为 CVE-2006-5051 补丁在 sigdie() 中添加的 "#ifdef DO_LOG_SAFE_IN_SIGHAND "将此不安全函数转换为安全的 _exit(1) 调用);
8.5p1 <= OpenSSH < 9.8p1 会再次出现这种信号处理竞赛条件(因为 "#ifdef DO_LOG_SAFE_IN_SIGHAND" 被意外地从 sigdie() 中删除了)。

鉴定为和之前xz 被植入木马一样的问题

开源社区并不是免费的,得盯着相关更新了

(安全的同事应该是在关注的。不过这些年降本增效第一个裁的就是安全吧)

文章

Can you change state in a const function in C++? Why? How?

const_cast

Cooperative Interruption of a Thread in C++20

介绍stop_token,其实就是塞了个状态回调,规范化了一下 直接看代码

#include <chrono>
#include <iostream>
#include <thread>
#include <vector>

using namespace::std::literals;

auto func = [](std::stop_token stoken) {                             // (1)
        int counter{0};
        auto thread_id = std::this_thread::get_id();
        std::stop_callback callBack(stoken, [&counter, thread_id] {  // (2)
            std::cout << "Thread id: " << thread_id 
                      << "; counter: " << counter << '\n';
        });
        while (counter < 10) {
            std::this_thread::sleep_for(0.2s);
            ++counter;
        }
    };

int main() {
    std::vector<std::jthread> vecThreads(10);
    for(auto& thr: vecThreads) thr = std::jthread(func);
  
    std::this_thread::sleep_for(1s);                              // (3)    
    for(auto& thr: vecThreads) thr.request_stop();                // (4)
}

A 16-byte std::function implementation.

直接贴代码吧

template<typename T>
struct sfunc;

template<typename R, typename ...Args>
struct sfunc<R(Args...)> {
    struct lambda_handler_result {
        void* funcs[3];
    };

    enum class tag {
        free,
        copy,
        call 
    };

    lambda_handler_result (*lambda_handler)(void*, void**) {nullptr};
    void* lambda {nullptr};

    template<typename F>
    sfunc(F f) { *this = f;}
    sfunc() {}
    sfunc(const sfunc& f) { *this = f; }
    sfunc(sfunc&& f) { *this = f; }

    sfunc& operator = (sfunc&& f) {
        if(&f == this){
            return *this;
        }
        lambda_handler = f.lambda_handler;
        lambda = f.lambda;
        f.lambda_handler = nullptr;
        f.lambda = nullptr;
        return *this;
    }

    void free_lambda() {
        if(lambda_handler) {
            auto ff {lambda_handler(lambda, nullptr).funcs[(int)tag::free]};
            if(ff){
                ((void(*)(void*))ff)(lambda); 
            }
        }
        lambda = nullptr;
    }

    sfunc& operator = (const sfunc& f) {
        if(&f == this) {
            return *this;
        }
        free_lambda();
        lambda_handler = f.lambda_handler;
        if(f.lambda) {
            auto ff {lambda_handler(lambda, nullptr).funcs[(int)tag::copy]};
            if(ff) {
                ((void(*)(void*, void**))ff)(f.lambda, &lambda); 
            } else { 
                lambda = f.lambda;
            }
        }
        return *this;
    }

    template<typename ...>
    struct is_function_pointer;

    template<typename T>
    struct is_function_pointer<T> {
        static constexpr bool value {false};
    };

    template<typename T, typename ...Ts>
    struct is_function_pointer<T(*)(Ts...)> {
        static constexpr bool value {true};
    };

    template<typename F>
    auto operator = (F f) {
        if constexpr(is_function_pointer<F>::value == true) {
            free_lambda();
            lambda = (void*)f;
            lambda_handler = [](void* l, void**) {
                return lambda_handler_result{/* 两个括号jekyll报错*/{nullptr, nullptr, (void*)+[](void* l, Args... args) {
                    auto& f {*(F)l};
                    return f(forward<Args>(args)...);
                }}};
            };
        } else {
            free_lambda();
            lambda = {new F{f}};
            lambda_handler = [](void* d, void** v) {
                return lambda_handler_result{/* 两个括号jekyll报错*/{(void*)[](void*d){ delete (F*)d;},
                                          (void*)[](void*d, void** v){ *v = new F{*((F*)d)};},
                                          (void*)[](void* l, Args... args)
                                          {
                                              auto& f {*(F*)l};
                                              return f(forward<Args>(args)...);
                                          }}};
            };
        }
    }

    inline R operator()(Args... args) {
        return ((R(*)(void*, Args...))lambda_handler(nullptr, nullptr).funcs[(int)tag::call])(lambda, forward<Args>(args)...);
    }

    ~sfunc() { free_lambda(); }
};

没SSO

A Type for Overload Set

函数没有类型,直接赋值是不可以的

void f(int);
void f(int, int);
// `f` is an overload set with 2 members

auto g = f;  // error! cannot deduce the type of `g`

// This is because:
using FF = decltype(f);  // error! overload set has not type
std::invocable<int> auto g = f;  // error! cannot deduce the type of `g`

// bind invoke也不行,他们要接受对象

而lambda有类型,即使这个类型看不到,那么就可以封装一层

#include <cassert>
#include <utility>

int g(int x) { return x + 1; }
int g(int x, int y) { assert(false); }

class MyClass {
public:
    int f(int x) { return x + 1; }
    int f(int x, int y) { assert(false); }
};

#define OVERLOAD(...) [&](auto &&... args) -> decltype(auto) { \
    return __VA_ARGS__(std::forward<decltype(args)>(args)...); \
}

int main(int argc, char *argv[]) {
    assert(OVERLOAD(g)(5) == 6);
    MyClass obj;
    assert(OVERLOAD(obj.f)(5) == 6);
	return 0;
}

godbolt

P3312就是这盘饺子

Compile-time JSON deserialization in C++

没看懂

Beating NumPy's matrix multiplication in 150 lines of C code

代码在这 https://github.com/salykova/matmul.c

感觉不是很靠谱,影响因素太多了

视频

C++ Weekly - Ep 435 - Easy GPU Programming With AdaptiveCpp (68x Faster!)

介绍opensycl的

自己看吧,我不是业内,不了解

Björn Fahller: Cache friendly data + functional + ranges = ❤️ https://www.youtube.com/watch?v=3Rk-zSzloL4&ab_channel=SwedenCpp

他这个SOA库还挺有意思的

开源项目介绍

互动环节

团建了一波,定的酒店露天自助,人均四百,吃出了80的感觉,亏麻

酒店内的餐饮服务还是太坑

C++ 中文周刊 2024-06-30 第162期

02 Aug 08:56
d071aaf
Compare
Choose a tag to compare

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

本期文章由 HMY lhmouse赞助


资讯

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

最近c++26还在开会,前方记者Mick有报道

后续有其他报道咱们也会转发一下

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

文章

Member ordering and binary sizes

就是字段排序可能存在空洞 导致padding填充

比如

class Example1 {
public:
    double a;// = 4.2;   // 8 bytes
    int b;// = 1;      // 4 bytes
    float c;    // 4 bytes
    char d;     // 1 byte
    bool e;     // 1 byte
    bool f;     // 1 byte
    // Assuming typical alignment, 'a' (8 bytes) should be first,
    // followed by 'b' and 'c' (both 4 bytes), and then 'd' and 'e' (1 byte each).
};

static_assert(sizeof(Example1) == 24);
struct Example2 {
    int b;//=1;      // 4 bytes
    char d;     // 1 byte
    float c;    // 4 bytes
    bool e;     // 1 byte
    double a;// = 4.2;   // 8 bytes
    bool f;
};

static_assert(sizeof(Example2) == 32);

popcnt也能向量化?

看个乐

UB or not UB: How gcc and clang handle statically known undefined behaviour

省流 gcc遇到UB倾向于生成ud2 崩溃,clang遇到UB倾向于不崩溃,把影响抹除

How the STL uses explicit

Efficiently allocating lots of std::shared_ptr

单线程分配shared_ptr对象,比较new make_shared 对象池,压测结果

new make_shared fast_pool_allocator
GCC 69.0 38.1 34.0
Clang + libstdc++ 69.2 38.6 35.7
Clang + libc++ 76.5 40.8 40.4
GCC + tcmalloc 57.2 30.3 33.8
GCC + jemalloc 86.7 42.7 33.9

看一乐 当然结果是满足直觉的,池化快一些,或者别用一堆shared ptr

How much memory does a call to ‘malloc’ allocates?

讲的是个常识,你分配的内存总是向上取整的,不一定是你要多少分配多少

size, speed and order tradeoffs

其实就是访问l1 l2 cache会有不同延迟,通过不同大小文件来测试,有空可以跑一下代码

为什么C++的std::forward会有两种重载

超详细!spdlog源码解析

1

2

3

Latency-Sensitive Applications and the Memory Subsystem: Keeping the Data in the Cache

while循环,没干活,干活逻辑是数据访问,那没干活分支应该可以热数据

比如原来的逻辑

td::unordered_map<int32_t, order> my_orders;
...
packet_t* p;
while(!exit) {
    p = get_packet();
    // If packet arrived
    if (p) {
        // Check if the identifier is known to us
        auto it = my_orders.find(p->id);
        if (it != my_orders.end()) {
            send_answer(p->origin, it->second);
        }
    }
}

while里是个干活逻辑,但是有个大的if,我们可以把这个if拆出来分成干活不干活两个逻辑

std::unordered_map<int32_t, order> my_orders;
...
packet_t* p;
int64_t total_random_found = 0;
while(!exit) {
    // 增加个检查header 然后再判断packet,不满足就去warm
    // 如果header没满足,packet必不满足
    if (packet_header_arrived()) {
        p = get_packet();
        // If packet arrived
        if (p) {
            // Check if the identifier is known to us
            auto it = my_orders.find(p->id);
            if (it != my_orders.end()) {
                send_answer(p->origin, it->second);
            }
        }
    } else {
        // 不干活就Cache warming 
        auto random_id = get_random_id();
        auto it = my_orders.find(random_id);
        // 随便干点啥避免被编译器优化掉
        total_random_found += (it != my_orders.end());
    }
}
std::cout << "Total random found " << total_random_found << "\n";

当然这种cache warm不一定非得随机,有可能副作用

可以从历史值来用,有个词怎么说来着,启发式

硬件层也有cache warm 比如 intel

amd也有 L3 Cache Range Reservation 不过没例子

作者测试了软件模拟cache warm,随机访问

数据,迭代多次的延迟,越小越好

hashmap数据量 正常访问hashmap 没有访问的时候只warm 0 没有访问的时候随机warm
1 K 226.1 (219.0) 213.3 (205.1) 132.5 (67.3)
4 K 324.7 (296.3) 350.7 (331.3) 140.1 (95.4)
16 K 396.8 (341.1) 389.1 (354.5) 208.7 (134.5)
64 K 425.5 (376.1) 416.0 (360.6) 232.1 (152.6)
256 K 514.2 (451.5) 473.3 (480.6) 338.8 (317.6)
1 M 599.8 (550.2) 615.1 (573.6) 466.3 (429.8)
4 M 702.1 (647.0) 619.7 (649.2) 531.3 (508.3)
16 M 756.7 (677.6) 668.8 (707.4) 543.2 (499.9)
64 M 769.1 (702.3) 735.9 (734.2) 641.0 (774.4)

能看到随机访问 随机warm效果显著

Latency-Sensitive Application and the Memory Subsystem Part 2: Memory Management Mechanisms

这篇文章的视角比较奇怪,可能和已知的信息不同,目标是低延迟避免内存机制影响

page fault会引入延迟,所以要破坏page fault的生成条件 怎么做?

尽可能分配好,而不是用到在分配,有概率触发page fault

  • mmap使用MAP_POPULATE
  • 使用calloc不用malloc,用malloc/new 强制0填充
  • 零初始化数组,立马使用上
  • vector 创造时直接构造好大小,不用reserve reserve不一定内存预分配,可能还会造成page fault()
    • 或者重载allocator,预先分配内存
    • 其他容器也是有类似的问题
  • 使用内存大页
  • 禁用 5-Level Page Walk
  • TLB shotdown规避 这个一时半会讲不完 可以看这个
  • 关闭swap

视频

C++ Weekly - Ep 434 - GCC's Amazing NEW (2024) -Wnrvo

-Wnrvo 帮助分析,效果显著

Mirko Arsenijević — Lifting the Pipes - Beyond Sender/Receiver and Expected Outcome — 26.6.2024.

介绍他的dag库,没开源

开源项目介绍

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

互动环节

最近睡眠很差,如果觉得内容有误大家多多指出,困了,先睡

练了一下午街霸6,好难啊我靠,我年纪真是大了,反应跟不上连招连不上,也有可能是设备不行

C++ 中文周刊 2024-06-23 第161期

29 Jun 14:13
e65d2be
Compare
Choose a tag to compare

本期文章由 HNY lhmouse 赞助

lhmouse的项目 mcfthread/asteria 推荐大家关注


资讯

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

编译器信息最新动态推荐关注hellogcc公众号 本周更新 第268期

boost 全新网站 高大上

文章

C++20 std::format 替换 fmtlib 的关键点

可以参考

Lambda, bind(front), std::function, Function Pointer Benchmarks

看一乐

gcc 协程的那些bug

看一乐

Your Own Constant Folder in C/C++

clang ffast-math有bug

__m128 test(const __m128 vec)
{
    return _mm_sqrt_ps(vec);
}
/*
test:                                   # @test
        sqrtps  xmm0, xmm0
        ret
*/

正常生成汇编没问题,但ffast-math生成的汇编有问题, 会替换成rsqrtps然后由于精度问题退化成牛顿法,反而满了我靠

.LCPI0_0:
        .long   0xbf000000                      # float -0.5
        .long   0xbf000000                      # float -0.5
        .long   0xbf000000                      # float -0.5
        .long   0xbf000000                      # float -0.5
.LCPI0_1:
        .long   0xc0400000                      # float -3
        .long   0xc0400000                      # float -3
        .long   0xc0400000                      # float -3
        .long   0xc0400000                      # float -3
test:
        rsqrtps xmm1, xmm0
        movaps  xmm2, xmm0
        mulps   xmm2, xmm1
        movaps  xmm3, xmmword ptr [rip + .LCPI0_0] # xmm3 = [-5.0E-1,-5.0E-1,-5.0E-1,-5.0E-1]
        mulps   xmm3, xmm2
        mulps   xmm2, xmm1
        addps   xmm2, xmmword ptr [rip + .LCPI0_1]
        mulps   xmm2, xmm3
        xorps   xmm1, xmm1
        cmpneqps        xmm0, xmm1
        andps   xmm0, xmm2
        ret

为了规避只好手写汇编

__m128 test(__m128 vec)
{
    __asm__ ("sqrtps %1, %0" : "=x"(vec) : "x"(vec));
    return vec;
}

但是手写汇编优化不彻底,该有的常量折叠没做

__attribute__((always_inline)) __m128 test(__m128 vec)
{
    __asm__ ("sqrtps %1, %0" : "=x"(vec) : "x"(vec));
    return vec;
}

__m128 call_test()
{
    return test(_mm_setr_ps(1.f, 2.f, 3.f, 4.f));
}
test:
        sqrtps  xmm0, xmm0
        ret
.LCPI1_0:
        .long   0x3f800000                      # float 1
        .long   0x40000000                      # float 2
        .long   0x40400000                      # float 3
        .long   0x40800000                      # float 4
call_test:
        movaps  xmm0, xmmword ptr [rip + .LCPI1_0] # xmm0 = [1.0E+0,2.0E+0,3.0E+0,4.0E+0]
        sqrtps  xmm0, xmm0
        ret

sqrtps并没有优化掉

手动折叠, __builtin_constant_p

__attribute__((always_inline)) __m128 test(__m128 vec)
{
    if (__builtin_constant_p(vec))
    {
        return _mm_sqrt_ps(vec);
    }

    __asm__ ("sqrtps %1, %0" : "=x"(vec) : "x"(vec));
    return vec;
}

__m128 call_test()
{
    return test(_mm_setr_ps(1.f, 2.f, 3.f, 4.f));
}

汇编

call_test:
        movaps  xmm0, xmmword ptr [rip + .LCPI11_0] # xmm0 = [1.0E+0,2.0E+0,3.0E+0,4.0E+0]
        sqrtps  xmm0, xmm0
        ret

并没有优化掉??又有个bug,__builtin_constant_p不能用于数组,得手动展开

__attribute__((always_inline)) __m128 test(__m128 vec)
{
    if (__builtin_constant_p(vec[0]) &&
      __builtin_constant_p(vec[1]) &&
      __builtin_constant_p(vec[2]) &&
      __builtin_constant_p(vec[3]))
    {
        return _mm_sqrt_ps(vec);
    }

    __asm__ ("sqrtps %1, %0" : "=x"(vec) : "x"(vec));
    return vec;
}

__m128 call_test()
{
    return test(_mm_setr_ps(1.f, 2.f, 3.f, 4.f));
}
.LCPI15_0:
        .long   0x3f800000                      # float 1
        .long   0x3fb504f3                      # float 1.41421354
        .long   0x3fddb3d7                      # float 1.73205078
        .long   0x40000000                      # float 2
call_test:
        movaps  xmm0, xmmword ptr [rip + .LCPI15_0] # xmm0 = [1.0E+0,1.41421354E+0,1.73205078E+0,2.0E+0]
        ret

折叠了

godbolt

Displaying File Time in C++: Finally fixed in C++20

c++20之前,文件的时间不够直观,得转一手

auto filetime = fs::last_write_time(myPath);
std::time_t convfiletime = std::chrono::system_clock::to_time_t(filetime);
std::cout << "Updated: " << std::ctime(&convfiletime) << '\n';

c++20可以直接用了

auto temp = std::filesystem::temp_directory_path() / "example.txt";
std::ofstream(temp.c_str()) << "Hello, World!";
auto ftime = std::filesystem::last_write_time(temp);
std::cout << std::format("File write time is {0:%R} on {0:%F}\n", ftime);
std::cout << ftime;
// File write time is 14:52 on 2024-06-22
// 2024-06-22 14:52:46.632061324

godbolt

Temporary Lifetime Extension: Complicated Cases

这个之前讲过,就是这个例子

#include <iostream>
#include <string_view>
#include <cstring>

struct Example {
    char data[6] = "hello";
    std::string_view sv = data;
    ~Example() { 
        strcpy(data, "bye");
        std::cout <<"~Example: "<< sv << '\n'; 
    }
};

int main() {
    auto&& local = Example().sv;  // Here we extend lifetime of entire Example
    std::cout << local << '\n'; // 打印hello
}

Example并不会立即析构,生命周期被local延长了,local打印hello

godbolt

这个例子来自这里

How to use the new counted_by attribute in C (and Linux)

列举了内核里使用count by的例子,比如

-    cmd.cmd.scan_type = WMI_ACTIVE_SCAN;
-    cmd.cmd.num_channels = 0;
+    cmd->scan_type = WMI_ACTIVE_SCAN;
+    cmd->num_channels = 0;
     n = min(request->n_channels, 4U);
     for (i = 0; i < n; i++) {
         int ch = request->channels[i]->hw_value;
@@ -991,7 +988,8 @@ static int wil_cfg80211_scan(struct wiphy *wiphy,
             continue;
         }
         /* 0-based channel indexes */
-        cmd.cmd.channel_list[cmd.cmd.num_channels++].channel = ch - 1;
+        cmd->num_channels++;
+        cmd->channel_list[cmd->num_channels - 1].channel = ch - 1;
         wil_dbg_misc(wil, "Scan for ch %d  : %d MHz\n", ch,
                  request->channels[i]->center_freq);
     }
 ...
--- a/drivers/net/wireless/ath/wil6210/wmi.h
+++ b/drivers/net/wireless/ath/wil6210/wmi.h
@@ -474,7 +474,7 @@ struct wmi_start_scan_cmd {
     struct {
         u8 channel;
         u8 reserved;
-    } channel_list[];
+    } channel_list[] __counted_by(num_channels);
 } __packed;

没啥说的。就是针对变长数组做的一个patch,帮助编译器分析的,类似guard_by

Double Linked List with a Single Link Field

就是异或链表,代码不贴了,谁这么写离我远点

On the sadness of treating counted strings as null-terminated strings

字符串复制要注意/0截断问题

Implementing General Relativity: What's inside a black hole?

看不懂

视频

C++ Weekly - Ep 433 - C++'s First New Floating Point Types in 40 Years!

介绍float特殊类型

#include <bit>
#include <concepts>
#include <cstdint>
#include <format>
#include <iomanip>
#include <iostream>
#include <stdfloat>

void explore_float16(std::uint16_t val) {
  const auto f16 = std::bit_cast<std::float16_t>(val);
  const auto bf16 = std::bit_cast<std::bfloat16_t>(val);
  std::cout << std::format(
      "{:#016b} {:#04x} {} {}f16 (0b{:01b}'{:05b}'{:010b}) {}bf16 "
      "(0b{:01b}'{:08b}'{:07b})\n",
      val, val, val, f16, (val >> 15) & 0b1, (val >> 10) & 0b11111,
      (val & 0b1111111111), bf16, (val >> 15) & 0b1, (val >> 7) & 0b11111111,
      (val & 0b1111111));
}

int main() {
  explore_float16(0b0'01101'0101010101);
  explore_float16(0b0'11110'1111111111);
  explore_float16(0b1'11100'0011111111);
}

godbolt

开源项目介绍

互动环节

最近的热点消息那必然是各个群里都传的过劳事件了,大家保重身体不要太拼了,起码睡够,不睡够干不了活的,起码闭眼够

C++ 中文周刊 2024-06-17 第160期

16 Jun 17:46
1eb8644
Compare
Choose a tag to compare

本期文章由 HNY 赞助


资讯

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

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

llvm19 支持#embed了

文章

Why do Windows functions all begin with a pointless MOV EDI, EDI instruction?

这是2011年的文章了,最近又有讨论了,简单来说就是nop,window上可以在线热补丁把这行汇编替换成jmp xx 不过这玩意也就windows 有

其实XCHG edi, edi也是一个意思

Making a bignum library for fun

实现一个大数乘法,直接看代码吧

这里当字符串处理的,难度降低了很多, 面试题属于是

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

typedef struct {
    char *digits;
    int size;
} BigNum;

void bignum_init(BigNum *n, const char *str) {
    n->size = strlen(str);
    n->digits = malloc(n->size * sizeof(char));
    for (int i = 0; i < n->size; i++) {
        // Store digits in reverse. Convert from ASCII.
        n->digits[i] = str[n->size - 1 - i] - '0'; 
    }
}

void bignum_free(BigNum *n) {
    free(n->digits);
    n->digits = NULL;
    n->size = 0;
}

void bignum_print(BigNum *n) {
    printf("BigNum: ");
    for (int i = n->size - 1; i >= 0; i--) {
        printf("%d", n->digits[i]);
    }
    printf("\n");
}

int bignum_compare(const BigNum *a, const BigNum *b) {
    // Returns 1 if a is greater than b, -1 if less than, and 0 if equal to.
    if (a->size != b->size) {
        return a->size > b->size ? 1 : -1;
    }
    for (int i = a->size - 1; i >= 0; i--) {
        if (a->digits[i] != b->digits[i]) {
            return a->digits[i] > b->digits[i] ? 1 : -1;
        }
    }
    return 0;
}

void bignum_add(BigNum *result, const BigNum *a, const BigNum *b) {
    int max_size = a->size > b->size ? a->size : b->size;
    result->digits = malloc((max_size + 1) * sizeof(char));
    int carry = 0;
    int i;

    for (i = 0; i < max_size || carry; i++) {
        int sum = carry + (i < a->size ? a->digits[i] : 0) + (i < b->size ? b->digits[i] : 0);
        result->digits[i] = sum % 10; // Store the last digit of the sum.
        carry = sum / 10; // Carry any overflow.
    }
    result->size = i;
}

void bignum_multiply(BigNum *result, const BigNum *a, const BigNum *b) {
    result->digits = calloc(a->size + b->size, sizeof(char));
    result->size = a->size + b->size; // Max size we will need.

    for (int i = 0; i < a->size; i++) {
        for (int j = 0; j < b->size; j++) {
            int index = i + j;
            result->digits[index] += a->digits[i] * b->digits[j];
            result->digits[index + 1] += result->digits[index] / 10;
            result->digits[index] %= 10;
        }
    }
    
    // Trim any leading zeros.
    while (result->size > 1 && result->digits[result->size - 1] == 0) {
        result->size--;
    }
}

int main() {
    BigNum a, b, sum, product;

    bignum_init(&a, "12345678901234567890");
    bignum_init(&b, "98765432109876543210");

    bignum_print(&a);
    bignum_print(&b);

    bignum_add(&sum, &a, &b);
    bignum_print(&sum);
    bignum_free(&sum);

    bignum_multiply(&product, &a, &b);
    bignum_print(&product);
    bignum_free(&product);

    int cmp_result = bignum_compare(&a, &b);
    if (cmp_result > 0) {
        printf("a is greater than b\n");
    } else if (cmp_result < 0) {
        printf("a is less than b\n");
    } else {
        printf("a is equal to b\n");
    }

    bignum_free(&a);
    bignum_free(&b);

    return 0;
}

Using namespaces effectively

不要头文件直接使用using namespace 放在cpp文件中或者函数里

另外 namespace 版本控制,两种玩法

namespace gem {
    namespace v1 {
        struct Point {
            int x;
            int y;
        };
    }
    namespace v2 {
        struct Point {
            int y; // y goes first in v2
            int x;
        };
    }
using namespace v1; // pull everything under v1 out
}
namespace gem {
    inline namespace v1 {
        ...
    }
    namespace v2 {
        ...
    }
}

推荐用inline,少写一行

其实是c++11之前有bug,但是我觉得还是不知道这个bug比较好,就当少写一行

What’s the deal with std::type_identity?

template<typename T>
struct type_identity {
    using type = T;
};

感觉很垃圾,这是干嘛的?

举个例子

template<typename T>
T add(T a, T b) {
    return a + b;
}

auto sum = add(0.5, 1); // error: cannot deduce T

报错了,这怎么办?主要是类型匹配太多,可以用identity建立关系,消除冲突的重载

template<typename T>
T add(T a, std::type_identity_t<T> b) {
    return a + b;
}
auto sum = add(0.5, 1); // T is "double"

来个现实例子

void enqueue(std::function<void(void)> const& work);

template<typename...Args>
void enqueue(std::function<void(Args...)> const& work,
    Args...args)
{
    enqueue([=] { work(args...); });
}
enqueue([](int v) { std::cout << v; }, 42); // 编译不过

这俩enqueue明显有推导关系 为啥编译不过呢,因为lambda可能匹配两个enqueue

需要一个限制

template<typename...Args>
void enqueue(
    std::type_identity_t<
        std::function<void(Args...)>
    > const& work,
    Args...args)
{
    enqueue([=] { work(args...); });
}

当然改成这样也行

template<typename...Args>
void enqueue(
    std::function<void(
        std::type_identity_t<Args>...
    )> const& work,
    Args...args)
{
    enqueue([=] { work(args...); });
}

当然还可以这样改

template<typename...Args>
void enqueue(
    std::function<void(
        std::decay_t<Args>...
    )> const& work,
    Args&&...args)
{
    enqueue([work, ...args = std::forward<Args>(args)]
            { work(std::move(args)...); });
}

当然也可以这样改 chatgpt老师教的​

template<typename Func, typename... Args>
void enqueue(Func&& func, Args&&... args)
{
    enqueue([=]() { std::invoke(std::forward<Func>(func), std::forward<Args>(args)...); });
}

想了解更多可以看提案哈

The limits of maybe_unused

不是特别好用,void忽略也能对服用

Destructuring Lambda Expression Parameters

结构化绑定很好用

int sum(tuple<int, int, int> triple) {
  auto [a, b, c] = triple;
  return a + b + c;
}

结构化绑定的值如果能直接传给lambda是不是很帅

比如这样

extern bool is_good(string s, int v);

auto foo(map<string, int> items) {
  return views::filter(
    items,
    // DOES NOT WORK!
    [](auto [key, value]) {
      return is_good(key, value);
    });
}

写一个转发吧,其实就是

inline constexpr struct {
  template <typename F>
  constexpr auto operator%(F&& fn) const {
    return [fn](auto&& tpl) {
      return std::apply(fn, FWD(tpl));
    };
  }
} spread_args;

上面的代码就可以这样了

auto foo(map<string, int> items) {
  return views::filter(
    items,
    // Works!
    spread_args % [](auto key, auto value) {
      return is_good(key, value);
    });
}

甚至可以更简单点

auto foo(map<string, int> items) {
  return views::filter(items, spread_args % is_good);
}

Scan HTML faster with SIMD instructions: Chrome edition

常规

void NaiveAdvanceString(const char *&start, const char *end) {
  for (;start < end; start++) {
    if(*start == '<' || *start == '&' 
        || *start == '\r' || *start == '\0') {
      return;
    }
  }
}

常规sse

void AdvanceString(const char*& start, const char* end) {
    const __m128i quote_mask = _mm_set1_epi8('<');
    const __m128i escape_mask = _mm_set1_epi8('&');
    const __m128i newline_mask = _mm_set1_epi8('\r');
    const __m128i zero_mask = _mm_set1_epi8('\0');

    static constexpr auto stride = 16;
    for (; start + (stride - 1) < end; start += stride) {
        __m128i data = _mm_loadu_si128(
           reinterpret_cast<const __m128i*>(start));
        __m128i quotes = _mm_cmpeq_epi8(data, quote_mask);
        __m128i escapes = _mm_cmpeq_epi8(data, escape_mask);
        __m128i newlines = _mm_cmpeq_epi8(data, newline_mask);
        __m128i zeros = _mm_cmpeq_epi8(data, zero_mask);
        __m128i mask = _mm_or_si128(_mm_or_si128(quotes, zeros),                   
             _mm_or_si128(escapes, newlines));
        int m = _mm_movemask_epi8(mask);
        if (m != 0) {
            start += __builtin_ctz(m);
            return;
        }
    }

    // Process any remaining bytes (less than 16)
    while (start < end) {
        if (*start == '<' || *start == '&' 
             || *start == '\r' || *start == '\0') {
            return;
        }
        start++;
    }
}

查表 sse

void AdvanceStringTable(const char *&start, const char *end) {
  uint8x16_t low_nibble_mask = {0b0001, 0, 0, 0, 0, 0, 0b0100, 
          0, 0, 0, 0, 0, 0b0010, 0b1000, 0, 0};
  uint8x16_t high_nibble_mask = {0b1001, 0, 0b0100, 0b0010, 
          0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
  uint8x16_t v0f = vmovq_n_u8(0xf);
  uint8x16_t bit_mask = {16, 15, 14, 13, 12, 11, 10, 9, 8,
                            7, 6, 5, 4, 3, 2, 1};
  static constexpr auto stride = 16;
  for (; start + (stride - 1) < end; start += stride) {
    uint8x16_t data = vld1q_u8(reinterpret_cast<const uint8_t *>(start));
    uint8x16_t lowpart = vqtbl1q_u8(low_nibble_mask, vandq_u8(data, v0f));
    uint8x16_t highpart = vqtbl1q_u8(high_nibble_mask,  
           vshrq_n_u8(data, 4));
    uint8x16_t classify = vandq_u8(lowpart, highpart);
    uint8x16_t matchesones = vtstq_u...
Read more

C++ 中文周刊 2024-06-02 第159期

16 Jun 17:45
68d3cfd
Compare
Choose a tag to compare

本期文章由 HNY 404 赞助


资讯

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

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

P3292R0 Provenance and Concurrency

这个论文讲了一种场景,这个场景其实也不算啥问题,然后也没提出啥解决方案,也没有啥好解决的方案,当前场景对服用也不是不行

分享给大家浪费一下大家时间

文章

C++23: chrono related changes

chrono相关改动

  • 修复缺陷:自动本地化
std::locale::global(std::locale("ru_RU"));
using sec = std::chrono::duration<double>;
auto s_std = std::format("{:%S}", sec(4.2)); // s == "04.200" (not localized)
auto s_std2 = std::format("{:L%S}", sec(4.2)); // s == "04,200" (localized)
std::string s_fmt = fmt::format("{:%S}", sec(4.2));  // s == "04.200" (not localized)

第四行c++20会挂,新版本强制自动本地化,如果你不想要这种行为,自己手动format

  • 澄清本地化的编码格式兼容问题

对于语言,存在本地编码和文字编码不匹配的问题??

std::locale::global(std::locale("Russian.1251"));
auto s = std::format("День недели: {:L}", std::chrono::Monday);

这个就会乱码,因为俄语的星期一有两种符号一个是utf8的一个不是

很抽象

  • 放松clock要求

现在只要支持now就行

An Extensive Benchmark of C and C++ Hash Tables

他这个压测场景和数据非常详细。非常不错

hashmap bm

看图来说 boost unordered map性能不错

多线程的hashmap比如tbb boost concurrency unorderedmap folly map之类 的压测,没有

感觉这个还是挺值得测的

另外这里没有folly f14, 感觉可以加一下 (感谢mwish指出)

Quickly checking whether a string needs escaping

简单实现可能是这样

bool simple_needs_escaping(std::string_view v) {
  for (char c : v) {
    if ((uint8_t(c) < 32) | (c == '"') | (c == '\\')) {
      return true;
    }
  }
  return false;
}

优化一下,去掉分枝

bool branchless_needs_escaping(std::string_view v) {
  bool b = false;
  for (char c : v) {
    b |= ((uint8_t(c) < 32) | (c == '"') | (c == '\\'));
  }
  return b;
}

更自然的,查表

static constexpr std::array<uint8_t, 256> json_quotable_character =
    []() constexpr {
  std::array<uint8_t, 256> result{};
  for (int i = 0; i < 32; i++) {
    result[i] = 1;
  }
  for (int i : {'"', '\\'}) {
    result[i] = 1;
  }
  return result;
}
();

bool table_needs_escaping(std::string_view view) {
  uint8_t needs = 0;
  for (uint8_t c : view) {
    needs |= json_quotable_character[c];
  }
  return needs;
}

更更自然的,simd

inline bool simd_needs_escaping(std::string_view view) {
  if (view.size() < 16) {
    return simple_needs_escaping(view);
  }
  size_t i = 0;
  __m128i running = _mm_setzero_si128();
  for (; i + 15 < view.size(); i += 16) {
    __m128i word = _mm_loadu_si128((const __m128i *)(view.data() + i));
    running = _mm_or_si128(running, _mm_cmpeq_epi8(word, _mm_set1_epi8(34)));
    running = _mm_or_si128(running, _mm_cmpeq_epi8(word, _mm_set1_epi8(92)));
    running = _mm_or_si128(
        running, _mm_cmpeq_epi8(_mm_subs_epu8(word, _mm_set1_epi8(31)),
                                _mm_setzero_si128()));
  }
  if (i < view.size()) {
    __m128i word =
        _mm_loadu_si128((const __m128i *)(view.data() + view.length() - 16));
    running = _mm_or_si128(running, _mm_cmpeq_epi8(word, _mm_set1_epi8(34)));
    running = _mm_or_si128(running, _mm_cmpeq_epi8(word, _mm_set1_epi8(92)));
    running = _mm_or_si128(
        running, _mm_cmpeq_epi8(_mm_subs_epu8(word, _mm_set1_epi8(31)),
                                _mm_setzero_si128()));
  }
  return _mm_movemask_epi8(running) != 0;
}

性能就不说了。看有没有必要吧,没有必要的话最多无分支版本哈,代码太多了,收益不大。当然性能肯定是simd最快

感兴趣自己玩一下 代码

Function Composition and the Pipe Operator in C++23 – With std::expected

实现shell管道用法

#include <iostream>
#include <functional>
#include <string>
#include <concepts>
#include <random>
#include <expected>

template < typename T >
concept is_expected = requires( T t ) {
	typename T::value_type;	// type requirement – nested member name exists
	typename T::error_type;	// type requirement – nested member name exists

	requires std::is_constructible_v< bool, T >;
	requires std::same_as< std::remove_cvref_t< decltype(*t) >, typename T::value_type >;
	requires std::constructible_from< T, std::unexpected< typename T::error_type > >; 
};

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

// We have a data structure to process
struct Payload {
	std::string	fStr{};
	int		fVal{};
};

// Some error types just for the example
enum class OpErrorType : unsigned char { kInvalidInput, kOverflow, kUnderflow };

// For the pipe-line operation - the expected type is Payload,
// while the 'unexpected' is OpErrorType
using PayloadOrError = std::expected< Payload, OpErrorType >;

PayloadOrError Payload_Proc_1( PayloadOrError && s ) {
	if( ! s )
		return s;

	++ s->fVal;
	s->fStr += " proc by 1,";

	std::cout << "I'm in Payload_Proc_1, s = " << s->fStr << "\n";

	return s;
}

PayloadOrError Payload_Proc_2( PayloadOrError && s ) {
	if( ! s )
		return s;

	++ s->fVal;
	s->fStr += " proc by 2,";

	std::cout << "I'm in Payload_Proc_2, s = " << s->fStr << "\n";

	// Emulate the error, at least once in a while ...
	std::mt19937 rand_gen( std::random_device {} () );
	return ( rand_gen() % 2 ) ? s : 
					std::unexpected { rand_gen() % 2 ? 
                                OpErrorType::kOverflow : OpErrorType::kUnderflow };
}

PayloadOrError Payload_Proc_3( PayloadOrError && s ) {
    if( ! s )
		return s;

	s->fVal += 3;
	s->fStr += " proc by 3,";

	std::cout << "I'm in Payload_Proc_3, s = " << s->fStr << "\n";
	return s;
}

void Payload_PipeTest() {
	static_assert( is_expected< PayloadOrError > );	// a short-cut to verify the concept
	auto res = 	PayloadOrError { Payload { "Start string ", 42 } } |
						Payload_Proc_1 | Payload_Proc_2 | Payload_Proc_3 ;

    // Do NOT forget to check if there is a value before accessing that value (otherwise UB)                    
	if( res )
		std::cout << "Success! Result of the pipe: fStr == " << res->fStr << " fVal == " << res->fVal;
	else
		switch( res.error() ) {
			case OpErrorType::kInvalidInput:    std::cout << "Error: OpErrorType::kInvalidInput\n";     break;
			case OpErrorType::kOverflow:		std::cout << "Error: OpErrorType::kOverflow\n";		    break;
			case OpErrorType::kUnderflow:		std::cout << "Error: OpErrorType::kUnderflow\n";	    break;
			default:                            std::cout << "That's really an unexpected error ...\n"; break;
		}
}

int main() { Payload_PipeTest(); }

代码 godbolt

揭秘C++:虚假的零成本抽象

标题党

Hydra: 窥孔优化泛化

看不懂

开源项目介绍

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

互动环节


上一期

C++ 中文周刊 2024-05-25 第158期

16 Jun 17:45
5884cb3
Compare
Choose a tag to compare

本期文章由 HNY 赞助, 本期内容很少


资讯

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

五月邮件

基本就是range constract executor 相关提案乱炖

讲几个R0

简单来说就是针对类级别的更精细的内存控制

operator new(sizeof(T), type_identity<T>{}, args…)

通常这种都是自己搞个数组placement new

提供根据类型的new接口,能更简单实现这种逻辑。看一乐,提供一种思路

这个其实就是很多指针实际上更多是抽象的,可以利用bit做事,但标准对这块不是很清晰,算是UB吧

比如llvm的PointerIntPair arm的MTE hash trie的指针管理 或者pointer swizzle技巧那种把指针搞出复合语义

这种玩意其实就是tag pointer,不同tag表达不同含义,感觉标准化会更好一些

浮点数的min/max处理了太多边角场景(NaN之类的),不够快,不想用ffastmath情况下提供了一套新接口

帮你把optional variant any里的T掏出来。这个很干净

实现godbolt

帮P2786加了个接口实现P1144相同能力。看不懂?没事我也不懂

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

文章

超好用的 C++ 在线编译器(VSCode 版)

godbolt集成vscode了,挺好的

C++神秘学习——memcpy的一个ub

memcpy data()了,编译器假设不空优化导致asan报错

vector string当buffer没问题,都用他们当buffer不用他们的成员函数复制是怎么个意思,瞧不起assign?

这个也和群友激烈讨论了

笔者的个人观点,data()虽然不是const,但是最好最好不乱玩,能用成员用成员,实在不行才hack,比如resize这种

assign肯定也是memcpy,他会帮你判断的,你直接拿来用,那肯定说明不为空,那编译器的假设笔者认为没毛病

write()和mmap写混用与cache alias

学到了

SLUB 分配器的下一步计划

看一乐

C++无锁(lock free)数据结构与有锁数据结构相比,速度,性能等有何区别?多线程中如何选择?

看一乐

long double to_string会丢失精度?

#include <string>
#include <iostream>
#include <iomanip>
using namespace std;
int main() {
    long double d = -20704.000055577169;
    string s = to_string(d);
    cout << s << endl;//-20704.000056 
    cout << setprecision(12) << fixed << s << endl;
}

两个输出没有差别。注意这个坑 godbolt

Looking up a C++ Hash Table with a pre-known hash

如果你知道了hash,如何避免容器计算hash?定制Equal,把hash比较也放进去,直接看完整代码

template<typename Hash>
class KeyHashPair {
private:
    std::string_view key_;
    std::size_t hash_;
public:
    KeyHashPair() = delete;
    KeyHashPair(std::string_view sv) : key_(sv), hash_(Hash{}(key_)) {}

    std::string_view key() const { return key_; }
    std::size_t hash() const { return hash_; }
};

struct Hash {
    using is_transparent = void;

    std::size_t operator()(std::string_view sv) const {
        return std::hash<std::string_view>{}(sv);
    }
    std::size_t operator()(KeyHashPair<Hash> pair) const {
        return pair.hash();
    }
};

struct KeyEqual {
    using is_transparent = void;

    bool operator()(std::string_view lhs, std::string_view rhs) const {
        return lhs == rhs;
    }
    template<typename Hash>
    bool operator()(KeyHashPair<Hash> lhs, std::string_view rhs) const {
        return lhs.key() == rhs;
    }
};

using Set = std::unordered_set<std::string, Hash, KeyEqual>;

int main() {
    Set set{"foo"};

    const std::string string{"foo"};
    const KeyHashPair<Set::hasher> pair{string};

    assert(set.contains(pair));
}

还是有点思路提升的,但并没有把hash存下来,其实更合理的思路是直接比较set内部的hash,可惜没有暴露

开源项目介绍

  • asteria 一个脚本语言,可嵌入,长期找人,希望胖友们帮帮忙,也可以加群753302367和作者对线
  • quill 4.0发布 编译时间 写入速度双提升
  • ipvar 多进程共享变量,没明白什么场景用得上

工作招聘

互动环节

和群友聊天聊到了某些爱装逼的人,发现每一代学生都是这样的思维,沉迷某些技术 术语,爱装逼

虽然笔者讲出来有一点好为人师的感觉,但我还是很后悔大学浪费很多时间在嘴贫装逼上的。

那么问题来了,咱们读者是学生还是工作党,如果是学生,本周刊对你有没有收益?还是在咱这里学装逼来了,切记低调别装逼

如果不懂或者认为笔者有错误,大胆评论区指出,谢谢先,千万别模棱两可混过去了,笔者也不一定懂


上一期