C++ 中文周刊 第32期

reddit/hackernews/lobsters/meetingcpp摘抄一些c++动态

本期把国庆节这两周的断更补上

周刊项目地址在线地址知乎专栏 腾讯云+社区

欢迎投稿,推荐或自荐文章/软件/资源等,请提交 issue


资讯

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

OSDT Weekly 2021-09-22 第117期

OSDT Weekly 2021-09-22 第118期

十月了,cppcon要在十月底。到时候会有很多视频分享

文章

多维数组的支持!c++23提案。说不定会过。Compiler Explorer试玩儿

template <class T, auto Dimensions> class mdarray2 {
public:
  template <class I1, class I2> constexpr T &operator[](I1 i1, I2 i2) {
    return vs_[i1][i2];
  }

private:
  std::array<std::array<T, 2>, Dimensions> vs_{};
};

int main() {
  mdarray2<int, 2> a{};
  a[1, 1] = 42;
  assert(0 == (a[0, 0]));
  assert(42 == (a[1, 1]));
}

一个demangle小工具

xmake是一个类似bazel的编译构建工具,这里列了一个用xmake组织导入库的方法

range based 遍历涉及到临时变量的时候会有一些问题。

这种场景要了解,不踩坑

for(auto e :getTmpColl());// 就这个可以,其他场景都会挂
for(auto e :getTmpColl().getRef());
for(char c :getVectorOfStrings()[0]);     
for(auto e :getOptionalVector().value());
for(auto e :std::get<0>(getTuple()));
for(auto e :std::span{getColl().data(), 5});

这个提案是建议标准委员会去修复,不过没啥进展,这里是周知一下这个坑

介绍了一些使用libcurl遇到的错误

  1. 不看文档
  2. 不检查返回值
  3. 不设置verbose日志看不全
  4. 没调用global_init
  5. 重定向设置?
  6. 不要让用户设定/拼接网址
  7. 不要禁止证书
  8. 检查\0
  9. c++ string和c string要注意区分
  10. 多线程问题,global_init不是线程安全的
  11. CURLOPT_NOSIGNAL默认是设置的,会忽略所有信号
  12. 用-DCURL_STATICLIB,尽量静态编译

还有一些c++相关的就不介绍了。基本函数使用

这个之前讲过,return一个值是不需要std::move就有copy elision 复制消除优化的,但是有些场景就优化不到,还需要std::move

比如这种

Foo update( Foo f, int const value )
{
    f.value = value;
    return f;
}

改了一下,就用不上copy elision了。这里的解决方案就是强制调用std::move(f)

c++20引入simpler implicit moves这个提案来修正,目前clang gcc都支持了

之前说过google 浏览器内核团队做的GC,这个文章详细的介绍了olipan这个GC的设计,以及相关的资料整理

用any_of和none_of,all_of实现同样的语义,如果要判断 != end(), 用 any_of, 如果判断 == end(), 用 none_of,如果用find_if_not 且判断== end() 使用 all_of

比较经典的overload trick了。再复读一次,代码这样

template<typename ... Ts>                                                 // (7) 
struct Overload : Ts ... { 
    using Ts::operator() ...;
};
template<class... Ts> Overload(Ts...) -> Overload<Ts...>;

std::vector<std::variant<char, long, float, int, double, long long>>  // (1)    
  vecVariant = {5, '2', 5.4, 100ll, 2011l, 3.5f, 2017};

auto TypeOfIntegral = Overload {                                      // (2)
  [](char) { return "char"; },
  [](int) { return "int"; },
  [](unsigned int) { return "unsigned int"; },
  [](long int) { return "long int"; },
  [](long long int) { return "long long int"; },
  [](auto) { return "unknown type"; },
};

for (auto v : vecVariant) {                                           // (3)
  std::cout << std::visit(TypeOfIntegral, v) << '\n';
}

std::cout << '\n';

std::vector<std::variant<std::vector<int>, double, std::string>>      // (4)
  vecVariant2 = { 1.5, std::vector<int>{1, 2, 3, 4, 5}, "Hello "};

auto DisplayMe = Overload {                                           // (5)
  [](std::vector<int>& myVec) { 
    for (auto v: myVec) std::cout << v << " ";
    std::cout << '\n'; 
  },
  [](auto& arg) { std::cout << arg << '\n';},
};

for (auto v : vecVariant2) {                                         // (6)
  std::visit(DisplayMe, v);
}

std::to_string() / std::stingstream / Boost’s lexical_cast()

复习一下UDL

using namespace std::literals::chrono_literals;

auto threeSeconds = 3s;
auto tenMinutes = 10min;
auto twoHours = 2h;

auto oneMillisecond = 1ms;
auto oneMicroSecond = 1us;
auto oneNanoSecond = 1ns;
using namespace std::literals::string_view_literals;

auto myStringView = "hello"sv;

这个是作者的一些代码review经验。其实根据google代码规范来review就好

同样一个算法,float和int的效果差距非常大

void hash_add(size_t& hash, size_t new_hash)
{
    // taken from boost::hash_combine
    hash ^= new_hash + 0x9e3779b9 + (hash << 6) + (hash >> 2);
}

size_t myhash_float(float x, float y, float z)
{
    size_t h = /* some fixed seed */;
    hash_add(h, std::bit_cast<uint32_t>(x));
    hash_add(h, std::bit_cast<uint32_t>(y));
    hash_add(h, std::bit_cast<uint32_t>(z));
    return h;
}

size_t myhash_int(float x, float y, float z)
{
    size_t h = /* some fixed seed */;
    hash_add(h, int32_t(256 * x));
    hash_add(h, int32_t(256 * y));
    hash_add(h, int32_t(256 * z));
    return h;
}

float 有3%的碰撞率int只有%0.2,所以有严重的性能问题

作者用xorshirf和xxhash,放在float数据集上,基本没有碰撞。所以还是这个算法的问题

如何估算碰撞?

template <class Map> 
double unordered_map_badness(Map const& map)
{
    auto const lambda = map.size() / double(map.bucket_count());

    auto cost = 0.;
    for (auto const& [k, _] : map)
        cost += map.bucket_size(map.bucket(k));
    cost /= map.size();

    return std::max(0., cost / (1 + lambda) - 1);
}

针对const的operator(),有了个新的语法糖提案

struct ByAuthor {
    bool operator()(const Book& a, const Book& b) const {
        return a.author() < b.author();
    }
};
// 新的写法
struct ByAuthor {
    static bool operator()(const Book& a, const Book& b) {
        return a.author() < b.author();
    }
};

如何用上这个特性又不改代码,作者给了个宏

#if __cplusplus > 202002L
 #define CALL_OPERATOR(...) static operator()(__VA_ARGS__)
#else
 #define CALL_OPERATOR(...) operator()(__VA_ARGS__) const
#endif

struct ByAuthor {
    bool CALL_OPERATOR(const Book& a, const Book& b) {
        return a.author() < b.author();
    }
};

读这篇文章,需要了解循环优化的基本原理,也就是这篇文章Loop Optimizations: how does the compiler do it?

先简单介绍一下基本原理

  1. 循环变量全放到寄存器
  2. 去掉多余的计算
  3. 循环不变量优化
for (int i = 0; i < n; i++) {
    switch (operation) {
        case ADD: a[i]+= x * x; break;
        case SUB: a[i]-= x * x; break;
    }
}

auto x_2 = x * x;
if (operation == ADD) {
    for (int i = 0; i < n; i++) {
        a[i] += x_2;
    }
} else if (operation == SUB) {
    for (int i = 0; i < n; i++) {
        a[i] -= x_2;
    }
}
  1. 替换简单的指令,指令改写
  2. 循环展开
  3. 流水线执行pipeline
  4. 向量话
  5. Loop Interchange 修改访问方式让内存访问更友好,感觉有点像循环展开的另一种表达?
  6. Loop Distribution 拆开循环

比如

for (int i = 0; i < n; i++) {
    a[i] = a[i - 1] * b[i];
    c[i] = a[i] + e[i];
}
//
for (int i = 0; i < n; i++) {
    a[i] = a[i - 1] * b[i];
}
for (int i = 0; i < n; i++) {
    c[i] = a[i] + e[i];
}
  1. Loop Fusion合并循环,上面的反过来

循环的性能杀手就是循环内部的函数调用以及指针引用

函数调用

for (int i = 0; i < n; i++) {
   ...
   if (debug) { 
       printf("The data is NaN\n"); 
   }
}

这种,编译器会生成两个循环,一个有调用的一个没调用的

指针问题

for (int i = 0; i < n; i++) {
   b[i] = 0;
   for (int j = 0; j < n; j++) {
      b[i] += a[i][j];
   }
}
//bad
double** a = new double*[n];
double* b = new double[n];
for (int i = 0; i < n; i++) {
    a[i] = b;
}

这种没法做优化,因为指针没法做分析,不能确定是不是地址被改了

解决方案__restrict__

for (int i = 0; i < n; i++) {
   double sum = 0;
   double* __restrict__ a_row = a[i];
   for (int j = 0; j < n; j++) {
      sum += a_row[j];
   }
   b[i] = sum;
}

循环展开优化怎么做

llvm有 #pragma clang loop unroll

向量化怎么做?

#pragma clang loop vectorize(enable)

#pramga omp simd得装openmp -fopenmp -fopenmp-simd

如果编译器没做循环展开,哪里出了问题?

没看懂要啥需求

讨论如何让模版参数的入参必须是const的

视频

不让用const_cast,用std::as_const来替代

其实是老生常谈了。错误处理场景,用goto还是很干净的。

一个谷歌浏览器开发者做的入门视频。没看

分析虚函数代价,结论,如果是长的函数,虚函数和非虚函数开销差距不大,如果是短的快的函数,虚函数浪费18%左右。如果有机会,可以研究一下他的测试方法复现一下

项目


本文永久链接

看到这里或许你有建议或者疑问或者指出错误,请留言评论! 多谢! 你的评论非常重要!也可以帮忙点赞收藏转发!多谢支持! 觉得写的不错那就给点吧, 在线乞讨 微信转账