GithubHelp home page GithubHelp logo

me's People

Watchers

 avatar

me's Issues

Wait Morphing

对于以下用条件变量来实现事件等待器的代码:

// waiter
pthread_mutex_lock(&mutex);
while (!flag) {
    pthread_cond_wait(&cond, &mutex);
}
pthread_mutex_unlock(&mutex);

// signaler
pthread_mutex_lock(&mutex);
flag = 1;
pthread_cond_signal(&cond);             // [1]
pthread_mutex_unlock(&mutex);     // [2]

在代码[1]和[2]处先调用signal函数,接着调用unlock释放锁,但是与先unlock再signal相比,这样可能会造成额外的两次上下文切换开销。考虑以下的执行情况:

  1. signaler线程调用signal函数,唤醒waiter线程
  2. waiter线程被唤醒,尝试获得锁,但此时锁仍然被signaler线程持有,waiter线程进入睡眠
  3. signaler线程得到调度,释放了锁
  4. waiter线程得到锁

对于以上这种情况,waiter线程可能会发生多余的一次唤醒操作,造成额外的性能开销。Linux内核为了解决这一问题存在专门的优化技术:Wait Morphing

Wait Morphing优化

内核知道在上述执行情况的第2步,并不能使得被唤醒的线程获得锁,所以把等待在该condvar下的所有等待线程,转移到mutex的等待线程队列中,而不会去真正调度该线程,从而免去多余的上下文切换开销。

参考资料

多线程网络库开发笔记

用条件变量实现事件等待器的正确与错误做法

For语句代码生成模式

RednaxelaFX在文章“对C语义的for循环的基本代码生成模式”中总结了for循环代码生成的三种模式。对于以下for循环语句:

for (loop-initialize; loop-test; loop-increment)
    loop-body

文中给出了以下三种不同的代码生成模式:

  1. 循环条件放前面,循环末尾用无条件跳转回到开头

2. 循环条件放后面,在进入循环的地方先无条件跳转到位于循环末尾的条件

3. 在进入循环的地方先判断是否跳过循环,然后循环条件放在末尾

这3种代码生成模式中,最优的生成模式是方法3,具体参见论文The Best Simple Code Generation Technique for WHILE, FOR, and DO Loops

False sharing

What

现代处理器在读取内存数据时,数据同时被读取到L1和L2中,通常L1中cache line大小是64字节,每个处理器核心都有各自独立的L1和L2缓存(cache)。当多线程程序访问互相独立的变量,如果这个变量被加载到同一缓存行,并且一个线程对变量进行写操作,另一个线程对变量进行读或写操作,不同核心(core)之间的cache需要同步,这将导致性能的严重退化。这种现象称为False Sharing

Example

struct foo {
  int x;
  int y;
};

foo f;

// 下面两个函数在不同线程中运行
int sum_add() {
  int s = 0,i;
  for (i = 0; i < 1000000; ++i)
    s += f.x;
  return s;
}

void inc_b(void) {
  int i;
  for (i = 0; i < 1000000; ++i)
    ++f.y;
}

变量f占用小于64字节的缓存行,sum_add和inc_b线程会将其加载至各自的私有缓存,虽然每个线程访问结构体的不同域(sum_add读f.x,inc_b写f.y),由于缓存同步仍然会对性能造成退化。

How

把每个项凑齐缓存行的长度,实现隔离。

Reference

False sharing-Wikipedia
False sharing问题及其解决方法

LevelDB Overview

设计思路与整体架构

LevelDB 是一种以 LSM-tree[1] 为底层数据结构的写优化的键值数据库。

在具体的实现中,LevelDB 主要包含 Memtable、WAL、分层的 SST 文件、用于版本控制的 Manifest 和 CURRENT 文件。

  • Memtable 内存数据结构,支持并发读写的跳表[2] 实现,新写入的 kv 会先写到 Memtable 中
  • Immutable Memtable 当 Memtable 写入数据量达到阈值时,会生成一个只读的 Memtable 并重新生成一个 Memtable,后台线程会进行 minor compaction 操作,将 immutable memtable 写入 Level 0 的 SST 文件中
  • WAL 预写式日志,在写入 Memtable 之前 kv 数据会先写入 WAL 中,用于机器崩溃重启时的数据恢复
  • SST 磁盘文件,LevelDB 的每一层由多个 SST 文件组成,每一个 SST 文件是一个只读且有序的 kv 序列。Level 0 中的 SST 文件由 Immutable memtable dump 得到,Level N( N >= 1) 层中的 SST 文件由 Level N-1 层中的 SST 文件和本层文件 merge 得到。注意:Level 0 中 SST 文件中的 keys 是可能存在重叠的,而 Level N(N >= 1) 层中各个 SST 文件的 keys 是不重叠的
  • Manifest Manifest 文件中记录了各个层中所有 SST 文件的元数据。每个 SST 文件的元数据主要包括最大最小 key、文件大小等信息
  • CURRENT LevelDB 支持 MVCC,所以同时可能存在多个版本的 Manifest 文件,CURRENT 文件中的内容指向当前的 manifest 文件

LevelDB Overview

写流程

LevelDB 的写入过程首先将 kv 写入 WAL 日志中,再将 kv 写入到 Memtable 中即可。详细的流程在之后的文章中会进行分析。

// 写入 WAL
log_->AddRecord(WriteBatchInternal::Contents(updates));
// 写 Memtable
WriteBatchInternal::InsertInto(updates, mem_);

读流程

LevelDB 的读取流程,首先从 Memtable 中查找 key,如果没有找到则从 Immutable Memtable 中查找 key,如果仍然没有找到则需要从各层中的 SST 文件中进行查找。

if (mem->Get(lkey, value, &s)) {                                // 首先从 Memtable 中查找
	// Done
} else if (imm != NULL && imm->Get(lkey, value, &s)) {          // 然后从 Immutable Memtable 中查找
	// Done
} else {                                                        // 最后依次从各层 SST 文件中查找
	s = current->Get(options, lkey, value, &stats);
	have_stat_update = true;
}

参考文献

  1. The log-structured merge-tree
  2. Skip Lists: A Probabilistic Alternative to Balanced Trees

Spurious wakeup

What

在使用条件变量时,常常使用如下编程模式

pthread_mutex_lock(&mutex);
while (condition is false)
    pthread_cond_wait(&cond,&mutex);

上面代码的 while 循环是必不可少的,因为当线程从 pthread_cond_wait 返回时并不能保证条件真的成立。当线程从 pthread_cond_wait 返回时条件仍为假的现象称为虚假唤醒(Supurious wakup)

Why

ptherad_cond_wait 在 Linux/x64 的内部实现中使用 futex 系统调用挂起当前线程,而 futex 系统调用是慢速系统调用(slow system call),当该调用被中断时将返回 EINTR,所以慢速系统调用的重启功能被关闭后往往需要手动进行重启。

// 手动重启慢速系统调用
for (;;) {
    int r = syscall();
    if (r < 0 && r == EINTR) {
        continue;
    } else {
        break;
    }
}

但是 futex 不能这样用,因为从 futex 结束到重启这个时间窗口内有可能其他线程调用了 pthread_cond_signal/pthread_cond_broadcast ,如果此时 futex 再次被调用将错过条件变量的变化而可能永久等待下去。

所以,正确的用法正是如上所述的编程模式,当出现虚假唤醒时,再次通过 while 循环进行条件判断并继续调用 pthread_cond_wait 等待条件成立。

从汇编角度看C++ lambda表达式

C++11引入了lambda表达式,我们从汇编角度来看lambda表达式底层的实现机制。

  1. No Capturing
int main() {
  auto lambda = [](int a) { return a + 3; }
  lambda(2);
}

以-g选项对代码进行编译,得到以下汇编代码

main: # @main
  push rbp
  mov rbp, rsp
  sub rsp, 16
  mov dword ptr [rbp - 4], 0
  lea rdi, [rbp - 8]                                             ; [1]
  mov esi, 2
  call main::$_0::operator()(int) const
  xor esi, esi
  mov dword ptr [rbp - 12], eax # 4-byte Spill
  mov eax, esi
  add rsp, 16
  pop rbp
  ret
main::$_0::operator()(int) const: # @"main::$_0::operator()(int) const"
  push rbp
  mov rbp, rsp
  mov qword ptr [rbp - 8], rdi
  mov dword ptr [rbp - 12], esi
  mov esi, dword ptr [rbp - 12]
  add esi, 3
  mov eax, esi
  pop rbp
  ret

C++编译器将一个lambda表达式编译成了一个函数,从[1]处开始的3条汇编指令对应C++代码中的lambda(2)函数调用。虽然lambda只有一个形参a,但是在call指令之前,除了将实参2存入寄存器esi,还将main函数栈帧中 ebp - 8 对应的地址存入寄存器edi,这个隐含参数用于对捕获参数的访问。

Double Checked Locking

What

双检查锁是多线程环境下延迟初始化单例的方法。

How

考虑下面代码:

// Broken multithreaded version
// "Double-Checked Locking" idiom
class Foo {
    private Helper helper = null;
    public Helper getHelper() {
        if (helper == null)
            synchronized(this) {
                if (helper == null)
                    helper = new Helper(); // [1]
            }
        return helper;
    }
    // other functions and members...
}

上面的代码其实是不安全的,原因在于 [1] 处的代码并不是一个原子操作,它包含下面三个步骤:

  1. 分配Helper对象占用的内存
  2. 调用Helper类的构造函数
  3. 将对象实例赋值给helper引用

如果发生乱序执行,1->3->2,可能在另外一个线程中看到完成步骤1->3的对象引用,但这是一个尚未构造完成的对象引用,这是典型的多线程不安全发布行为。

在JDK5.0以上版本中,将变量helper用volatile进行修饰,则可以避免上述的不安全行为:

// Broken multithreaded version
// "Double-Checked Locking" idiom
class Foo {
    private volatile Helper helper = null;
    public Helper getHelper() {
        if (helper == null)
            synchronized(this) {
                if (helper == null)
                    helper = new Helper(); // [1]
            }
        return helper;
    }
    // other functions and members...
}

这是因为JDK5.0以上版本增强了volatile的语义:在代码层面上happens before写volatile变量的行为在程序执行时必须happens before写操作(也就是禁止了指令重排序优化)。

Reference

The "Double-Checked Locking is Broken" Declaration
double checkd locking

Chandy-Lamport Distributed Snapshots Algorithm

一个分布式的快照可以理解为在某个瞬时时刻或时间静止时,整个系统中各个节点和节点间 channel 的状态集合。难点在于系统运行过程中是无法静止时间的。Chandy-Lamport Snapshot 算法以 marker 消息做为切割全局视角下状态的切割点。对于每个节点而言,第一次收到 marker 消息时自身的状态做为全局状态的一部分。对于每个节点 p 的 input channel c 而言,如果 p 首次收到 marker 消息,c 的状态是 empty(PS: p 收到的 marker 消息之前的消息已经作用在了 p 的本地状态中);如果 p 非首次收到 marker 消息,那么肯定存在一条环 p -> ... -> ... -> p,c 是经过这条环与 p 直接相连的 channel,此时从 p 记录状态开始到从 c 收到 marker 消息之间 c 中的消息序列是由于系统运动处于 marker 时间点之前未被节点 p 消费的消息,这些消息序列也属于快照的一部分,与所有节点的本地状态共同构成全局的分布式快照。

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.