MSVC 对多继承下的 EBO 支持的一个 workaround

首先简单介绍一下 EBO(Empty Base Class Optimization)。

因为 C++ 规定,任何一个 instance 在内存中必须要有唯一的地址,因此一个空的 class/struct 会在编译时被偷偷插入一个外人看不到的 char mem;,于是这个空类的每一个 instance 都可以有一个唯一的地址了。

但是如果将这个空类作为某个类的成员时,这个隐藏的成员会被计入内存布局之中,考虑到 memory padding,有时候会导致类对象体积膨涨一倍。

例如考虑:

1
2
3
4
5
6
7
8
9
class E {};

class A1 {
E e;
int i;
};

assert(sizeof(E) == 1);
assert(sizeof(A1) == 8);

我们会发现每一个 A1 的 instance 都占了 8-byte,比起 4-byte 足足翻了一倍。

占用内存无端变大导致 cache 问题啥的就不讲了,这方面的内容任何讲 computer architecture 的书应该都会有。

Chromium Base MessageLoop Internals (0)

Our fancy star in this post is class base::MessageLoopCurrent.

MessageLoopCurrent

Version: r70_3538
File: base/message_loop/message_loop_current.{h, cc}

MessageLoopCurernt is a proxy class for interactions with the MessageLoop bound to the current thread.

It is introduced to avoid direct uses of MessageLoop::current(), quoting from original comments:

Why: Historically MessageLoop::current() gave access to the full MessageLoop API, preventing both addition of powerful owner-only APIs as well as making it harder to remove callers of deprecated APIs (that need to stick around for a few owner-only use cases and re-accrue callers after cleanup per remaining publicly available).

Because it is a light-weight proxy, it contains only a single pointer to the MessageLoop bound to the current thread.

Monthly Read Posts in Oct 2018

Concurrency

C11 Lock-free Stack

使用C 11 atomic operations 实现一个轻量的 lock-free stack

  • pre-allocation,不需要考虑单个节点的销毁问题,所以不需要使用 hazard pointer
  • free 和 head 两个列表,两个单独的 head 均可用做 sentinel
  • 使用 aba counter 来避免 ABA problem

MySQL/InnoDB中,乐观锁、悲观锁、共享锁、排它锁、行锁、表锁、死锁概念的理解

感觉这篇讲的好混乱,几个概念是明显正交的都混在一起了。

瞄了一眼,基本脑子里都在找平常 locking mechanism 里的概念做映射了。


The method to epoll’s madness

总体写的一般,而且内容有点乱。

madness 应该指的就是 epoll 对外表现的是引用 file descriptor 但是内部维护的确实 file descrption。

Non-blocking Connect(2) and Error Handling

这是我在实现 ezio connector 时遇到的一个比较有意(keng)思(die)的问题。

在使用 non-blocking 的 connect(2) 时,按照 manual 的说法,如果调用的返回被认为是合理的(例如 EINPROGRESS),那么就需要:

  1. 将 sock fd 扔到 IO multiplexor 里,然后等待 writable 事件返回
  2. writable 返回后检查 SO_ERROR,确认没错后才可以认为连接建立

然而因为 ezio 使用 notifier 来实现对某个 fd 的 IO 事件侦听和分发,在上面的操作中,除了 writable 事件外,默认还会加上对错误事件的侦听,即 EPOLLERROR,并且 event handling 的逻辑中,始终先检查 IO 事件是否包含了错误事件。

在某个 test case 中发现如下情况,整理了一下顺序大概这样:

  1. 对一个无 server listening 的地址发起 connect
  2. 此时会因为 EINPROGRESS 的返回而建立 notifier,并且 notifier 绑定了 HandleNewConnection()HandleError() 来分别处理 EPOLLOUTEPOLLERROR
  3. epoll 很迅速的返回,然而返回事件里同时包含了 EPOLLERROREPOLLOUT,导致先执行了 HandleError() 进行了错误处理,重置了 Connector 的某些状态;然后又执行了 HandleNewConnection(),触发了这个 handler 里的某些 precondition assertions,直接挂了程序。

这个情况是我事先压根没考虑过的,毕竟到现在也就 connect(2) 里会出现 HandleError()HandleNewConnection() 里包含部分一模一样的错误处理代码。

而且更精彩的是,在我的 WSL (ubuntu 16.04) 里同一个 test case 的代码根本不会触发 EPOLLERROR,只有 EPOLLOUT;于是错误处理直接留到了 HandleNewConnection() 里检查 SO_ERROR 的部分,剩下的也是如丝般顺滑。

最后的解决方案是修改了一下两个 handler 的语义,对 HandleNewConnection() 做了 error-already-handled-aware 的语义调整。

另外需要提一下日志系统的重要性。

在这个问题的 debugging 过程中,虽然触发了我的 ENSURE(CHECK),有错误的上下文,也完整的调用栈,但是相关信息里根本看不到 HandleError() 被调用的痕迹;而且因为需要错误处理的点甚多,如果没有完善的 callstack + logging 机制,很难才能发现是因为 IO event 包含了 EPOLLERROR,导致 notifier 调用了 on-error-handler 导致的。

最后吐槽一下,server 端来说 asynchronous 比 non-blocking 难写,但是对于 client 端,asynchronous 反而比 non-blocking 顺滑多了;一个 ConnectEx() 丢出去,定时检查一下结果就行了,简直比海飞丝还顺滑。

Naming a Native Thread

这里所说的 naming 主要是为了能够被 debugger 识别,所以单纯的通过 TLS 存储一个额外的字符串是不够的。

Windows

Windows 上的做法有两种。

第一种是利用现成的 API SetThreadDescription()

通过这个 API 设置的名字据说新版的 minidump 和 WinDBG 都能认了。

不过缺点是这个 API 很新,从 Windows 10 1607 (build 14393) 开始才有,所以稳妥的使用方式还是从 kernel32.dll 里动态获取。

第二种做法比较传统,而且很不直观,来源是 MSDN 的一篇 doc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
const DWORD kVCThreadNameException = 0x406D1388;

typedef struct tagTHREADNAME_INFO {
DWORD dwType; // Must be 0x1000.
LPCSTR szName; // Pointer to name (in user addr space).
DWORD dwThreadID; // Thread ID (-1=caller thread).
DWORD dwFlags; // Reserved for future use, must be zero.
} THREADNAME_INFO;

void SetThreadName()
{
THREADNAME_INFO info;
info.dwType = 0x1000;
info.szName = "Worker-Traditional";
info.dwThreadID = -1;
info.dwFlags = 0;

__try {
RaiseException(kVCThreadNameException, 0, sizeof(info) / sizeof(DWORD),
reinterpret_cast<ULONG_PTR*>(&info));
} __except(EXCEPTION_EXECUTE_HANDLER) {
}
}

基本上照搬 doc 上的代码就好了。

Using Boolean Switch with Python Argparser the Right Way

The Context

之前拿 Python 写了一个 CMake 的 build driver,因为要控制一些编译参数,所以使用了如下代码:

1
2
3
4
5
6
7
import argparse

parser = argparse.ArgumentParser()
parser.add_argument('--build-test', dest='build_test', type=bool, default=True)
args = parser.parse_args()

no_build_unittest = not args.build_test

后来有一次偶然发现,无论指定 --build-test=False 还是 --build-test=false 还是 --build-test=0args.build_test 的值永远是 True

然后一脸懵逼。

万幸有万能的 Stackoverflow,搜了一下发现有人遇到了类似的问题。

问题的根源在于,即使你指定了 type=bool,你的输入仍然是 string,而 argparse 没有做 string 到 boolean 的语义化转换,仅简单地判断了一下字符串是否为空,所以前面的参数指定全都被认为是 True

The Solution

解决的方案是将参数类型指定为 lambda 然后自己做转换:

1
2
3
-parser.add_argument('--build-test', dest='build_test', type=bool, default=True)
+parser.add_argument('--build-test', dest='build_test',
+ type=lambda opt: bool(strtobool(opt)), default=True)

Argparse 这个点的实现个人感觉不好,因为违反了直觉。

另,我在饭否上吐槽这个问题的时候,F叔提到了 docopt 作为替代品。但是我看了一下简介之后对这种使用描述生成的方式并不感冒(虽然看起来很酷),第一眼看着觉着 verbose,而且总感觉这种比较 fancy 的做法在一些 edge cases 上可能会更痛苦(想起之前折腾各种 build system 的痛苦 -‘’-)。

不懂之后会不会出现真香的反转,XD

Monthly Read Posts in Sep 2018

Programming Language

Strong types for strong interfaces

How to add a type wrapper for built-in types.

Besides using phantom template parameter to avoid alias, one can also use private inheritance:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class NamedType
{
public:
explicit NamedType(T const& value) : value_(value) {}
explicit NamedType(T&& value) : value_(std::move(value)) {}
T& get() { return value_; }
T const& get() const {return value_; }
private:
T value_;
};

class Width : NamedType<int> {
public:
using NamedType::NamedType;
using NamedType::get;
};

Passing strong types by reference – First attempt

This post is the sequal of the above post.

The main problem the post tryies to solve is: how to make copy of NamedType values cheap.

However, I am conservative on using reference-wrapper as the solution, because doing this has to expose the lifetime of the wrapped object to public; after all, reference-wrapper is only a point per se.