实现类型为key的map

翻译整理自这篇文章,加了点自己的理解

类型为key,value不重要,以string举例

其实主要就是解决类型的映射,如何把类型映射成什么东西,肯定离不开模版就是了

静态map

先说一种static compile time map的方法,也就是模版偏特化

形状可以是这样的

template<typename K, typename V>
struct TypeMap { static const V v;}

然后根据不同的K来偏特化

这里弱化一下,用string举例,去掉V

#include <iostream>
#include <string>
template <typename T>
struct StringAnnotationTypeMap { static const std::string annotation; };
//故意不实现,这样没注册的类型会直接link error
//template <typename T>                         
//const std::string StringAnnotationTypeMap<T>::annotation = "default";

#define _TYPE_REGISTER(T, s)                            \
template <>                                                   \
const std::string StringAnnotationTypeMap<T>::annotation = (s);\
template <>                                                   \
const std::string StringAnnotationTypeMap<T&>::annotation = (s)
//这个T&是为了方便从指针推导出类型的特化

#define __STR(x) #x
#define _STR(x) __STR(x)
// 这个宏的目的是拼不同的类型
// 在rpc场景下,rpc函数名, rpc的request response参数都有相同的前缀
// 通过宏帮忙拼接出 参数 < - > rpc函数名字的映射
#define REQ(F) _TYPE_REGISTER(F##Request, _STR(F))
#define RSP(F) _TYPE_REGISTER(F##Response, _STR(F))

struct ARequest{};
struct BResponse{};
REQ(A);
RSP(B);

int p(const std::string & a) {
    std::cout<<a<<'\n';
    return 0;
}

int main() {
  p(StringAnnotationTypeMap<ARequest>::annotation);
  p(StringAnnotationTypeMap<BResponse>::annotation);
  BResponse *p;
  p(StringAnnotationTypeMap<decltype(*p)>::annotation);
}

这个方案用用还行,缺点是必须在编译期间就定好。

如果想要runtime方案,如何设计?

运行时typemap

考虑ECS设计模式 可以看这个链接简单了解,不是本文的重点

大概意思就是尽可能的组件化,每个组件有各自的侵入方法,更灵活的组装实现

而不是去操作一个大的数据结构,对数据结构提供N个接口

每个Entity对应一个实例,每个组件是Component

不想让Entity和具体的Component绑定,那就需要一个字符串 <->类型的typemap来辅助,运行时注册

下面举例

class Entity {
public:
// ...
  AbstractComponent* get (const std::string &component_name) {
    auto it = m_components.find(component_name);
    assert(it != m_components.end());
    return it->second.get();
  }

private:
  std::unordered_map<std::string, std::unique_ptr<AbstractComponent>> m_components;
//...
};

至于component,继承基类就可以

class HitPointComponent : public AbstractComponent {
public:
  void takeHitFrom(const Entity *other_entity);
  void add(int hp);
  void setCurrentCap(int c);
  
private:
  int m_hitPoints;  // amount of hitpoints, can't be greater than m_currentCap
  int m_currentCap; // maximum amount of hitpoints
};

调用就这个德行

dynamic_cast<HitPointComponent>(player->get("HitPointComponent"))->takeHitFrom(projectile)

就是看着闹心,犯个错实在太轻松,core给你看

我们需要type map,而不是type-string map

既然有类型type,提供类型模版接口,帮助指针转换, 类似这种用法

auto e = std::make_unique<Entity>();
e->provideComponent<HitPointComponent>(std::make_unique<HitPointComponent>());
//...
e->get<HitPointComponent>()->takeHitFrom(projectile);

增加与调用接口需要指定参数类型,也就是说子类的信息没丢,那么,基类实际上不需要放接口,只需要定义虚析构函数就行了

如何实现typemap 之保存类型信息

首先考虑的就是type_info 返回整数

这里要考虑两个问题,1 是type_info的实现是依赖RTTI的,2是type_info是内部调用hash_code,这个实现是不透明的,换言之,这个返回值是不是唯一的,不能保证,只能确定同一个类型肯定返回同一个值

解决方案,自己引入一个getTypeId 保证每个类型的值唯一,一个很简单的唯一方法,自增id

// In a header file:
#include <atomic>

extern std::atomic_int TypeIdCounter;

template <typename T>
int getTypeId() {
  static int id = ++TypeIdCounter;
  return id;
}

// In a *.cpp file:
std::atomic_int TypeIdCounter(0);

当然,如果是单线程,可以用int代替atomic_int

这样,调用一次,就会特化一次,而且每个类型的id是不同的,在不同的翻译单元(TU)里,这就保证了id唯一

有唯一id之后,再保存一个id < - > value 就可以了,当然value是模版,随便是什么都可以

#include <unordered_map>
#include <atomic>

template <class ValueType>
class TypeMap {
  // Internally, we'll use a hash table to store mapping from type
  // IDs to the values.
  typedef std::unordered_map<int, ValueType> InternalMap;
public:
  typedef typename InternalMap::iterator iterator;
  typedef typename InternalMap::const_iterator const_iterator;
  typedef typename InternalMap::value_type value_type;

  const_iterator begin() const { return m_map.begin(); }
  const_iterator end() const { return m_map.end();  }
  iterator begin() { return m_map.begin();  }
  iterator end() { return m_map.end(); }

  // Finds the value associated with the type "Key" in the type map.
  template <class Key>
  iterator find() { return m_map.find(getTypeId<Key>());  }

  // Same as above, const version
  template <class Key>
  const_iterator find() const { return m_map.find(getTypeId<Key>()); }

  // Associates a value with the type "Key"
  template <class Key>
  void put(ValueType &&value) {
    m_map[getTypeId<Key>()] = std::forward<ValueType>(value);
  }  

private:
  template <class Key>
  inline static int getTypeId() {
    static const int id = LastTypeId++;
    return id;
  }

  static std::atomic_int LastTypeId;
  InternalMap m_map;
};

template <class ValueType>
std::atomic_int TypeMap<ValueType>::LastTypeId(0);

这样用起来就是这样的

TypeMap<std::string> tmap;
tmap.put<int>("integers!");
tmap.put<double>("doubles!");
std::cout << tmap.find<int>()->second << "\n";

最终回到ECS模型上来,把Component做Value,问题就解决了

class Entity {
public:
// ...
  template <typename Component>
  Component* get() {
    auto it = m_components.find<Component>();
    assert(it != m_components.end());
    return static_cast<Component*>(it->second.get());
  }
  
  template <typename Component>
  void provideComponent(std::unique_ptr<Component> &&c) {
    m_components.put<Component>(std::forward<std::unique_ptr<Component>>(c));
  }

private:
  TypeMap<std::unique_ptr<AbstractComponent>> m_components;
//...
};

老知识,新复习


update 2021-01-10-20-14

其实这种模式,如果是全局的组件,用type_id就够用了,以arangodb为例子,全局server有个addFeature和getFeature接口,思路和上面基本一致,这里把代码贴出来

其中_features就是type map

class ApplicationServer {
  using FeatureMap =
      std::unordered_map<std::type_index, std::unique_ptr<ApplicationFeature>>;
  ApplicationServer(ApplicationServer const&) = delete;
  ApplicationServer& operator=(ApplicationServer const&) = delete;
////省略一部分代码
 public:
   // adds a feature to the application server. the application server
   // will take ownership of the feature object and destroy it in its
   // destructor
   template <typename Type, typename As = Type, typename... Args,
             typename std::enable_if<std::is_base_of<ApplicationFeature, Type>::value, int>::type = 0,
             typename std::enable_if<std::is_base_of<ApplicationFeature, As>::value, int>::type = 0,
             typename std::enable_if<std::is_base_of<As, Type>::value, int>::type = 0>
   As& addFeature(Args&&... args) {
     TRI_ASSERT(!hasFeature<As>());
     std::pair<FeatureMap::iterator, bool> result =
         _features.try_emplace(std::type_index(typeid(As)),
                           std::make_unique<Type>(*this, std::forward<Args>(args)...));
     TRI_ASSERT(result.second);
     result.first->second->setRegistration(std::type_index(typeid(As)));
#ifdef ARANGODB_ENABLE_MAINTAINER_MODE
     auto obj = dynamic_cast<As*>(result.first->second.get());
     TRI_ASSERT(obj != nullptr);
     return *obj;
#else
     return *static_cast<As*>(result.first->second.get());
#endif
   }

   // checks for the existence of a feature by type. will not throw when used
   // for a non-existing feature
   bool hasFeature(std::type_index type) const noexcept {
     return (_features.find(type) != _features.end());
   }

   // checks for the existence of a feature. will not throw when used for
   // a non-existing feature
   template <typename Type, typename std::enable_if<std::is_base_of<ApplicationFeature, Type>::value, int>::type = 0>
   bool hasFeature() const noexcept {
     return hasFeature(std::type_index(typeid(Type)));
   }

   // returns a reference to a feature given the type. will throw when used for
   // a non-existing feature
   template <typename AsType, typename std::enable_if<std::is_base_of<ApplicationFeature, AsType>::value, int>::type = 0>
   AsType& getFeature(std::type_index type) const {
     auto it = _features.find(type);
     if (it == _features.end()) {
       throwFeatureNotFoundException(type.name());
     }
#ifdef ARANGODB_ENABLE_MAINTAINER_MODE
     auto obj = dynamic_cast<AsType*>(it->second.get());
     TRI_ASSERT(obj != nullptr);
     return *obj;
#else
     return *static_cast<AsType*>(it->second.get());
#endif
   }

   // returns a const reference to a feature. will throw when used for
   // a non-existing feature
   template <typename Type, typename AsType = Type,
             typename std::enable_if<std::is_base_of<ApplicationFeature, Type>::value, int>::type = 0,
             typename std::enable_if<std::is_base_of<Type, AsType>::value || std::is_base_of<AsType, Type>::value, int>::type = 0>
   AsType& getFeature() const {
     auto it = _features.find(std::type_index(typeid(Type)));
     if (it == _features.end()) {
       throwFeatureNotFoundException(typeid(Type).name());
     }
#ifdef ARANGODB_ENABLE_MAINTAINER_MODE
     auto obj = dynamic_cast<AsType*>(it->second.get());
     TRI_ASSERT(obj != nullptr);
     return *obj;
#else
     return *static_cast<AsType*>(it->second.get());
#endif
   }

   // map of feature names to features
   FeatureMap _features;
}

其实可以把type_index和type_info隐藏的更深一些,不提供type_index的接口,这样背后替换更轻松一些

使用时这样的

    ApplicationServer server(options, SBIN_DIRECTORY);
//...
    // Adding the Phases
    server.addFeature<AgencyFeaturePhase>();
    server.addFeature<CommunicationFeaturePhase>();
    server.addFeature<AqlFeaturePhase>();
    server.addFeature<BasicFeaturePhaseServer>();
    server.addFeature<ClusterFeaturePhase>();
//...

效果类似


ref

  • https://gpp.tkchu.me/ 意外找到了游戏编程模式的中文翻译
  • Arangodb 源码 https://github.com/arangodb/arangodb

Read More

12月待读

http://ot-note.logdown.com/posts/231212/compile-time-constant-comparison-take-the-character-the-length-of-the-string-test

介绍crow里的编译期比较字符串。crow star了,一直没机会看

保存type类型的库 https://github.com/Neargye/nameof

字节跳动的rocksdb开源了

https://github.com/bytedance/terarkdb

https://www.tuicool.com/wx/BFRRb2y

他们也实现了kv分离,复习一下kv分离

https://en.pingcap.com/blog/titan-storage-engine-design-and-implementation

字节小伙的sig_tree

https://github.com/JimChengLin/sig_tree/

我数据结构的基础还是差

腾讯的tendis开源了

https://github.com/Tencent/Tendis

没有exporter 其实可以照着https://github.com/oliver006/redis_exporter 改,我发现他们这个还不支持集群模式,所以一时半会还改不了

boost hana的中文翻译,其实一直没机会用,也就没看

有机会调查一下

https://github.com/freezestudio/hana.zh/blob/master/hana-zh.md

https://plywood.arc80.com/

有反射

React 教程 https://zh-hans.reactjs.org/tutorial/tutorial.html

lisp方言

https://github.com/SuperFola/Hitoban

https://github.com/ArkScript-lang/Ark

一个小脚本语言,架构类似lua

https://github.com/Skiars/berry

这种脚本语言和mruby相比有啥优势?哦嵌入式

https://github.com/ssloy/tinyraycaster

https://github.com/ssloy/tinyraytracer

简短的教程,教你写光锥

rust中文教程

https://kaisery.github.io/trpl-zh-cn

了解lua

https://github.com/lichuang/Lua-Source-Internal/blob/master/doc/ch02-Lua%E4%B8%AD%E7%9A%84%E6%95%B0%E6%8D%AE%E7%B1%BB%E5%9E%8B.md

https://www.zhihu.com/question/20617406

分形树

http://mysql.taobao.org/monthly/2016/04/09/

https://github.com/percona/PerconaFT

https://github.com/percona/tokudb-engine

http://www.fxysw.com/thread-5061-1-1.html

http://mysql.taobao.org/monthly/2016/03/01/

toydb

https://github.com/erikgrinaker/toydb


Read More

(译)Data Structures and Algorithms for Big Databases


这篇文章是percona的在xldb2012的分享,是一篇教程,介绍了一些基本概念,比较旧了,了解概念

官方总结 ppt链接

  • 如何选取数据结构能显著减轻(signaficantly mitigate)插入/查询/更新 的开销
  • 这些数据结构数据量大了,怎样更高效的利用内存

大数据,也就是放不下内存,靠数据结构来管理,教程也是针对大数据场景下数据结构的管理来展开的

Module 1: I/O model and cache-oblivious analysis.

IO带宽,三个参数, 数据大小N,内存大小M,传块大小B

  • 如果有1000M数据,10M内存,数据块1M,怎么排序呢
    • 首先读10M 排一次,一共排100次
    • 然后10个10M合并,一共10次
    • 然后10个100M合并
  • 浪费的IO
    • 首先是N/B次 传输,然后是合并logM/B(N/B)

上面这个也叫做DAM(Disk-Access-Model)分析,因为这种场景下,内存不是瓶颈,IO是瓶颈

问题在于,对于cache-oblivious的场景,你是不知道M(可用内存) B(传输块大小)的,作者做了一版穷举B的测试,数据表示没有最佳的B

感觉作者没说完啊,对于CO怎么优化也没说

Module 2: Write-optimized data structures. We give the optimal trade-off between inserts and point queries. We show how to build data structures that lie on this tradeoff curve.

这里列的是tokudb,用的lsm tree,没啥说的

Module 2 continued: Write-optimized data structures perform writes much faster than point queries; this asymmetry affects the design of an ACID compliant database.

这里提到了mongo用的Fractal-Tree Index 没听说过这数据结构

Module 3: Case study – TokuFS. How to design and build a write-optimized file systems.

介绍tokufs,读写指标吊锤ext4

论文在这里

实现细节

  • metadata index data block index 加速
  • metadata index key记录文件名,data block index记录文件名和blocknum 连续读
  • blocksize固定512
  • 压缩索引
    • 路径名字前缀非常多余,可以优化移除
    • zero-padding,很多块占用用了padding,可以移除(?有没有安全风险?)
  • 原型阶段,不知道谁用了

Module 4: Page-replacement algorithms. We give relevant theorems on the performance of page-replacement strategies such as LRU.

讨论缓存有效性问题,引入各种cache组件,比如LRU

讨论最佳替换算法,以及竞争性分析,在线算法竞争性分析可以看这篇文章 也放在参考链接里了

具体的概念就不展开了,直接说结论

LRU LFU FIFO k-competitive

基本上LRU和内存(OPT)表现一致,多了一些内存浪费

大段时间都在证明上

还有一些需要考证的问题

  • 如果block大小不一,性能表现如何?
  • 写回(write-back)的代价
  • LRU实现起来 代价稍高(调用系统时间,其实这不算什么问题)

Module 5: Index design, including covering indexes.

b树索引加速查询但是影响插入,维护索引代价不大,所以如何索引需要考虑清楚

  • 索引缩小数据规模
  • 提高局部性
  • 排序

Module 6: Log-structured merge trees and fractional cascading.

介绍LSM tree以及LSM tree在DAM模型上的表现

如何提高点查效率

  • 缓存
  • 不聋过滤器 Bloom filter
  • fractional cascading.(?啥意思)

以及LSM加优化之后和COLA差不多

LSM tree + forward + ghost = COLA

没看懂细节 总之tokudb论文里有

还有buffer tree之类的都是对btree的优化。这里不展开了

Module 7: Bloom filters.

读完感觉没有参考资料有可读性。毕竟比较旧了,2012年的,还是挺有前瞻性的

参考资料

  • 需要了解cache-bolivious 概念 https://users-cs.au.dk/gerth/emF03/slides/cacheoblivious.pdf
  • 一个CO的简单优化,就是分治 https://blog.csdn.net/toughbro/article/details/5931029

考量cache oblivious最好是对比完全不考虑memory hierarchy结构的算法复杂度测量机制,O(nlogn)这样的,只是计数计算的复杂度。或者在console上常常做的cache aware的优化,就是针对cache line size来组织数据结构大小,一次prefetch一个cache line的数据做操作。这个cache oblivious比理想算法更接近于电脑硬件(考虑到memory hierarchiy)但也没有变态到完全根据cache line size来设计数据结构和算法。简单说来,就是假设一个cache line size,对问题进行分而治之,分的粒度到virtual cache line size,就停止分解

  • 和我第一小节差不多 https://www.jianshu.com/p/8bfb47c01a7e
  • 随便搜co搜到一篇论文 https://www.cse.ust.hk/catalac/papers/coqp_tods08.pdf
  • 算法介绍 https://blog.csdn.net/dc_726/article/details/51724097
    • 第五小结,介绍了具体的优化算法,以及列出了很多论文,不错
    • 第四小结,写优化数据结构,除了LSM tree还有很多,提供思路
  • 竞争性分析,建议看这篇文章 https://blog.csdn.net/anshuai_aw1/article/details/108467900 写的不错
  • tokudb有很多点子,催生了很多想法
    • 写优化数据库 https://github.com/weicao/cascadb
    • fractal-tree数据库 https://github.com/BohuTANG/nessDB
      • 另外,这个兄弟的博客不错,对于了解CK来说。值得看一看 https://bohutang.me/2020/06/05/clickhouse-and-friends-development/

Read More

(译)Scaling Cache Infrastructure at Pinterest


原文链接

需求,业务激增,缓存不够,要分布式缓存

pinterest的业务架构图

分布式缓存,使用mcrouter + memcache架构,facebook的方案,他们还发了paper

memcache可以开启extstore,如果数据过大放不下,可以保存到硬盘里,flash nvme之类的

mcrouter的抽象能力非常好

  • 解藕数据面控制面
  • 提供上层更精细的控制 修改ttl之类

这套方案mcrouter也高可用

  • 背后的复制行为对上层来说也是透明的 双活等等
  • 也可以接入测试流量,更好的隔离流量

主要风险

  • 配置文件容易出错
  • 瓶颈在proxy 得多个proxy
  • 尽管业务侧可以灵活的设计key,热点key问题还是不可避免 (有没有mongodb的那种分裂range机制?如果没有,能引入吗?)

说实话这个软文没讲什么东西


PS

我浏览的途中又一次看了眼beandb,原来也是mc的协议啊

一个db的列表 https://github.com/sdcuike/issueBlog/blob/master/%E5%AD%98%E5%82%A8%E5%BC%95%E6%93%8E.md

https://github.com/alibaba/tair


Read More

(译)对于模版类,尽可能的使用Hidden Friend函数定义operator,而不是放在外侧当成模版方法


原文链接

两种比较实现

一种是通用的模版方法

template<class V>
struct Cat {
    V value_;
};

template<class V>
bool operator<(const Cat<V>& a, const Cat<V>& b) {
    return a.value_ < b.value_;
}

另一种是友元函数

template<class V>
struct Dog {
    V value_;

    friend bool operator<(const Dog& a, const Dog& b) {
        return a.value_ < b.value_;
    }
};

这也叫Hidden Friend 惯用法,更推荐这种写法,比如这种场景

template<class T>
void sort_in_place(const std::vector<T>& vt) {
    std::vector<std::reference_wrapper<const T>> vr(vt.begin(), vt.end());
    std::sort(vr.begin(), vr.end());
    std::transform(vr.begin(), vr.end(),
        std::ostream_iterator<int>(std::cout), std::mem_fn(&T::value_));
}

使用reference_wrapper,增加一层,对于sort比较,通过ADL找对应的operator < , 推导失败,(Godbolt.)

opt/compiler-explorer/gcc-snapshot/lib/gcc/x86_64-linux-gnu/11.0.0/../../../../include/c++/11.0.0/bits/predefined_ops.h:43:23: error: invalid operands to binary expression ('std::reference_wrapper<const Cat<int>>' and 'std::reference_wrapper<const Cat<int>>')
      { return *__it1 < *__it2; }
               ~~~~~~ ^ ~~~~~~

对于Dog类,用到friend方法,能隐式把 const Dog<int>& 转换reference_wrapper<Dog<int»

对于Cat类,operator < 需要具体的类型来推导,否则直接报错

这个技巧也叫Barton–Nackman trick

标准库的写法通常都是Cat,reference_wrapper 也是后加的,大部分没有sort_in_place这种需求


Read More

(译)还是讨论folly的静态注入技术:合法访问私有成员函数


原文链接

需求,不改动Foo类的前提下访问bar和x,即使他们是private

// foo.h
#pragma once
#include <iostream>

class Foo {
    int bar() const {
        std::cout << __PRETTY_FUNCTION__;
        return x;
    }

    int x{42};
};

先是总结了一遍folly的技术

// access_private_of_foo.h
#pragma once
#include "foo.h"

// Unique namespace in each TU.
namespace {
// Unique type in each TU (internal linkage).
struct TranslationUnitTag {};
}  // namespace

// 'Foo::bar()' invoker.
template <typename UniqueTag,
          auto mem_fn_ptr>
struct InvokePrivateFooBar {
    // (Injected) friend definition.
    friend int invoke_private_Foo_bar(Foo const& foo) {
        return (foo.*mem_fn_ptr)();
    }
};
// Friend (re-)declaration.
int invoke_private_Foo_bar(Foo const& foo);

// Single explicit instantiation definition.
template struct InvokePrivateFooBar<TranslationUnitTag, &Foo::bar>;

// 'Foo::x' accessor.
template <typename UniqueTag,
          auto mem_ptr>
struct AccessPrivateMemFooX {
    // (Injected) friend definition.
    friend int& access_private_Foo_x(Foo& foo) {
        return foo.*mem_ptr;
    }
};
// Friend (re-)declaration.
int& access_private_Foo_x(Foo& foo);

// Single explicit instantiation definition.
template struct AccessPrivateMemFooX<TranslationUnitTag, &Foo::x>;

这个代码更清晰一点,之前也谈到过,见这篇文章

现在是2020年了,考虑c++20的做法

C++20 implemented P0692R1 (Access Checking on Specializations), summarized in P2131R0 (Changes between C++17 and C++20 DIS) as

This change fixes a long-standing, somewhat obscure situation, where it was not possible to declare a template specialization for a template argument that is a private (or protected) member type. For example, given class Foo { class Bar {}; };, the access Foo::Bar is now allowed in template<class> struct X; template<> struct X<Foo::Bar>;.

特化模版,模版参数可以填private/protected成员函数, 也就规避了显式实例化,保留原来的特化即可

回到这个函数接口,原来的友元技术不变,只是去掉显式实例化

// accessprivate.h
#pragma once
template <auto mem_ptr>
struct AccessPrivate
{
    // kMemPtr is intended to be either a pointer to a private
    // member function or pointer to a private data member.
    static constexpr auto kMemPtr = mem_ptr;
    struct Delegate;  // Define only in explicit specializations.
};
// access_private_of_foo_cpp20.h
#pragma once
#include "accessprivate.h"
#include "foo.h"

// Specialize the nested Delegate class for each private
// member function or data member of Foo that we'd like to access.

template <>
struct AccessPrivate<&Foo::bar>::Delegate {
    // (Injected) friend definition.
    friend int invoke_private_Foo_bar(Foo const& foo) {
        return (foo.*kMemPtr)();
    }
};
// Friend (re-)declaration.
int invoke_private_Foo_bar(Foo const& foo);

template <>
struct AccessPrivate<&Foo::x>::Delegate {
    // (Injected) friend definition.
    friend int& access_private_Foo_x(Foo& foo) {
        return foo.*kMemPtr;
    }
};
// Friend (re-)declaration.
int& access_private_Foo_x(Foo& foo);

注意这里,声明了Delegate,只特化需要的注入访问接口,之前的显式实例化,以及匿名空间Tag(TU唯一)都去掉了。加了一层Delegate

用宏整理一下

// accessprivate/accessprivate.h
#pragma once

namespace accessprivate {
template <auto mem_ptr>
struct AccessPrivate
{
    // kMemPtr is intended to be either a pointer to a private
    // member function or pointer to a private data member.
    static constexpr auto kMemPtr = mem_ptr;
    struct Delegate;  // Define only in explicit specializations.
};

}  // namespace accessprivate

// DEFINE_ACCESSOR(<qualified class name>, <class data member>)
//
// Example usage:
//   DEFINE_ACCESSOR(foo::Foo, x)
//
// Defines:
//   auto& accessprivate::get_x(foo::Foo&)
#define DEFINE_ACCESSOR(qualified_class_name, class_data_member)\
namespace accessprivate {\
template <>\
struct AccessPrivate<&qualified_class_name::class_data_member>::Delegate {\
    friend auto& get_##class_data_member(\
        qualified_class_name& obj) { return obj.*kMemPtr; }\
};\
auto& get_##class_data_member(qualified_class_name& obj);\
}

这样写getter setter更简单

#include <iostream>
#include "accessprivate/accessprivate.h"

namespace bar {

struct Bar {
    int getX() const { return x; }
    int getY() const { return y; }
private:
    int x{42};
    int y{88};
};

}  // namespace bar

DEFINE_ACCESSOR(bar::Bar, x)
// -> accessprivate::get_x(Bar&)
DEFINE_ACCESSOR(bar::Bar, y)
// -> accessprivate::get_y(Bar&)

void demo() {
    bar::Bar b{};
    accessprivate::get_x(b) = 13;
    accessprivate::get_y(b) = 33;
    std::cout << b.getX() << " " << b.getY();  // 13 33
}

作者已经写了仓库 c++17可用


ref

  • 原文中列出了一些c++的标准中对应的描述,这里不列举了,不仔细追究什么符号查找之类的限定了
  • 作者的博客很值得一读,老语言律师了
  • 还有一个讨论,技巧和folly一样,不多说了 https://quuxplusone.github.io/blog/2020/12/03/steal-a-private-member/

Read More

(译)编译器是如何处理没用到的代码的?


原文链接

作者整理了一份测试的表格(这个大哥是真爱c++啊这种细节都要扣我感觉魔怔了有点)

编译器是否会对没被用到的___ 发出警告 Clang GCC ICC MSVC
static function -Wall -Wall   -W4
static variable -Wall -Wall    
private data member -Wall      
private static data member        
private member function        
private static member function        
data member of private class        
static data member of private class        
member function of private class        
static member function of private class        
anonymous-namespaced function -Wall -Wall    
anonymous-namespaced variable -Wall -Wall    
data member of anonymous-namespaced class        
static data member of anonymous-namespaced class -Wall -Wall    
member function of anonymous-namespaced class   -Wall    
static member function of anonymous-namespaced class   -Wall    
function taking anonymous-namespaced class -Wall -Wall    
编译器是否会优化掉未使用的____ Clang GCC ICC MSVC
static function -O0 -O1 -O0 -Od
static variable -O0 -O0 -O1 -Od
private data member
private static data member
private member function
private static member function
static data member of private class
member function of private class
static member function of private class
anonymous-namespaced function -O0 -O1 -O0  
anonymous-namespaced variable -O0 -O0 -O1 -Od
static data member of anonymous-namespaced class -O0 -O0 -O1  
member function of anonymous-namespaced class -O0 -O1 -O1  
static member function of anonymous-namespaced class -O0 -O1 -O1  
function taking anonymous-namespaced class -O0 -O1 -O1  

还有很多优化空间

注意 没用到的私有函数是不回被删掉的,所以有个hack: 模版参数是私有函数指针,通过显式实例化绕开private限制,实现静态注入/调用,详情看这篇文章


Read More

(译)socket in your shell


整理自这篇博客

简单说,就是基本工具shell也可以用socket来做服务/客户端(尤其是在没有nc/telnet的场景下)

作者列了普通bash和zsh下两种用法

bash

echo "text!" > /dev/$PROTO/$HOST/$PORT

一个检测例子

#!/bin/bash
if exec 3>/dev/tcp/localhost/4000 ; then
	echo "server up!"
else
	echo "server down."
fi

我以前都用netcat检测

也可以用exec检测

samplecurl

#!/bin/bash
exec 3<>/dev/tcp/"$1"/80
echo -e "GET / HTTP/1.1\n" >&3
cat <&3

使用

$ ./simplecurl www.google.com
HTTP/1.1 200 OK
Date: Thu, 03 Dec 2020 00:57:30 GMT
Expires: -1
....
<google website>

zsh

有内建模块支持

zmodload zsh/net/tcp

这行放到.zshrc ,或者shell里执行,就加载了ztcp

# host machine:
lfd=$(ztcp -l 7128)
talkfd=$(ztcp -a $lfd)

# client machine
talkfd=$(ztcp HOST 7128)

这样客户端服务端的fd有了,就可以通话了

# host machine
echo -e "hello!" >&$talkfd

# client machine
read -r line <&$talkfd; print -r - $line
> hello!

Read More


(译)现代存储硬件足够快啦就是老api不太好用


这里存储设备指的optane这种 原文

简单整理,用deepl翻译的

作者是老工程师了,列出了常见的几种对存储的误解

  • IO比复制更重,所以复制数据代替直接读是合理的,因为省了一次IO
  • “我们设计的系统要非常快,所以全放在内存里是必须的”
  • 文件拆分成多个反而会慢,因为会产生随机IO 不如直接从一个文件里读,顺序的
  • Direct IO非常慢,只适用于特殊的设备,如果没有对应的cache支持,会很糟糕

作者的观点是,现在设备非常牛逼,以前的api有很多设计不合理的地方,各种拷贝,分配 ,read ahead等等操作过于昂贵

即:传统api的设计是因为IO昂贵,所以做了些昂贵的动作来弥补

  • 读没读到 cache-miss -> 产生page-fault 加载数据到内存 -> 读好了,产生中断
    • 如果是普通用户态进程,再拷贝一份给进程
    • 如果用了mmap,要更新虚拟页

在以前,IO很慢,对比来说这些更新拷贝要慢一百倍,所以这些也无足轻重,但是现在IO延迟非常低,可以看三星nvme ssd指标,基本上耗时数量级持平

简单计算,最坏情况,设备耗时也没占上一半,时间都浪费在哪里了?这就涉及到第二个问题 读放大

操作系统读数据是按照页来读,每次最低4k,如果你读一个1k的数据,这个文件分成了两个,那也就是说,你要用8k的页读1k的数据,浪费87%,实际上,系统也预读(read ahead) 每次默认预读128k,方便后面继续读,那也就是说相当于用256k来读1k,非常恐怖的浪费

那么 用direct IO直接读怎么样,不会有页浪费了吧

问题还在,老api并不是并发读,尽管读的快但是比cpu还是慢,阻塞,还是要等待

所以又变成多文件,提高吞吐,但是

  • 多文件又有多的read ahead浪费,
  • 而且多文件可能就要多线程,还是放大,如果你并没有那么多文件,这个优化点也用不上

新的api

io_uring是革命性的,但还是低级的api:

  • io_uring的IO调度还是会收到之前提到的各种缓存问题影响
  • Direct IO有很多隐藏的条件(caveats 注释事项) 比如只能内存对齐读,io_uring作为新api对于类似的问题没有任何改进

为了使用io_uring你需要分批积累和调度,这就需要一个何时做的策略,以及某种事件循环

为此,作者设计了一个io框架glommio Direct IO,基于轮训,无中断 register-buffer

Glommio处理两种文件类型

  • 随机访问文件
    • 不需要缓冲区,直接用io_uring注册好的缓冲区拿过来用,没有拷贝,没有内存映射,用户拿到一个引用计数指针
    • 因为指导这是随机IO,要多少读多少
  • 流文件
    • 设计的和rust的asyncread一样,多一层拷贝,也有不用拷贝的api

作者列出了他的库做的抽象拿到的数据,和bufferred io做比较

  bufferred IO DirectIO(glommed) +开启预读 read ahead 提高并发度 +使用避免拷贝的api + 增大buffer
53G 2x内存 顺序读sequential 56s, 945.14 MB/s 115s, 463.23 MB/s 22s, 2.35 GB/s 21s, 2.45 GB/s 7s, 7.29 GB/s

注意,随机读+ scan对内存page cache污染比较严重

在小的数据集下

Buffered I/O: size span of 1.65 GB, for 20s, 693870 IOPS
Direct I/O: size span of 1.65 GB, for 20s, 551547 IOPS

虽然慢了一点,但是内存占用上有优势

对于大的数据,优势还是比较明显的,快三倍

Buffered I/O: size span of 53.69 GB, for 20s, 237858 IOPS
Direct I/O: size span of 53.69 GB, for 20s, 547479 IOPS

作者的结论是 新时代新硬件direct IO还是非常可观的,建议相关的知识复习一下


ref/ps

  • https://github.com/DataDog/glommio 有时间仔细看看 这个作者之前是做seastar的,seastar是DirectIO+Future/Promise
  • 详细介绍的文档 https://www.datadoghq.com/blog/engineering/introducing-glommio/
  • 代码文档 https://docs.rs/glommio/0.2.0-alpha/glommio/

Read More

^