最終更新日時: 2024年09月18日 14時54分15秒 (JST)
Akira Takahashi が更新

履歴 編集

variable
<new>

# std::hardware_destructive_interference_size(C++17)

    namespace std {
      inline constexpr std::size_t hardware_destructive_interference_size = implementation-defined;
    }

## 概要

2つのオブジェクトに並行アクセスする際に、パフォーマンス低下を避けられる最小アライメントサイズ。

    struct keep_apart {
      std::atomic<int> cat;
      std::atomic<int> dog;
    };

このような構造体がある場合、`cat`と`dog`が同じキャッシュラインに乗ることがある。スレッド1では`cat`変数、スレッド2では`dog`変数を操作するような状況で、それぞれが共通のキャッシュを無効化してしまうパフォーマンス劣化の問題が起こりえる。こういった状況を「false sharing」という。

`hardware_destructive_interference_size`は、false sharingを回避するための、変数ごとにキャッシュラインを分けられる最小アライメントサイズである。

    struct keep_apart {
      alignas(std::hardware_destructive_interference_size) std::atomic<int> cat;
      alignas(std::hardware_destructive_interference_size) std::atomic<int> dog;
    };

## 備考

- この変数の値は、`alignof(max_align_t)`以上である
- 実装上、`hardware_constructive_interference_size`と`hardware_destructive_interference_size`は同値になるはずだが、利用目的によって名前を使い分けるために分かれている

## 例

    #include <iostream>
    #include <new>
    #include <thread>
    #include <vector>

    struct X {
      alignas(std::hardware_destructive_interference_size) int a;
      alignas(std::hardware_destructive_interference_size) int b;
    };

    struct IndividualCacheInt {
      alignas(std::hardware_destructive_interference_size) int value;
    };

    int main()
    {
      std::cout << "hardware_destructive_interference_size : "
                << std::hardware_destructive_interference_size
                << std::endl;

      // 構造体内のメンバ変数aとbを、それぞれ別なキャッシュラインに乗せる
      {
        X x;
        x.a = 0;
        x.b = 0;
        std::thread t1{[&x]{
          for (int i = 0; i < 100; ++i) {
            ++x.a;
          }
        }};

        std::thread t2{[&x]{
          for (int i = 0; i < 100; ++i) {
            ++x.b;
          }
        }};

        t1.join();
        t2.join();
      }

      // 連続したメモリの各要素を、個別のキャッシュに乗せる
      {
        std::vector<IndividualCacheInt> v{10};
        std::vector<std::thread> threads;
        for (std::size_t i = 0; i < v.size(); ++i) {
          threads.push_back(std::thread{[&v, i]{
            for (int j = 0; j < 100; ++j) {
              ++v[i].value;
            }
          }});
        }

        for (std::thread& t : threads) {
          t.join();
        }
      }
    }

### 出力例

    hardware_destructive_interference_size : 64

## バージョン

### 言語

- C++17

### 処理系

- Clang: 19 ✅
- GCC: 12.1 ✅
- Visual C++: 2019 ✅

## 参照

- N4523 `constexpr std::thread::hardware_{true,false}_sharing_size`
- P0154R0 `constexpr std::hardware_{constructive,destructive}_interference_size`
- P0154R1 `constexpr std::hardware_{constructive,destructive}_interference_size`
- 今さら聞けないマルチプロセッサの基礎教えます　――キャッシュの共有，割り込みの共有，OSによる制御 - ページ11 キャッシュの利用にも注意が必要
- false sharingの整理 - yoskhdia’s diary
- Understanding `std::hardware_destructive_interference_size` and `std::hardware_constructive_interference_size` - Stack Overflow
  - 設計についての作者JF Bastien氏からのコメントがある
  - WebAssemblyなどの仮想環境ではターゲットアーキテクチャが実行時に決まるため、実行時の値もあるとよいだろう、とのコメントもある
- [RFC] C++17 hardware constructive / destructive interference size - Clang Developers Mailing list