Skip to content

Releases: wanghenshui/cppweeklynews

C++ 中文周刊 2024-11-23 第173期

23 Nov 14:48
1708e52
Compare
Choose a tag to compare

周刊项目地址

公众号

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

qq群 点击进入 满了加这俩 729240657 866408334

RSS

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

本期文章由 LH_mouse 赞助 在此表示感谢

祝老板身体健康事业顺利开新车装新房夜夜当新郎全世界都有你的丈母娘


资讯

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

来自本台记者Mick前方发来的报道

欢迎来到C++26的第五次会议,也是feature freeze之前的倒数第二次会议。本次会议共约230人参与,31个NB参会,依然是传统的线下:线上=2:1模式

在通过的提案方面,本次共通过8篇语言提案和19篇库提案。这比上次会议的7+12要多一些,和东京正好持平。

语言方面,最重磅的也是最坎坷的提案无疑是P1061 auto [...xs] = ... (structured binding packs),作为上次Plenary中被撤销的提案,P1061本来应该波澜不惊地再次投票。没想到的是,EWG第三天的讨论中MSVC开发者提出了对实现难度的抗议,差一点导致本提案倒在Stage 2。幸运的是,最后作者找到了一个妥协方案,将带pack的structured binding限制在只能在模版中使用,从而成功进入标准。除此之外,语言方面还通过了P3068,允许在编译期抛出异常。(当然,异常不能离开编译期,编译抛运行catch是不行的),并且常规deprecate了一批特性(is_trivial,不带逗号的varargs语法)。

LWG方面,最重磅的提案无疑是极度坎坷的P1928 std::simd。本提案的历史极其悠久,从2013年的N3759初创之后,-> N4184/5 -> N4395 -> P0214R9这14个revision之后终于在2018年修成半个正果,成功进入Parallelism TS v2。不过,随后的IS merge依然极度艰难,大规模的设计改动和名称反复贯穿了P1928的历史,最终在用了十年,30个revision之后终于成功进入C++26。本提案事实上大体标准化了SIMD指令,让标准中可以直接像操纵其他原生类型一样操作SIMD向量。至此,C++26标准库的两个T0和一个T0.5特性均已成功进入标准,L(E)WG成功提前完成了自己本周期的目标。下一次会议的主要目标看来就是搞定hive这个老大难。
另一个重点提案就是P3019 indirect/polymorphic,即一个deep copy版的智能指针。从cloned_ptr走到indirect_value走到indirect,花了P0201R6 -> P3019R11的19个revision才走完这条路,不过好歹是走完了。现在pimpl就可以用标准解法了。

除此之外,本次搞定了大量的线性代数bugfix提案,包括aligned_accessor,submdspan fix等对C++26至关重要的提案被成功完成(还剩下atomic_accessor和rank-2k两个提案,预计下次吧)。另外,本次会议还迈出了C23 rebase的第一步,成功将C23新增的两个头文件(bits,安全整数加法)加入了C++26。

本次会议的Stage 2工作组相对来说更有看点一些。EWG方面,P2786平凡迁移和P2900 Contracts均成功被推进Stage 3,但是两者的争议都非常大,forwarding poll可以说是barely consensus,要避免Plenary的失败还有很长的路要走。第三天的讨论中,反射终于确定了使用^^语法(unibrow),并逐渐开始在一些post-P2996反射提案上有了进展(比如consteval block,可惜expansion statement依然卡死着)。第四天早上是模式匹配的主场,虽然这一特性进入26的希望已经非常渺茫,但是P2688仍然在为此努力,并成为了EWG选择的语法而不是P2392的is/as。较为遗憾的是,原本应该在本次会议通过的fiber_context在最后一刻找到了Windows下的实现难题,只得推迟到下个周期去了。

LEWG方面,P2996反射和P2900 Contracts同样进入了Stage 3,从而扫清了这两个语言的主要目标在库这边的障碍。除此之外,整周大部分都在搞S&R相关扩展,例如async_scope,system scheduler等对P2300发挥作用至关重要的补充提案拿到了一定进展(所以lazy啥时候有人愿意接手…)。遗憾的是,concurrent queue依然在Concurrency TS v3和IS之间举棋不定,进入26的希望已经较为渺茫。除此之外,type_order_v也进入了Stage 3,有望给所有类型一个标准化的偏序关系。

Stage 1工作组方面,SG9 Ranges完成了Range化的并行算法的设计,但是离自己的plan依然差的有点远()。SG21 Contracts在完善Wording的同时,已经渐渐转向post-MVP特性,例如把语言UB大部分转成Contracts。SG23 Security完成了Profile的初版设计,不过前景究竟如何还要看看

展望明年的会议总体情况,“双边反转”已经基本成为事实,20/23周期的末尾都是LWG提案太多完不成不得不扔掉一些,这次LWG已经完成了绝大多数大提案,队伍反而不是很挤。提案太多完不成的变成了CWG,队伍里三个大提案实在有点吃不消,要做好CWG扔掉一堆小提案的准备。

编译器信息最新动态推荐关注hellogcc公众号 本周没更新 点击跳转上周的

文章

Exploring C++ std::span – Part 4: Const-Correctness and Type-Safety

还是介绍span的优缺点。这里介绍一种API冗余的问题

#include <span>
#include <iostream>
#include <vector>
void processData(int* data, std::size_t size) {
    std::cout << "fucked";
}
void processData(std::span<int> data) {
    std::cout << "ok";
}


int main() {
    std::vector<int> v{ 1,3, 5,7, 9};
    processData({v.data(), v.size()});
}

两种接口存在误用可能,保留一个即可

How do I put a non-copyable, non-movable, non-constructible object into a std::optional?

The operations for reading and writing single elements for C++ standard library maps

raymood chen这俩文章是连着的,我就放在一起讲了

一个是如何处理optional构造不能复制/移动的对象,简单来说解决方案就是类似std::elide

看代码 godbolt

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

struct Region {
        int dummy[100];
};
struct Widget
{
    Widget(Region const& region) {

    }
    Widget() = delete;
    Widget(Widget const&) = delete;
    Widget(Widget &&) = delete;
    Widget& operator=(Widget const&) = delete;
    Widget& operator=(Widget &&) = delete;

    static Widget CreateInside(Region const& region) {
        std::cout << "inside\n";
        return Widget(region);
    }
    static Widget CreateOutside(Region const& region) {
        std::cout << "outside\n";
        return Widget(region);
    }
private:
    int dummy[100];
};


struct WidgetInsideRegionCreator
{
    WidgetInsideRegionCreator(Region const& region) : m_region(region) {}
    operator Widget() { return Widget::CreateInside(m_region); }
    Region const& m_region;
};

template<typename F>
struct EmplaceHelper
{
    EmplaceHelper(F&& f) : m_f(f) {}
    operator auto() { return m_f(); }
    F& m_f;
};


int main()
{
    Region region;
    // construct with a Widget value
    std::optional<Widget> o0(WidgetInsideRegionCreator(region)); //函数声明我草
    std::optional<Widget> o{WidgetInsideRegionCreator(region)};

    std::optional<Widget> o2;
    // or place a Widget into the optional
    o2.emplace(WidgetInsideRegionCreator(region));
    // construct with a Widget value
    // 为什么这个不被解析成函数声明?
    std::optional<Widget> o3(
        EmplaceHelper([&] {
            return Widget::CreateInside(region);
        }));
    std::optional<Widget> o4;
    // or place a Widget into the optional
    o4.emplace(EmplaceHelper([&] {
            return Widget::CreateInside(region);
        }));
}

简单来说是通过optional的成员构造,从T本身构造,通过wrapper转发给optional隐式构造

第二篇文章是 map插入接口复杂以及带来的构造问题

Operation 操作 Method 方法
Read, throw if missing读取,如果缺失则抛出异常 m.at(key)
Read, allow missing 阅读,允许缺失 m.find(key)
Read, create if missing阅读,如果不存在则创建 m[key]
Write, nop if exists, discard value写入,如果存在则 nop,丢弃值 m.insert({ key, value })
m.emplace(key, value)
Write, nop if exists写入,如果存在则 nop m.emplace(std::piecewise_construct, ...)
m.try_emplace(key, params)
Write, overwrite if exists编写,如果存在则覆盖 m.insert_or_assign(key, value)

这么多接口,想实现如果没有就插入,如果有不要浪费构造这种逻辑,怎么做?

template<typename Map, typename Key, typename... Maker>
auto& ensure(Map&& map, Key&& key, Maker&&... maker)
{
    auto lower = map.lower_bound(key);
    if (lower == map.end() || map.key_comp()(lower->first, key)) {
        lower = map.emplace_hint(lower, std::forward<Key>(key),
            std::invoke(std::forward<Maker>(maker)...));
    }
    return lower->second;
}

auto& ensure_named_widget(std::string const& name)
{
    return ensure(widgets, name,
        [](auto&& name) { return std::make_shared<Widget>(name); },
        name);
}

一坨屎啊,注意到我们转发的这个逻辑,其实就是上面的elide

之前在115期也介绍过 优化insert

insert可能不成功,所以需要推迟value构造,惰性构造

template <class F>
struct lazy_call {
    F f;

    template <class T> operator T() { return f(); }
};

#define LAZY(expr) lazy_call{[&]{ return expr; }}
auto [iter, success] = map.try_emplace(key, LAZY(acquire_value()));

其实就是elide

应该有机会合入,代码也很简单 godbolt

那我们可以用emplacehelper,也就是elide重新实现一下,就用try_emplace就好了

template<typename Map, typename Key, typename... Maker>
auto& ensure(Map&& map, Key&& key, Maker&&... maker)
{
    return *map.try_emplace(key, EmplaceHelper([&] {
        return std::invoke(std::forward<Maker>(maker)...);
    }).first;
}

一行,甚至都不用封装这么丑的代码,直接在用到map的地方直接调用就行,比如

auto& item =
    *widgets.try_emplace(name, EmplaceHelper([&] {
        return std::make_shared<Widget>(name); })).first;

Some Of My Experience About Linking C/C++ On Linux

简单来说就是符号查找的问题

比如不同库可能的符号覆盖,符号weak strong之类的说明,不了解的建议翻翻《链接装载库》,虽然旧也够用

引申阅读 问题排查:C++ exception with description “getrandom“ thrown in the test body

找不到符号 -> 宿主机没提供 编译问题,版本没对上,实现链接了不存在的符号

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

[加固详细介绍](https://l...

Read more

C++ 中文周刊 2024-11-16 第172期

23 Nov 14:49
c8b4250
Compare
Choose a tag to compare

周刊项目地址

公众号

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

qq群 点击进入 满了加这俩 729240657 866408334

RSS

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

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


资讯

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

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

clang增加了一个safebuffer模式

可以使用 -Wunsafe-buffer-usage 目前还在开发中

clang增加了函数分析能力

在noexcept 基础上增加了noblocking noallocating

更准确分析函数行为,可以配合之前介绍的RealTimeSan使用

主要问题是函数指针不行,函数指针/function自动丢弃上述属性

另外存在属性覆盖,noblocking noallocating的必要条件是noexcept,权限大于,大家懂我意思吧

函数指针怎么绕过

std::sort(vec.begin(), vec.end(),
  [](const Elem& a, const Elem& b) [[clang::nonblocking]] { return a.mem < b.mem; }); // OK

static bool compare_elems(const Elem& a, const Elem& b) [[clang::nonblocking]] {
  return a.mem < b.mem; }; // 不行 属性会丢

std::sort(vec.begin(), vec.end(), compare_elems);

template <typename>
class nonblocking_fp;

template <typename R, typename... Args>
class nonblocking_fp<R(Args...)> {
public:
  using impl_t = R (*)(Args...) [[clang::nonblocking]];

private:
  impl_t mImpl{ nullptr_t };
public:
  nonblocking_fp() = default;
  nonblocking_fp(impl_t f) : mImpl{ f } {}

  R operator()(Args... args) const
  {
    return mImpl(std::forward<Args>(args)...);
  }
};

// deduction guide (like std::function's)
template< class R, class... ArgTypes >
nonblocking_fp( R(*)(ArgTypes...) ) -> nonblocking_fp<R(ArgTypes...)>;

// --

// Wrap the function pointer in a functor which preserves ``nonblocking``.
std::sort(vec.begin(), vec.end(), nonblocking_fp{ compare_elems });

文章

~~我用得着你说?~~强调span不容易用错

template<typename T>
class HasIsNullMethod {
    struct Yes { char unused[1]; };
    struct No { char unused[2]; };
    template <typename U, U u> struct reallyHas;
    template <typename C> static Yes& test(reallyHas<char (C::*)(), &C::isNull>* /*unused*/) { }
    template <typename C> static Yes& test(reallyHas<char(C::*)() const, &C::isNull>* /*unused*/) { }
    // template<class C> static Yes test(char(*)[static_cast<int>(&C::isNull == false) + 1]) {}
    template<class C> static No test(...);
public:
    static const bool Value = (sizeof(test<T>(0)) == sizeof(Yes));
};

struct A {
    char isNull() {}
};
struct B {
    void isNull() {}
};


struct C {
    char isNull;
};

bool fA() {
  return HasIsNullMethod<A>::Value;
}

bool fB() {
    return HasIsNullMethod<B>::Value;
}

bool fC() {
    return HasIsNullMethod<C>::Value;
}
// Type your code here, or load an example.
bool fint()
{
    return HasIsNullMethod<int>::Value;
}
#include <iostream>
int main() {
    std::cout << fint() << "\n";
    std::cout << fA()<< "\n";
    std::cout << fB()<< "\n";
    std::cout << fC()<< "\n";
    return 0;
}

godbolt 不会也没啥,糟粕 喜欢怀旧可以看一下

当实现pimpl惯用法的时候,使用unique_ptr通常因为看不到完整实现(析构)调用失败

作者给的办法是手动加上deleter

不要听他的,直接用shared_ptr,不要多此一举好吧

介绍timezone

我记得有一个date库就做了这个活,用不上可以直接用那个date

简单贴一下代码,介绍一下接口

#include <chrono>
#include <print>

int main() {
    const auto now = std::chrono::system_clock::now();      
    auto zt_local = std::chrono::zoned_time{ std::chrono::current_zone(), now };
    std::print("now is {} UTC and local is: {}\n", now, zt_local);

    constexpr std::string_view Warsaw{ "Europe/Warsaw" };
    constexpr std::string_view NewYork{ "America/New_York" };
    constexpr std::string_view Tokyo{ "Asia/Tokyo" };

    try
    {
        const std::chrono::zoned_time zt_w{Warsaw, now};
        std::print("Warsaw: {0:%F} {0:%R}\n", zt_w);
        const std::chrono::zoned_time zt_ny{NewYork, now};
        std::print("New York: {0:%F} {0:%R}\n", zt_ny);
        const std::chrono::zoned_time zt_t{Tokyo, now};
        std::print("Tokyo: {0:%F} {0:%R}\n", zt_t);
    }
    catch (std::runtime_error& ex)
    {
        std::print("Error: {}", ex.what());
    }
}
/*
now is 2024-11-15 22:31:24.193993753 UTC and local is: 2024-11-15 22:31:24.193993753
Warsaw: 2024-11-15 23:31
New York: 2024-11-15 17:31
Tokyo: 2024-11-16 07:31
*/

godbolt 还有其他代码,就不贴了

这人不懂c++大惊小怪,就是简单的const延长生命周期

不看代码了。单纯喷他一下

老文章,valgrind不如sanitizer直接/快,且有遗漏

死循环优化

#include <iostream>
     
int fermat () {
    const int MAX = 100;
    int a=1,b=1,c=1;
    int iter = 0;
    while (1) {
        if ( (a*a*a) == (b*b*b) + (c*c*c) ) {
            std::cout << "Found!\n";
            return 1;
        }
        a++;
        if (a>MAX) {
            a=1;
            b++;
        }
        if (b>MAX) {
            b=1;
            c++;
        }
        if (c>MAX) {
            c=1;
        }
        ++iter;
    }
    return 0;
}

int main () {
    if (fermat()) {
        std::cout << "Fermat's Last Theorem has been disproved.\n";
    } else {
        std::cout << "Fermat's Last Theorem has not been disproved.\n";
    }
    return 0;
}

打印

Found!
Fermat's Last Theorem has been disproved.

由于return是死循环唯一出口,编译器激进到直接return 1

这个可以看lancern文章有介绍过 感谢zwuis提醒

析构栈溢出一个例子

#include <iostream>
#include <memory>
#include <vector>

struct Node {
    int value = 0;
    std::vector<Node> childrens;
};

struct List {
    int value = 0;
    std::unique_ptr<List> next;

    ~List() {
        while (next) {
            // The destructor is still recursive,
            // but now the recursion depth is 1 call.
            next = std::move(next->next);
        }
    }

    List() noexcept = default;
    List(List&&) noexcept = default;
    List& operator=(List&&) noexcept = default;
};

int main() {
    List dummynode;
    List* l = &dummynode;

    int BOUND = 1000;
    int SUB_BOUND = 100;
    for (int i = 1; i<BOUND; i++) {
        l->value = i;
        l->next = std::make_unique<List>();
        l = l->next.get();
    }
    /*
    // 这个析构会栈溢出
    Node n;
    auto tmp = &n;
    for (int i = 1; i<BOUND; i++) {
        for (int j = 0; j< SUB_BOUND; j++) {
            Node c;
            c.value = j*i;
            tmp->childrens[j] = c;
            // tmp = &tmp->childrens[j]; 
        }
    }
    */
}

noexcept问题 noexcept=noexcept(true), noexcept(cond) 可以自己定制

成员函数除了析构都是noexcept(false) ,如果析构函数抛异常需要显式指明

struct SoBad {
  // invoke std::terminate
  ~SoBad() {
     throw std::runtime_error("so bad dtor");
  }
};

struct  NotSoBad {
  // OK
  ~NotSoBad() noexcept(false) {
    throw std::runtime_error("not so bad dtor");
  }
};

使用noexcept需要你写异常验证代码,避免terminate爆炸

缓冲区溢出问题

一大堆傻逼函数 scanf strcpy strcat gets strncat等等,哦还有memcpy

引申出数组越界问题,数组越界会被激进优化,一定要注意

其实有点和前面的例子很相似

const int N = 10;
int elements[N];

bool contains(int x) {
  for (int i = 0; i <= N; ++i) {
    if (x == elements[i]) {
      return true;
    }
  }
  return false;
}

int main() {
  for (int i = 0; i < N; ++i) {
    std::cin >> elements[i];
  }
  return contains(5);
}

存在越界 -> 越界是UB,编译器认为代码中没有UB,说明越界肯定不可达,说明提前返回 所以直接优化成true

类似例子

const int N = 10;
int main() {
  int decade[N];
  for (int k = 0; k <= N; ++k) {
    printf("k is %d\n",k);
    decade[k] = -1;
  }
}

k越界,越界是UB,编译器认为代码中没有UB,说明永远到不了N,直接死循环

鉴定为不如varint,评论区有人指出性能比varint不太行,测试代码我拿来在7950x WSL跑了一下

2024-11-16T23:09:24+08:00
Running ./benchmarks/msft_proxy_benchmarks
Run on (32 X 4499.92 MHz CPU s)
CPU Caches:
  L1 Data 32 KiB (x16)
  L1 Instruction 32 KiB (x16)
  L2 Unified 1024 KiB (x16)
  L3 Unified 32768 KiB (x1)
Load Average: 0.65, 0.21, 0.11
---------------------------------------------------------------------------------------
Benchmark                                             Time             CPU   Iterations
---------------------------------------------------------------------------------------
BM_SmallObjectInvocationViaProxy                4648790 ns      5071349 ns          136
BM_SmallObjectInvocationViaVirtualFunction     11986710 ns     13076183 ns           54
BM_SmallObjectInvocationViaVariant              5044399 ns      5495963 ns          127
BM_LargeObjectInvocationViaProxy                7689574 ns      8388641 ns           83
BM_LargeObjectInvocationViaVirtualFunction      9397069 ns     10251350 ns           68
BM_LargeObjectInvocationViaVariant              5046724 ns      5490317 ns          127
BM_SmallObjectManagementWithProxy               1...
Read more

C++ 中文周刊 2024-11-03 第171期

23 Nov 14:49
ad11b49
Compare
Choose a tag to compare

周刊项目地址

公众号

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

qq群 点击进入 满了加这俩 729240657 866408334

RSS

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

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

本期内容较少,掺杂一些互动


资讯

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

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

文章

How useful is the hint passed to the std::unordered_… collections?

emplace_hit 在有序map有很大作用,但是在无序容器,由于不允许重复,hint基本没啥用

但无序容器存在允许重复值的 multixx,这种场景可以用,一般来说用不着

总结了一个表格

实现 unordered_multixx unordered_xx
是否允许重复
msvc/STL 如果匹配就是用 如果匹配就使用
clang/libcxx 忽略 如果匹配就使用
gcc/libstdc++ (large or fast) 忽略 如果匹配就使用
gcc/libstdc++ (small and slow) 忽略 使用

libstdc++针对不同key的hash有快慢识别,默认是快的 (long double慢) 这里有坑不知道大家记得不

群友mapleFU投稿

之前用 hint 优化过一些有序容器相关的处理( io range 维护什么的),感觉还是挺有用的

工厂函数的几种实现

其实就是static map,可以有多种维护方法

  • 可以利用类来封装,利用宏生成多个static变量构造,来注册到map
  • singleton模版注册也可以,利用模版实例化来调用注册到map 这两种都适合分散写法

这里有个例子 https://www.cnblogs.com/qicosmos/p/5090159.html

我就不贴代码了,脑补一下就有了

  • 直接注册也可以,比如
static std::unordered_map<std::string, OptionTypeInfo>
    lru_cache_options_type_info = {
        {"capacity",
         {offsetof(struct LRUCacheOptions, capacity), OptionType::kSizeT,
          OptionVerificationType::kNormal, OptionTypeFlags::kMutable}},
        {"num_shard_bits",
         {offsetof(struct LRUCacheOptions, num_shard_bits), OptionType::kInt,
          OptionVerificationType::kNormal, OptionTypeFlags::kMutable}},
        {"strict_capacity_limit",
         {offsetof(struct LRUCacheOptions, strict_capacity_limit),
          OptionType::kBoolean, OptionVerificationType::kNormal,
          OptionTypeFlags::kMutable}},
        {"high_pri_pool_ratio",
         {offsetof(struct LRUCacheOptions, high_pri_pool_ratio),
          OptionType::kDouble, OptionVerificationType::kNormal,
          OptionTypeFlags::kMutable}},
        {"low_pri_pool_ratio",
         {offsetof(struct LRUCacheOptions, low_pri_pool_ratio),
          OptionType::kDouble, OptionVerificationType::kNormal,
          OptionTypeFlags::kMutable}},
};

直接注册也未尝不可,直观,适合聚集写法

依赖dlopen也可以,不过属于杀鸡牛刀

如何判断一个数字是不是浮点数0?判断0正负 https://godbolt.org/z/jcqc38qqW

直接贴代码

#include <numeric>
#include <cmath>
#include <iostream>


class FloatingPointComparator {
private:
    static constexpr double DEFAULT_EPSILON = 1e-10;
    static constexpr double MIN_NORMAL = std::numeric_limits<double>::min();
    static constexpr double MAX_NORMAL = std::numeric_limits<double>::max();

public:
    // 基本的零值检查
    static bool isZero(double value) {
        return std::abs(value) < DEFAULT_EPSILON;
    }
    
    // 带自定义误差的零值检查
    static bool isZeroWithEpsilon(double value, double epsilon) {
        return std::abs(value) < epsilon;
    }
    
    // 相对误差检查
    static bool isZeroRelative(double value) {
        if (std::abs(value) < MIN_NORMAL) {
            return true;
        }
        return std::abs(value) < DEFAULT_EPSILON * std::max(1.0, std::abs(value));
    }
    
    // IEEE 754 特殊值检查
    static bool isSpecial(double value) {
        return std::isnan(value) || std::isinf(value);
    }
    
    // 判断是否为正负零
    static bool isExactZero(double value) {
        return value == 0.0 || value == -0.0;
    }
    
    // 综合判断
    static bool isEffectivelyZero(double value) {
        if (isSpecial(value)) {
            return false;
        }
        if (isExactZero(value)) {
            return true;
        }
        return isZeroRelative(value);
    }
};

class ZeroSignChecker {
public:
    static bool isNegativeZero(double value) {
        if (value != 0.0) return false;
         /*
        union {
            double d;
            uint64_t u;
        } u = {value};
        return (u.u >> 63) == 1;
        */   
        auto u = std::bit_cast<std::uint64_t>(value);
        // 检查符号位(最高位)
        return (u >> 63) == 1;
    }
    
    static bool isPositiveZero(double value) {
        if (value != 0.0) return false;
        /*
        union {
            double d;
            uint64_t u;
        } u = {value};
        return (u.u >> 63) == 0;
        */
        auto u = std::bit_cast<std::uint64_t>(value);
        
        // 检查符号位
        return (u >> 63) == 0;
    }
    static bool isPositiveZeroV2(double value) {
        return value == 0.0 && !std::signbit(value);
    }
    
    static bool isNegativeZeroV2(double value) {
        return value == 0.0 && std::signbit(value);
    }

    static bool isNegativeZeroCoreDump(double value) {
        if (value != 0.0) return false;
        return std::isinf(1.0 / value) && (1.0 / value < 0);
    }
    
    static bool isPositiveZeroCoreDump(double value) {
        if (value != 0.0) return false;
        return std::isinf(1.0 / value) && (1.0 / value > 0);
    }
};

// 使用示例
void testZeroSign() {
    double pzero = 0.0;
    double nzero = -0.0;
    
    std::cout << "Positive zero: " << ZeroSignChecker::isPositiveZero(pzero) << std::endl;
    std::cout << "Negative zero: " << ZeroSignChecker::isNegativeZero(nzero) << std::endl;
    std::cout << "Positive zero: " << ZeroSignChecker::isPositiveZeroV2(pzero) << std::endl;
    std::cout << "Negative zero: " << ZeroSignChecker::isNegativeZeroV2(nzero) << std::endl;
}

// 使用示例
void testFloatingPoint() {
    double values[] = {
        0.0,
        -0.0,
        1e-15,
        1e-10,
        std::numeric_limits<double>::min(),
        std::numeric_limits<double>::denorm_min(),
        std::numeric_limits<double>::quiet_NaN(),
        std::numeric_limits<double>::infinity()
    };
    
    for (double val : values) {
        std::cout << "Value: " << val << std::endl;
        std::cout << "Is zero? " << FloatingPointComparator::isEffectivelyZero(val) << std::endl;
        std::cout << "Is special? " << FloatingPointComparator::isSpecial(val) << std::endl;
        std::cout << "Is exact zero? " << FloatingPointComparator::isExactZero(val) << std::endl;
        std::cout << "-------------------" << std::endl;
    }
}

int main() { 
    testFloatingPoint();
    testZeroSign();
    return 0;
}

为什么 exit() 函数不是线程安全的? https://www.zhihu.com/question/2278762213/

主要原因 exit语义等同于从main 返回,会涉及到资源释放等相关流程,自然引入竞争问题

避免全局资源释放,使用quick_exit

另外直接列一下各种exit区别 https://learn.microsoft.com/en-us/previous-versions/6wdz5232(v=vs.140)

  • exit 执行完整的 C 库终止过程,终止进程,并向主机环境提供提供的状态代码。
  • _Exit 执行最少的 C 库终止过程,终止进程,并向主机环境提供提供的状态代码。
  • _exit 执行最少的 C 库终止过程,终止进程,并向主机环境提供提供的状态代码。
  • quick_exit 执行快速 C 库终止过程,终止进程,并向主机环境提供提供的状态代码。
  • _cexit 执行完整的 C 库终止过程并返回给调用方。不终止进程。
  • _c_exit 执行最少的 C 库终止过程并返回给调用方。不终止进程。

VC++中 log10(1e-23f) 向下舍入时,结果错误。如何解决? https://www.zhihu.com/question/1790209844/

msvc的log10f 和gcc libm的log10f行为不一样。大概实现算法有区别

两种结果都是符合标准的,毕竟round

互动环节

熬夜看了英雄联盟S14总决赛,BLG vs T1, 2:3 本来2:1很有机会,但

第四局第五局的faker真的发挥了200%的水平,逆转夺冠

我看的很难受。尤其是赛后很多人踩脸

我不知道为什么全华班追求冠军是一种过错

什么开香槟不谦逊都成了罪过,怎么给自己打气也要批评?

星际韩国包圆,李培楠也努力追求冠军不放弃

街霸日本包圆,曾卓君也努力追求冠军

dota有wings,就算lgd ti10打的气人也是追求过了

唯独lol,我是真无法理解有这么多喜欢狗仗人势的观众

这样的环境,真令人遗憾

也许这次就相当于dota ti8吧 大家记住了水人泼高地记住了on出乱送,然后顺便骂捞批捞底座赛区杂交赛区

真令人遗憾


上一期 下一期

C++ 中文周刊 2024-10-28 第170期

28 Oct 17:22
90d0ab4
Compare
Choose a tag to compare

周刊项目地址

公众号

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

qq群 点击进入 满了加这俩 729240657 866408334

RSS

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

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


资讯

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

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

文章

On designing Tenseur, A C++ tensor library with lazy evaluation

这个人写了个类似eigen的库,介绍他的设计原理。其实主要是表达式模版,这里举一个例子

比如你有一个数组相加的场景,但想延迟计算

原型

/// @brief class representing a mathematical 3D vector
class Vec : public std::array<double, 3> {
  public:
    using std::array<double, 3>::array; 
    // inherit constructor (C++11)
    // see https://en.cppreference.com/w/cpp/language/using_declaration
};


/// @brief sum 'u' and 'v' into a new instance of Vec
Vec operator+(Vec const &u, Vec const &v) {
    Vec sum;
    for (size_t i = 0; i < u.size(); i++) {
        sum[i] = u[i] + v[i];
    }
    return sum;
}

Vec x = a + b + c 就有点低效了,中间结果优化不掉

我们的想法就是让operator+推迟,比如a+b生成一个VecSum 他本身不实际计算,直到多个VecSum合并成一个VecSum之后再计算

显然这种转发得用CRTP

template <typename E>
class VecExpression {
  public:
    static constexpr bool is_leaf = false;

    double operator[](size_t i) const {
        // Delegation to the actual expression type. This avoids dynamic polymorphism (a.k.a. virtual functions in C++)
        return static_cast<E const&>(*this)[i];
    }
    size_t size() const { return static_cast<E const&>(*this).size(); }
};
class Vec : public VecExpression<Vec> {
    std::array<double, 3> elems;

  public:
    static constexpr bool is_leaf = true;

    decltype(auto) operator[](size_t i) const { return elems[i]; }
    decltype(auto) &operator[](size_t i)      { return elems[i]; }
    size_t size()               const { return elems.size(); }

    // construct Vec using initializer list 
    Vec(std::initializer_list<double> init) {
        std::copy(init.begin(), init.end(), elems.begin());
    }

    // A Vec can be constructed from any VecExpression, forcing its evaluation.
    template <typename E>
    Vec(VecExpression<E> const& expr) {
        for (size_t i = 0; i != expr.size(); ++i) {
            elems[i] = expr[i];
        }
    }
};
template <typename E1, typename E2>
class VecSum : public VecExpression<VecSum<E1, E2> > {
  // cref if leaf, copy otherwise
  typename std::conditional<E1::is_leaf, const E1&, const E1>::type _u;
  typename std::conditional<E2::is_leaf, const E2&, const E2>::type _v;

  public:
    static constexpr bool is_leaf = false;

    VecSum(E1 const& u, E2 const& v) : _u(u), _v(v) {
        assert(u.size() == v.size());
    }
    decltype(auto) operator[](size_t i) const { return _u[i] + _v[i]; }
    size_t size()               const { return _v.size(); }
};
  
template <typename E1, typename E2>
VecSum<E1, E2>
operator+(VecExpression<E1> const& u, VecExpression<E2> const& v) {
   return VecSum<E1, E2>(*static_cast<const E1*>(&u), *static_cast<const E2*>(&v));
}

这样 a+b+c 的类型是 VecSum<VecSum<Vec, Vec>, Vec>

Vec x = a + b + c 会调用Vec(VecExpression<E> const& expr)

elems[i] = expr[i];会展开成elems[i] = a.elems[i] + b.elems[i] + c.elems[i]

这样就没有临时Vec对象了

教你 require用法

基本,require concept

template <typename T>
auto debug_output(const T&) { // default implementation
    return "???";
}

template <typename T>
    requires std::integral<T>
auto debug_output(const T& t) {
    // return a range of characters representing the integer value of t
}

template <typename T>
    requires std::floating_point<T>
auto debug_output(const T& t) {
    // return a range of characters representing the floating point value of t
}

用在constexpr里

template <typename Cont, typename Rng>
void cont_assign(Cont& cont, Rng&& rng) {
    cont.clear();

    if constexpr (requires { cont.reserve(std::ranges::size(rng)); }) {
        cont.reserve(std::ranges::size(rng));
    }
    for (auto&& elem : std::forward<Rng>(rng)) {
        cont.push_back(std::forward<decltype(elem)>(elem));
    }
}

requires requires, requires本身就是concept

template <typename T>
    requires requires(const T& t) { t.debug_output(); }
auto debug_output(const T& t) noexcept(noexcept(t.debug_output())) {
    return t.debug_output();
}

requires { requires } 用在constexpr里

template <std::ranges::forward_range Rng>
bool all_same(Rng&& rng) {
    if constexpr (requires { requires tc::constexpr_size<Rng>() <= 1; }) {
        return true;
    } else {
        … // loop as before
    }
}

Heterogeneous lookup in unordered C++ containers

透明查找,避免复制,默认不开,怎么开?看代码

struct stringly_hash
{
  using is_transparent = void;
  [[nodiscard]] size_t operator()(char const* rhs) const
  {
    return std::hash<std::string_view>{}(rhs);
  }
  [[nodiscard]] size_t operator()(std::string_view rhs) const
  {
    return std::hash<std::string_view>{}(rhs);
  }
  [[nodiscard]] size_t operator()(std::string const& rhs) const
  {
    return std::hash<std::string>{}(rhs);
  }
};

template <typename ValueType>
using unordered_string_map = std::unordered_map<
  std::string,
  ValueType,
  stringly_hash,
  std::equal_to<>
>;

咱们说过挺多次了

Zero or sign extend

讨论一种场景,无符号数/有符号数的扩展问题,比如给你一个11位的数字,你给扩展到32位

一种最简单的写法

int32 sign_extend(int32 val_11b) {
    int32 t = val_11b << (32 - 11);
    return t >> (32 - 11);
}

举例

// 假设输入的11位数是: 000 0010 1001 (原始值为41)

int32 t = val_11b << (32 - 11); // 左移21位

// 变成: 0010 1001 0000 0000 0000 0000 0000 0000

return t >> (32 - 11); // 算术右移21位

// 变成: 0000 0000 0000 0000 0000 0000 0010 1001 (仍然为41)

// 如果输入是负数,比如 110 0010 1001 (-215)

// 左移后: 0010 1001 0000 0000 0000 0000 0000 0000

// 算术右移后: 1111 1111 1111 1111 1111 1110 0010 1001 (-215)

>> 移动保留符号,所以这么玩也不会存在问题,但可能依赖数字实现(补码反码问题)

考虑位运算

int sign_extend(int val_11b) {
    return (val_11b & 0x3ff) - (val & 0x400);
}

0x3ff和400哪里来的?0x400是11位, 0x3ff是后10位

举例

// 对于正数 0010 1001 (41):

(41 & 0x3ff) - (41 & 0x400)

= 41 - 0 = 41

// 对于负数 1100 1001 (-215):

(0x329 & 0x3ff) - (0x329 & 0x400)

= 809 - 1024 = -215

当然还有更简洁的

int sign_extend(int val_11b) {
    return val - (val & 0x400) * 2;
}

举例
// 对于正数 0010 1001 (41):

41 - (41 & 0x400) * 2

= 41 - (0) * 2

= 41 - 0

= 41

// 对于负数 1100 1001 (原值 809):

809 - (809 & 0x400) * 2

= 809 - (0x400) * 2

= 809 - 2048

= -215

让我们从11位扩展到任意位(小于32)

int zero_or_sign_extend(int val, int sign_bit) {
    return val - (val & sign_bit) * 2;
}

当然也可以异或

int zero_or_sign_extend(int val, int sign_bit) {
    return (val ^ sign_bit) - sign_bit;
}

举例

// 对于11位正数 0010 1001 (41), sign_bit = 0x400:

(41 ^ 0x400) - 0x400

= 1065 - 1024

= 41

// 对于11位负数 1100 1001 (809), sign_bit = 0x400:

(809 ^ 0x400) - 0x400

= 809 - 1024

= -215

// 对于8位数:
// 正数 0100 0001 (65), sign_bit = 0x80:

(65 ^ 0x80) - 0x80

= 193 - 128

= 65

// 负数 1100 0001 (193), sign_bit = 0x80:

(193 ^ 0x80) - 0x80

= 65 - 128

= -63

Inserting a 0 bit in the middle of a value

这个背景可以不提,简单说就是给一个二进制中间差一个0,很妙的办法,直觉来说怎么写?

首先给位置分两段,低位不变,高位置移动一位,然后拼起来,对不对

uint64 insert_zero_bit(uint64 value, int pos) {
    uint64 bottom_mask = (1u64 << pos) - 1;
    uint64 top_mask = ~bottom_mask;

    uint64 bottom_bits = value & bottom_mask;
    uint64 top_bits = value & top_mask;
    return bottom_bits | (top_bits << 1);
}

代码也很直观,咱们拿一个例子带入一下

假如 1 0 1 1 0 1 0 1 -> 1 0 1 1 0 0 1 0 1

第四位插个0 pos=3

首先拿到bottom_mask 0 0 0 0 0 0 0 1左移3位减一 0 0 0 0 0 1 1 1

top mask就是 1 1 1 1 1 0 0 0

bottom_bits就是 1 0 1 1 0 1 0 1 保留后三位 0 0 0 0 0 1 0 1

top_bits 就是1 0 1 1 0 1 0 1 保留前五位 1 0 1 1 0 0 0 0

然后top bit左移一位组合1 0 1 1 0 0 0 0 0 | 0 0 0 0 0 1 0 1 -> 1 0 1 1 0 0 1 0 1

这里提到了一个优化的写法

我们要做的就是高位移动一位,就不要考虑低位了,还要算来算去

最简单的移动方法就是自己加自己对不对? 1 + 1 -> 10

那我高位加自己不就解决了?

这也就是优化算法的原理

uint64 insert_zero_bit(uint64 value, int pos) {
    uint64 top_mask = ~0u64 << pos;
    return value + (value & top_mask);
}

首先我们找到高位,然后高位相加等于移动一位,然后低位没加,不变

很巧妙的思路,就是不能一次性加多个0,得一点一点加

当然同理,我们可以弄一个去掉0 的算法

uint64 remove_zero_bit(uint64 value, int pos) {
    uint64 top_mask = ~0u64 << pos;
    return value - ((value & top_mask) >> 1);
}

注意我们知道指定位置是0,所以移动没啥影响,如果指定去除位置的值,就不能这么简单的算了

一切的前提是知道指定pos是0 的前提下展开的

Triaging clang C++ frontend bugs

教你处理clang前端bug/修复指引

Implementing Trivial Relocation in Library

介绍trivial relocation基于反射的实现。复杂。看一乐,这个之前提到过,是两个提案实现方法不同,一直在吵架

Placeholder substitution in the preprocessor

用宏实现了magic enum类似手法。这拖代码屎一样 godbolt

开源项目介绍

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

[上一期...

Read more

C++ 中文周刊 2024-10-22 第169期

28 Oct 17:21
531b78a
Compare
Choose a tag to compare

周刊项目地址

公众号

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

qq群 点击进入 满了加这俩 729240657 866408334

RSS

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


资讯

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

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

十月邮件列表

重点还是反射

让status更好的move,避免使用误用, 一个pr观察

起因

if (auto&& status = functionReturningArrowResult().status(); status.ok())
  return 0;
return -1;

显然status是调用出现问题,status()返回的不是值而是const Status& ,而函数执行完了,所以这个Status已经析构,UB

这个问题和range for loop中的悬垂引用问题一样。

这里引入了一个解决办法,支持多种status()方法

constexpr const Status& status() const& { return status_; }
Status status() && { return status_; }

status() 支持两种 分别是普通引用和万能引用,及时的把值复制出来

这种场景我以前也介绍过,但是不单单这么简单,这个PR还改动了别的地方

  /// Helper method for implementing Status returning functions in terms of semantically
  /// equivalent Result returning functions. For example:
  ///
  /// Status GetInt(int *out) { return GetInt().Value(out); }
  template <typename U, typename E = typename std::enable_if<
                            std::is_constructible<U, T>::value>::type>
  Status Value(U* out) && {
    if (!ok()) {
-      return status();
+      return std::move(*this).status();
    }
    *out = U(MoveValueUnsafe());
    return Status::OK();
  }

这算是一个挺妙的改动,std::move(*this)强制右值,这样就会调用status() && 从而帮助编译器优化潜在的悬垂场景

这种增加&&方法不是简单的增加一个就完了,还有其余的影响也需要覆盖到。这个PR算是见到一个思路

文章

Detect C++ Memory Leaks with ALSan: Attachable Leak Sanitizer - Bojun Seo - C++Now 2024

觉得 -fsanitize=leak 需要preload重跑

麻烦,自己写了个基于ebpf的

What is faster: vec.emplace_back(x) or vec[x] ?

emplace_back没有向量化优势

Iterating through matched characters in modern C++

查找,代码

std::string data = load_file_content("data.html");
std::string_view targets = "<&\r\0";
auto start = data.begin();
auto end = data.end();
while (start != end) {
  start = std::find_first_of(start, end, targets.begin(),
       targets.end());
  if (start != end) { 
    /* you are pointing at start */
  }
}

也可以这样写

size_t location = 0;
while ((location = data.find_first_of(targets, location)) !=
  std::string::npos) {
  // matched character at data[location]
  location++;
}

还可以用range

auto matched_characters =
  data | std::views::filter([](char c) {
    return c == '<' | c == '&' | c == '\r' | c == '\0';
});
for (const char &c : matched_characters) {
  /* you hold a reference to a matched character */
};

甚至可以这样写

auto target_finder = [](auto& data,
    auto& targets) -> std::generator<const char *> {
  auto start = data.begin();
  auto end = data.end();
  while (start != end) {
    start = std::find_first_of(start, end, targets.begin(),
                               targets.end());
      if (start == end) {
        co_return;
      }
      co_yield start;
      start++;
    }
};

for (auto match : target_finder(data, targets)) {
   /* match is a matched character*/
};

性能比较差还是算了

A Two Dimensional Low Discrepancy Shuffle Iterator (+Random Access & Inversion)

Scaling Points In a Specific Direction

看不懂,图形学的,标记一个TODO

Why don’t compilers warn for const T f()?

告警太多,干脆忽略,反正没用

Replace strings by views when you can

view传值更省,const string&潜在拷贝风险/指针不能彻底优化

他的这个例子是string table vs 巨大array + stringview维护,stringview占优势。代码就不贴了

Reflection in C++26

直接贴代码

简单例子

#include <iostream>
#include <cassert>
#include <concepts>

int main() {
    constexpr auto r = ^int;
    typename[:r:] x = 42;       // Same as: int x = 42;
    typename[:^char:] c = '*';  // Same as: char c = '*';

    static_assert(std::same_as<decltype(x), int>);
    static_assert(std::same_as<decltype(c), char>);
    assert(x == 42);
    assert(c == '*');
}

使用^拿到类型 std::meta::info,使用[: :] 使用类型

一个enum转string例子

#include <iostream>
#include <experimental/meta>
#include <string>
#include <type_traits>


template<typename E>
  requires std::is_enum_v<E>                      // (1)
constexpr std::string enum_to_string(E value) {
  std::string result = "<unnamed>";
  [:expand(std::meta::enumerators_of(^E)):] >>    // (2)
  [&]<auto e>{
    if (value == [:e:]) {
      result = std::meta::identifier_of(e);       // (3)
    }
  };
  return result;
}

template <typename E>
  requires std::is_enum_v<E>                           
constexpr std::optional<E> string_to_enum(std::string_view name) {
  template for (constexpr auto e : std::meta::enumerators_of(^E)) {
    if (name == std::meta::identifier_of(e)) {                     
      return [:e:];
    }
  }

  return std::nullopt;
}

int main() {
    enum Color { red, green, blue };
    std::cout << "enum_to_string(Color::red): " << enum_to_string(Color::red) << '\n';
}

(2) 不好懂,你就当一种特殊语法好了,执行指定lambda

内置的metafunction非常多

namespace std::meta {
  using info = decltype(^::);

  template <typename R>
  concept reflection_range = /* see above */;

  // name and location
  consteval auto identifier_of(info r) -> string_view;
  consteval auto u8identifier_of(info r) -> u8string_view;

  consteval auto display_string_of(info r) -> string_view;
  consteval auto u8display_string_of(info r) -> u8string_view;

  consteval auto source_location_of(info r) -> source_location;

  // type queries
  consteval auto type_of(info r) -> info;
  consteval auto parent_of(info r) -> info;
  consteval auto dealias(info r) -> info;

  // object and value queries
  consteval auto object_of(info r) -> info;
  consteval auto value_of(info r) -> info;

  // template queries
  consteval auto template_of(info r) -> info;
  consteval auto template_arguments_of(info r) -> vector<info>;

  // member queries
  consteval auto members_of(info type_class) -> vector<info>;
  consteval auto bases_of(info type_class) -> vector<info>;
  consteval auto static_data_members_of(info type_class) -> vector<info>;
  consteval auto nonstatic_data_members_of(info type_class) -> vector<info>;
  consteval auto subobjects_of(info type_class) -> vector<info>;
  consteval auto enumerators_of(info type_enum) -> vector<info>;

  // member access
  struct access_context {
    static consteval access_context current() noexcept;
    consteval access_context() noexcept;
  };

  consteval auto is_accessible(
          info r,
          acess_context from = access_context::current());

  consteval auto accessible_members_of(
          info target,
          access_context from = access_context::current()) -> vector<info>;
  consteval auto accessible_bases_of(info target,
          info target,
          access_context from = access_context::current()) -> vector<info>;
  consteval auto accessible_nonstatic_data_members_of(
          info target,
          access_context from = access_context::current()) -> vector<info>;
  consteval auto accessible_static_data_members_of(
          info target,
          access_context from = access_context::current()) -> vector<info>;
  consteval auto accessible_subobjects_of(
          info target,
          access_context from = access_context::current()) -> vector<info>;

  // substitute
  template <reflection_range R = initializer_list<info>>
  consteval auto can_substitute(info templ, R&& args) -> bool;
  template <reflection_range R = initializer_list<info>>
  consteval auto substitute(info templ, R&& args) -> info;

  // reflect_invoke
  template <reflection_range R = initializer_list<info>>
  consteval auto reflect_invoke(info target, R&& args) -> info;
  template <reflection_range R1 = initializer_list<info>, reflection_range R2 = initializer_list<info>>
  consteval auto reflect_invoke(info target, R1&& tmpl_args, R2&& args) -> info;

  // reflect expression results
  template <typename T>
    consteval auto reflect_value(T value) -> info;
  template <typename T>
    consteval auto reflect_object(T& value) -> info;
  template <typename T>
    consteval auto reflect_function(T& value) -> info;

  // extract
  template <typename T>
    consteval auto extract(info) -> T;

  // other type predicates (see the wording)
  consteval auto is_public(info r) -> bool;
  consteval auto is_protected(info r) -> bool;
  consteval auto is_private(info r) -> bool;
  consteval auto is_virtual(info r) -> bool;
  consteval auto is_pure_virtual(info entity) -> bool;
  consteval auto is_override(info entity) -> bool;
  consteval auto is_final(info r) -> bool;
  consteval auto is_deleted(info entity) -> bool;
  consteval auto is_defaulted(info entity) -> bool;
  consteval auto is_explicit(info entity) -> bool;
  consteval auto is_noexcept(info entity) -> bool;
  consteval au...
Read more

C++ 中文周刊 2024-09-07 第168期

28 Oct 17:20
9029ae3
Compare
Choose a tag to compare

周刊项目地址

公众号

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

qq群 点击进入

RSS

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

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


资讯

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

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

文章

隆重介绍proxy 3.0版本,更好用的fat pointer库

看个乐呵,其实还是要面向需求,这个概念起码16/17年就有了,隔壁rust有用,folly::Poly没听说有谁用

为什么 const 无法让 C 代码跑得更快?

省流,约束自己的,编译器足够聪明能分析出优化点,用const指引没啥帮助

使用static 快十倍

看看作者给的B代码

uint64_t modulus = 1ULL << 31; // 2^31
// static uint64_t modulus = 1ULL << 31; // 2^31
uint64_t loop(uint64_t N, uint64_t S, uint64_t P, uint64_t Q) {
  for (uint64_t i = 0; i < N; i++) {
    S = (S*P+Q) % modulus;
  }
  return S;
}

把modulus改成static就内联了。

不是哥们,你直接用宏/constexpr得了呗,又不改动,全局变量影响多大没有数?

回顾shared ptr实现

template<typename T>
class shared_ptr {
  ctrl_blk_base* ctrl_blk_{};
  T*             t_{};

  shared_ptr(ctrl_blk_with_storage<T>* cb)
  : shared_ptr{cb, cb->get()}
  {}

  shared_ptr(ctrl_blk_base* cb, T* t)
  : ctrl_blk_{cb}
  , t_{t}
  {}

  template<typename U, typename... Args>
  friend shared_ptr<U> make_shared(Args&&... vals);

public:
  shared_ptr() = default;

  shared_ptr(T* t)
  : shared_ptr{new ctrl_blk<T>{t}, t}
  {}

  ~shared_ptr()
  {
    if(ctrl_blk_) { ctrl_blk_->release_shared(); }
  }

  shared_ptr(const shared_ptr& rhs)
  : ctrl_blk_{rhs.ctrl_blk_}
  , t_{rhs.t_}
  {
    if(ctrl_blk_) { ctrl_blk_->add_shared(); }
  }

  shared_ptr(shared_ptr&& rhs)
  : ctrl_blk_{rhs.ctrl_blk_}
  , t_{rhs.t_}
  {
    rhs.ctrl_blk_ = nullptr;
    rhs.t_        = nullptr;
  }

  shared_ptr& operator=(const shared_ptr& rhs)
  {
    shared_ptr{rhs}.swap(*this);  // forward to copy ctor
    return *this;
  }

  shared_ptr& operator=(shared_ptr&& rhs)
  {
    shared_ptr{std::move(rhs)}.swap(*this);  // forward to move-ctor
    return *this;
  }

  void swap(shared_ptr& rhs)
  {
    std::swap(t_, rhs.t_);
    std::swap(ctrl_blk_, rhs.ctrl_blk_);
  }
};

template<typename T, typename... Args>
shared_ptr<T> make_shared(Args&&... vals)
{
  return new ctrl_blk_with_storage<T>(std::forward<Args>(vals)...);
}

struct ctrl_blk_base {
  std::atomic_uint64_t shared_ref_count_{1};

  void add_shared() { ++shared_ref_count_; }
  auto dec() { return --shared_ref_count_; }

  virtual void release_shared() = 0;
};

template<typename T>
struct ctrl_blk : ctrl_blk_base {
  T* data_;

  explicit ctrl_blk(T* data)
  : ctrl_blk_base{}
  , data_{data}
  {}

  void release_shared() override
  {
    if(0 == dec()) {
      delete data_;
      delete this;  // self delete
    }
  }
};

template<typename T>
struct ctrl_blk_with_storage : ctrl_blk_base {
  T in_place_;

  template<typename... Args>
  explicit ctrl_blk_with_storage(Args&&... vals)
  : ctrl_blk_base{}
  , in_place_{std::forward<Args>(vals)...}
  {}

  T* get() { return &in_place_; }

  void release_shared() override
  {
    if(0 == dec()) {
      delete this;  // self delete
    }
  }
};

非常简单,大家看懂了吗

RealtimeSanitizer

llvm引入了新的sanitizer, RTSan 标记了noblocking的函数只要监测到路径中存在 malloc, free, pthread_mutex_lock,就会报错

看样例

#include <vector>

void violation() [[clang::nonblocking]]{
  std::vector<float> v;
  v.resize(100);
}

int main() {
  violation();
  return 0;
}

//clang++ -fsanitize=realtime -g example_realtime_violation.cpp

输出

clang++ -fsanitize=realtime -g example_realtime_violation.cpp

./a.out
Real-time violation: intercepted call to real-time unsafe function `malloc` in real-time context! Stack trace:

0 0x000102893034 in __rtsan::PrintStackTrace() rtsan_stack.cpp:45
1 0x000102892e64 in __rtsan::Context::ExpectNotRealtime(char const*) rtsan_context.cpp:78
2 0x00010289397c in malloc rtsan_interceptors.cpp:286
3 0x000195bd7bd0 in operator new(unsigned long)+0x1c (libc++abi.dylib:arm64+0x16bd0)
4 0x5c7f00010230f07c  (<unknown module>)
5 0x00010230f058 in std::__1::__libcpp_allocate[abi:ue170006](unsigned long, unsigned long) new:324
6 0x00010230effc in std::__1::allocator<float>::allocate[abi:ue170006](unsigned long) allocator.h:114
 ... snip ...
10 0x00010230e4bc in std::__1::vector<float, std::__1::allocator<float>>::__append(unsigned long) vector:1162
11 0x00010230dcdc in std::__1::vector<float, std::__1::allocator<float>>::resize(unsigned long) vector:1981
12 0x00010230dc28 in violation() main.cpp:5
13 0x00010230dd64 in main main.cpp:9
14 0x0001958960dc  (<unknown module>)
15 0x2f557ffffffffffc  (<unknown module>)

还是很准的,可以更好的控制快速路径中的可能阻塞的函数调用

如何使用?最新llvm

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

使用bfloat16来压缩浮点数

lemire新活,

并不需要那么的精确,可以使用brain float 16 显然计算带宽四倍,如果用上SIMD,那速度可以更快

#include <immintrin.h>
#include <cstddef>
#include <cstdint>

void to_float16(uint16_t *dst, const double *src, size_t length) {
    size_t i = 0;
    __mmask8 mask;

    // Process 8 elements at a time
    for (; i + 7 < length; i += 8) {
        // Load 8 double-precision floats
        __m512d src_vec = _mm512_loadu_pd(&src[i]);

        // Convert to 16-bit floats with rounding
        __m128bh dst_vec = _mm256_cvtneps_pbh(_mm512_cvt_roundpd_ps(src_vec, _MM_FROUND_TO_NEAREST_INT |_MM_FROUND_NO_EXC));

        // Store the result
        _mm_storeu_si128((__m128i*)&dst[i], *(__m128i*)&dst_vec);
    }

    // Handle remaining elements
    if (i < length) {
        // Create a mask for the remaining elements
        mask = (1 << (length - i)) - 1;

        // Load remaining double-precision floats
        __m512d src_vec = _mm512_maskz_loadu_pd(mask, &src[i]);

        // Convert to 16-bit floats with rounding
        __m128bh dst_vec = _mm256_cvtneps_pbh(_mm512_cvt_roundpd_ps(src_vec, _MM_FROUND_TO_NEAREST_INT |_MM_FROUND_NO_EXC));

        // Store the result with masking
        _mm_mask_storeu_epi16(&dst[i], mask, *(__m128i*)&dst_vec);
    }
}


void from_float16(double *dst, const uint16_t *src, size_t length) {
    size_t i = 0;
    __mmask8 mask;

    // Process 8 elements at a time
    for (; i + 7 < length; i += 8) {
        // Load 8 half-precision floats
        __m128i src_vec = _mm_loadu_si128((__m128i*)&src[i]);

        // Convert to double-precision floats
        __m512d dst_vec = _mm512_cvtps_pd(_mm256_cvtpbh_ps(*(__m128bh*)&src_vec));

        // Store the result
        _mm512_storeu_pd(&dst[i], dst_vec);
    }

    // Handle remaining elements
    if (i < length) {
        // Create a mask for the remaining elements
        mask = (1 << (length - i)) - 1;

        // Load remaining half-precision floats
        __m128i src_vec = _mm_maskz_loadu_epi16(mask, &src[i]);

        // Convert to double-precision floats
        __m512d dst_vec = _mm512_cvtps_pd(_mm256_cvtpbh_ps( *(__m128bh*)&src_vec));

        // Store the result with masking
        _mm512_mask_storeu_pd(&dst[i], mask, dst_vec);
    }
}

作者测试压缩2亿条每秒,解压0.9亿条每秒

考虑geo/AI之类的不精确场景,这种压缩带来的提升是非常迅速的

开源项目介绍

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

简单代码段分享

一个c的enum to string

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

#define NUMARGS(...)  (sizeof((int[]){__VA_ARGS__})/sizeof(int))

#define ENUM_TO_STRING(ENUM_NAME, ...)                                                   \
    enum ENUM_NAME { __VA_ARGS__ };                                                      \
    char ENUM_NAME##_strings[] = #__VA_ARGS__ ;                                          \
    long ENUM_NAME##strings_indices[NUMARGS(__VA_ARGS__)];                               \
    char *ENUM_NAME##_to_string(enum ENUM_NAME value) {                                  \
        static int init = 0;                                                             \
        if(init == 0){                                                                   \
            int n = 0;                                                                   \
            ENUM_NAME##strings_indices[n++] = 0;                                         \
            char* curr_pos = strchr(ENUM_NAME##_strings,',');                            \
            while(curr_pos){                                                             \
                *curr_pos = '\0';                                                        \
                ENUM_NAME##strings_indices[n++]= (++curr_pos - ENUM_NAME##_strings);     \
                curr_pos = strchr(curr_pos,',');                                         \
            }                                                                            \
            init++;                                                                      \
        }                                                                                \
        return  (char *)ENUM_NAME##_strings+ENUM_NAME##strings_indices[value];           \
    }

/* Usage just create the enum */
ENUM_TO_STRING(Color,RED,GREEN,BLUE,VIOLET)

int main(void) 
{
    printf("%s...
Read more

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 指正