kubo39's blog

ただの雑記です。

jemallocator/tikv-jemallocatorのdisable_initial_exec_tlsってなに?

GitHubでよくjemallocatorを使っているときにdisable_initial_exec_tls featureを有効にしているのをみかける。

これはなにかという話をする前に、TLSモデルについて知っておく必要がある。

TLSにはアクセスモデルというものがあり、モジュールの種類(実行バイナリであるとかdlopenでロードされるものであるとか)や 別のモジュールからアクセスされうるものかといった条件によって種類がある。

ELFなどで一般に用いられているものとして local-exec initial-exec local-dynamic global-dynamic の4種類がある。

ざっくり、

  • local-exec: 実行バイナリの中でのみ参照されるTLS変数
  • initial-exec: 実行バイナリ or 最初にロードが走る共有ライブラリ内の静的TLSブロック内に存在するTLS変数
  • local-dynamic: 動的ロードされるモジュールの中で、同一モジュール内での参照のみであるTLS変数
  • global-dynamic(デフォルト): 動的ロードされるものの中で、どこからでも参照可能なTLS変数

基本的にlocal-execのように局所的であるほど速く、global-dynamicが最も遅い。 とりわけxxx-dynamicは __tls_get_addr を経由する必要があるため有意に遅い。__tls_get_addr 遅いよね問題はIntelデベロッパーブログで ネタになるくらい遅い(解決策として何度もアクセスするような使い方をしている場合はローカル変数にキャッシュすることを提示している)。

(追記: オリジナル記事は追えなくなっているが、邦訳記事がある https://www.isus.jp/products/vtune/hidden-performance-cost/ )

jemallocは初期ではTLSアクセスモデルを規定しておらず、デフォルトのglobal-dynamicを用いていた。 しかしjemallocのように速度が求められる(もともとglibcよりよいmallocが欲しいという話であった)、またTLSを使ってロックの利用個所を減らす ようなアーキテクチャでは、より最適なTLSモデルを利用したいという要求がある。

また、mallocを提供するライブラリの場合はlibcが提供しているmallocと混在するような使い方はほぼないはず(と当時おそらく考えていたのだろう)、 であればより効率的なinitial-exec(jemallocは共有ライブラリとして提供する用途があるためlocal-execにはしていない)を使えばよいのではないか という考えは自然と出てくる。

そういうわけで、jemallocではコンパイラがサポートしている場合にはInitial exec TLS modelを有効にしてコンパイルするようになった。

しかし以下のIssueのように、Python界隈などでは共有リンクライブラリにリンクしている共有リンクライブラリをdlopen(3)で読み込むケースがあり、 この変更はそのようなケースで問題になっていた。

これは指摘されているように複数のmallocが混在することによるメモリリーク・データ破壊を引き起こす可能性があり、 そもそもLD_PRELOADを使って丸ごと差し替えるべきであるのだが、わりとそのように共有リンクライブラリをリンクしている 行儀の悪いPythonパッケージが多く、そこそこ影響範囲が多きな変更となっていた。

そこでjemallocはデフォルトではInitial exec TLS Modelを使うが、無効にするオプションを追加することにした。

RustでもPyO3経由でPythonのライブラリを使う場合に同様の問題にあたるため、このオプションを選択できるように追加された。

ちなみにmimallocでも同様の問題は起きており、やはりTLSモデルを選択可能とすることによって解決している。

参考資料