高速通信計算研究所

slankdevの報告

ダブルロックの有用性について

2016.08.01 サイボウズラボユースにて. ずいぶんまえに学んだ内容だが、資料を生理していたらmemoがでてきたので。 ここに吐いておく(間違っていたらすみません)

はじめに通常のロックについて

以下の手順で行われる

lock()
if (inited == false)
    init()
    inited = true
unlock()

このときif文とlock,unlockのレイテンシの重さはだいたい以下のような関係

if() <<<< lock(), unlock()

初期化をする人は以下のように実行する

lock()
if (inited == false)
init()
inited = true
unlock()

それ以降の人はこのように実行する

lock()
unlock()

このときの初期化をする人、それ以降の人の割合は以下のような関係

初期化者 <<<<< 超えられない壁 <<<<<< それ以外

なので、いちいちlock, unlockを毎回させるのは嫌だ!!

これがダブルロックのモチベーション

ダブルロックとは

以下のような手順で行われる

if (inited == false)
    lock()
    if (inited == false)
        init()
        inited = true
    unlock()

初期化者は以下のように実行

if (inited == false)
lock()
if (inited == false)
init()
inited = true
unlock()

それ以外の人は以下のように実行

if (inited == false)

見てわかるようにlock,unlockを回避できた。だがしかし、2000年くらいに ただのダブルロックがすべてのアーキテクチャで安全ではないことが示された。

Intelのアーキとかだと、大体うまく動くようだが、Javaとかだと様々なアーキで 動かすことになるので、その問題が浮き彫りになったっぽい。 その出来事以降、Intelのプロセッサのマニュアルにもその趣旨に関する文が追加されている。 詳しくは以下の4/21日を参照する。

http://homepage1.nifty.com/herumi/diary/1204.html

C++11以降はスレッドセーフなロック手法を使っている。

C++のスレッドセーフロックの確認

以下のコードをコンパイルしてasmを見る

struct A {
   int a;
   A(int b) : a(b) {}
};
A& get_instance()
{
    static A a(2);
    return a;
}
$ c++ -std=c++11 -S -Ofast -o out.s main.cc
 .section    __TEXT,__text,regular,pure_instructions
    .macosx_version_min 10, 11
    .globl  __Z12get_instancev
    .align  4, 0x90
__Z12get_instancev:                     ## @_Z12get_instancev
    .cfi_startproc
## BB#0:
    pushq   %rbp
Ltmp0:
    .cfi_def_cfa_offset 16
Ltmp1:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp2:
    .cfi_def_cfa_register %rbp
    movb    __ZGVZ12get_instancevE1a(%rip), %al
    testb   %al, %al
    jne LBB0_3
## BB#1:
    leaq    __ZGVZ12get_instancevE1a(%rip), %rdi
    callq   ___cxa_guard_acquire
    testl   %eax, %eax
    je  LBB0_3
## BB#2:
    movl    $2, __ZZ12get_instancevE1a(%rip)
    leaq    __ZGVZ12get_instancevE1a(%rip), %rdi
    callq   ___cxa_guard_release
LBB0_3:
    leaq    __ZZ12get_instancevE1a(%rip), %rax
    popq    %rbp
    retq
    .cfi_endproc

.zerofill __DATA,__bss,__ZZ12get_instancevE1a,4,2 ## @_ZZ12get_instancevE1a
.zerofill __DATA,__bss,__ZGVZ12get_instancevE1a,8,3 ## @_ZGVZ12get_instancevE1a

.subsections_via_symbols

これがスレッドセーフなロックのasm. __cxa_guard_acquire, __cxa_guard_releaseがそれである

こうするとスレッドセーフでない従来型なasmをはいてくれる

$ c++ -std=c++11 -S -Ofast -o out.s main.cc -fno-threadsafe-statics
 .section    __TEXT,__text,regular,pure_instructions
    .macosx_version_min 10, 11
    .globl  __Z12get_instancev
    .align  4, 0x90
__Z12get_instancev:                     ## @_Z12get_instancev
    .cfi_startproc
## BB#0:
    pushq   %rbp
Ltmp0:
    .cfi_def_cfa_offset 16
Ltmp1:
    .cfi_offset %rbp, -16
    movq    %rsp, %rbp
Ltmp2:
    .cfi_def_cfa_register %rbp
    movb    __ZGVZ12get_instancevE1a(%rip), %al
    testb   %al, %al
    jne LBB0_2
## BB#1:
    movl    $2, __ZZ12get_instancevE1a(%rip)
    movb    $1, __ZGVZ12get_instancevE1a(%rip)
LBB0_2:
    leaq    __ZZ12get_instancevE1a(%rip), %rax
    popq    %rbp
    retq
    .cfi_endproc

.zerofill __DATA,__bss,__ZZ12get_instancevE1a,4,2 ## @_ZZ12get_instancevE1a
.zerofill __DATA,__bss,__ZGVZ12get_instancevE1a,1,0 ## @_ZGVZ12get_instancevE1a

.subsections_via_symbols

これが従来型。 以上でした。