C++ 中文周刊 第95期

周刊项目地址

公众号

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

提交 issue

新年第一周


资讯

时隔多年,TIOBE年度语言又是c++了。看个乐

文章

__builtin_clzll.作者还讲了一些优化的东西,涨涨见识

总结了2022年来c++的各种进展,很全面了

直接看代码

constexpr auto foo() {
  static constexpr auto value = 42; // error in C++20, okay in C++23
  return value;
}

乍看没啥用

给个例子

template<char... Cs>
[[nodiscard]] constexpr auto to_string_view(){
    static constexpr char p[sizeof...(Cs)] = {Cs...};
    return std::string_view(p, sizeof...(Cs));
}

我发现这个特性和例子差距有点像 你已经学会1+1=2了来证明黎曼猜想吧那种感觉

感谢热心网友CappucimoOffitial补充

constexpr 函数里允许 static constexpr 变量有一些更有意义的用途…… 譬如我要做个 std::visit 的类似物,通常需要一个手工虚表。而这个虚表理论上只在函数体里有用,不应暴露到外面。 C++23 的改动解决了“把表限制到仅在函数内部使用”和“支持常量求值”不可兼得的问题。

实现条件判断版本的none of any of

auto none_of(auto pred, auto... ts) {
    const auto a = std::array{ts...};
    return std::none_of(std::cbegin(a), std::cend(a), pred);
}

auto before(int a, int b, int c) {
    if (a != 2 and b != 2 and c != 2) {
      return 42;
    }
    return 0;
}

auto after(int a, int b, int c) {
    if (none_of([](auto x) { return x == 2; }, a, b, c)) {
      return 42;
    }
    return 0;
}

anyof怎么实现?

#include <utility>
#include <tuple>
#include <concepts>

template<class ... Args>
struct Comp {
    template<class T>
    auto operator==(T && other) {
        return std::apply(
            [&other](auto &&... data) {
                return (((std::equality_comparable_with<decltype(data), T>) && data == other) || ...);
            },
            this -> data
        );
    }
    std::tuple<Args...> data {};
};

template<class ...Args>
auto any_of(Args && ... args)  {
    return Comp<Args...> {
        std::make_tuple(std::forward<Args>(args)...)
    };
  
}

和上面差距有点大

实现了一个gcc插件支持 [[invariant]]特性,代码在这里https://github.com/GavinRay97/gcc-invariant-plugin

博客记录了开发插件的方法和过程,挺有意思的

gdb调试和shell交互,复杂

介绍他的折腾

就是 std::execution::par/std::execution::par_unseq这玩意

std::vector<size_t> indices(num_pixels);
 // initialize indices with 0, 1, ..
std::iota(indices.begin(), indices.end(), 0); // needs <numeric>

std::transform( // needs <algorithm>
    std::execution::par, // <-- needs <execution>
    indices.begin(), indices.end(), pixels.begin(), 
    [](size_t index){
        return expensive_calculation(index);
    }
);

老生常谈讲智能指针那套东西

检查有没有std::hash特化

struct HasStdHash {
private:
  template <class T, class Dummy = decltype(std::hash<T>{})>
  static constexpr bool exists(int) {
    return true;
  }

  template <class T>
  static constexpr bool exists(char) {
    return false;
  }

public:
  template <class T>
  static constexpr bool check() {
    return exists<T>(42);
  }
};

std::cout << "Does std::string have std::hash? " << HasStdHash::check<std::string>();

能不能更泛化一点?

template <template <class... InnerArgs> class Tmpl>
struct IsSpecialized {
private:
  template <class... Args,
          class dummy = decltype(Tmpl<Args...>{}.~Tmpl<Args...>())>
  static constexpr bool exists(int) {
    return true;
  }

  template <class... Args>
  static constexpr bool exists(char) {
    return false;
  }

public:
  template <class... Args>
  static constexpr bool check() {
    return exists<Args...>(42);
  }
};

但这个代码对于这种场景是无效的

template<class T> struct SomeStruct;
bool test1 = IsSpecialized<SomeStruct>::check<std::string>();

template<> struct SomeStruct<std::string> {};
bool test2 = IsSpecialized<SomeStruct>::check<std::string>();

后面又讨论了一通ADL检测,我已经看不懂了

随便看看

cuda代码调优记录。看不懂

模拟器里玩模拟器,看不懂

msvc一个bug

#include <iostream>
#include <string>

template <typename T>
T galactus_the_devourer_of_const(const T& v) {
    return false ? std::move(T{}) : std::move(v);
}

int main() {
    const std::string food = "asdf";
    std::cout << "before: " << food << '\n';
    galactus_the_devourer_of_const(food);
    std::cout << "after:  " << food << '\n';
    return 0;
}

// before: asdf
// after:  

莫名其妙的被move了。解决办法,/permissive-,默认是 /permissive

感兴趣可以看看

介绍这个库 https://github.com/iboB/xmem

可以分析智能指针引用,用法就不贴了。和hook malloc那种类似。不过要做很多很多适配代码

看代码

namespace winrt::Contoso::implementation {
    struct ItemCollection : ItemCollectionT<ItemCollection>{
        template<typename...Args> auto First(Args&&... args) {
            return m_items.First(args...);
        }

        template<typename...Args> auto GetAt(Args&&... args) {
            return m_items.GetAt(args...);
        }

        template<typename...Args> auto Size(Args&&... args) {
            return m_items.Size(args...);
        }

        template<typename...Args> auto IndexOf(Args&&... args) {
            return m_items.IndexOf(args...);
        }

        template<typename...Args> auto GetMany(Args&&... args) {
            return m_items.GetMany(args...);
        }
        // And our bonus method
        hstring ToJson();
    private:
        Windows::Foundation::Collections::IVector<Contoso::Item> m_items;
    };
}

视频

optvier做高频交易的,这个talk还是很有东西的

一些性能优化点

小对象尽可能紧凑,利用好cpu cache

能用vector用vector,甚至boost::stable_vector,unordered_map开销非常恐怖 workding set size有分析工具wss https://github.com/brendangregg/wss

seqlock怎么做更快?

作者实现了个基于奇偶版本号的lock,单生产者多消费者,T很小,这种写法没啥竞争,很值

template<typename T>
class SeqLock {
  T mData;
  std::atomic<uint32_t> mVersion;
};

template<typename T>
void SeqLock<T>::Store(const T& value) {
  mVersion+=1;
  std::memcpy(&mData, value, sizeof(T));
  mVersion+=1;
}

template<typename T>
bool SeqLock<T>::Load(T& value) {
  const auto version = mVersion.load();
  if (version & 1 != 0) {
      return false;
  }
  std::memcpy(&value, mData, sizeof(T));
  return version == mVersion;
}

更快的SPMC?

考虑消费队列 SPSC

struct SPSCQueue {
  alignas(64) std::atomic<uint64_t> mWriteIndex;
  alignas(64) std::atomic<uint64_t> mReadIndex;
  alignas(64) uint8_t mData[0];
};

就是一个循环buffer

SPMC那就不用维护mReadIndex,同时尽可能的让竞争更小

struct SPMCQueueV1 {
  alignas(64) std::atomic<uint64_t> mIndex;
  alignas(64) std::atomic<uint64_t> mPendingIndex;
  alignas(64) uint8_t mData[0];
};
template <typename C>
void SPMCQueueV1::Push(MessageSize size, C WriteCallback) {
    mPendingIndex += size;
    std::memcpy(mCurrent, size, sizeof(MessageSize));
    WriteCallback(mCurrent +  sizeof(MessageSize));
    mIndex += size;
}

template <typename C>
void SPMCQueueV1::Pop(C ReadCallback) {
    if (mPendingIndex == mIndex) return;
    MessageSize size;
    std::memcpy(&size, mCurrent + sizeof(MessageSize), sizeof(MessageSize));
    uint8_t buffer[size];
    std::memcpy(buffer, mCurrent + sizeof(MessageSize), size);
    ReadCallback(buffer, mSize);
}

性能不行

考虑seqlock的思路,使用版本号来替换index,降低index频繁修改的开销,均摊到每个槽的版本号上,性能直接起飞 <img src="https://user-images.githubusercontent.com/8872493/211016183-cc1ceb6f-0260-4c64-be89-0d3d00de54bf.png" alt="" width="80%">

每个槽都有mBlockCounter和mVersion,mVersion判定变化,mBlockCounter控制消费


struct Block {
  alignas(64) std::atomic<uint64_t> mVersion;
  alignas(64) std::atomic<uint64_t> mSize;
  alignas(64) uint8_t mData[0];
};
struct Header {
  alignas(64) std::atomic<uint64_t> mBlockCounter;
  alignas(64) Block mData[0];
};

template <typename C>
void SPMCQueueV2::Push(MessageSize size, C WriteCallback) {
    mVersion+=1
    WriteCallback(&mCurrentBlock->mData[0]);
    mVersion+=1
    mBlockCounter+=1
}

系统调优,降了很多cpu cache利用,还有CPU隔离/用户态网络,NUMA绑定 大页等等

采集参数信息,保持观察

这随便说了一嘴,重要还是上面seqlock这套思路,将了大半个小时

开源项目需要人手

新项目介绍/版本更新


本文永久链接

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