web-dev-qa-db-ja.com

C ++ 11でメモリを調整するための推奨される方法は何ですか

単一のプロデューサーと単一のコンシューマーリングバッファーの実装に取り​​組んでいます。2つの要件があります。

1)リングバッファの単一のヒープ割り当てインスタンスをキャッシュラインに揃えます。

2)リングバッファ内のフィールドをキャッシュラインに揃えます(誤った共有を防ぐため)。

私のクラスは次のようになります。

_#define CACHE_LINE_SIZE 64  // To be used later.

template<typename T, uint64_t num_events>
class RingBuffer {  // This needs to be aligned to a cache line.
public:
  ....

private:
  std::atomic<int64_t> publisher_sequence_ ;
  int64_t cached_consumer_sequence_;
  T* events_;
  std::atomic<int64_t> consumer_sequence_;  // This needs to be aligned to a cache line.

};
_

最初にポイント1に取り組みましょう。つまり、クラスの単一のヒープに割り当てられたインスタンスの整列です。いくつかの方法があります:

1)c ++ 11 alignas(..)指定子を使用します。

_template<typename T, uint64_t num_events>
class alignas(CACHE_LINE_SIZE) RingBuffer {
public:
  ....

private:
  // All the private fields.

};
_

2)クラス定義を変更せずにposix_memalign(..) +配置new(..)を使用します。これには、プラットフォームに依存しないという問題があります。

_ void* buffer;
 if (posix_memalign(&buffer, 64, sizeof(processor::RingBuffer<int, kRingBufferSize>)) != 0) {
   perror("posix_memalign did not work!");
   abort();
 }
 // Use placement new on a cache aligned buffer.
 auto ring_buffer = new(buffer) processor::RingBuffer<int, kRingBufferSize>();
_

3)GCC/Clang拡張機能__attribute__ ((aligned(#)))を使用します

_template<typename T, uint64_t num_events>
class RingBuffer {
public:
  ....

private:
  // All the private fields.

} __attribute__ ((aligned(CACHE_LINE_SIZE)));
_

4)aligned_alloc(..)の代わりにC++ 11標準化posix_memalign(..)関数を使用しようとしましたが、Ubuntu 12.04のGCC 4.8.1は_stdlib.h_で定義を見つけることができませんでした

これらはすべて同じことを行うことが保証されていますか?私の目標はキャッシュラインのアライメントですので、アライメントにいくつかの制限があるメソッド(ダブルWordなど)は実行しません。標準化されたalignas(..)を使用することを指すプラットフォーム独立性は、二次的な目標です。

alignas(..)__attribute__((aligned(#)))に、マシンのキャッシュラインより下にある可能性のある制限があるかどうかはわかりません。これをもう再現することはできませんが、アドレスを印刷するときに、alignas(..)で常に64バイトにアライメントされたアドレスを取得できなかったと思います。それどころか、posix_memalign(..)は常に機能しているように見えました。繰り返しますが、これはもう再現できないので、間違いを犯していたのかもしれません。

2番目の目的は、クラス/構造内のフィールドを整列するをキャッシュラインに合わせることです。これは、誤った共有を防ぐためです。私は次の方法を試しました:

1)C++ 11 alignas(..)指定子を使用します。

_template<typename T, uint64_t num_events>
class RingBuffer {  // This needs to be aligned to a cache line.
  public:
  ...
  private:
    std::atomic<int64_t> publisher_sequence_ ;
    int64_t cached_consumer_sequence_;
    T* events_;
    std::atomic<int64_t> consumer_sequence_ alignas(CACHE_LINE_SIZE);
};
_

2)GCC/Clang拡張機能__attribute__ ((aligned(#)))を使用します

_template<typename T, uint64_t num_events>
class RingBuffer {  // This needs to be aligned to a cache line.
  public:
  ...
  private:
    std::atomic<int64_t> publisher_sequence_ ;
    int64_t cached_consumer_sequence_;
    T* events_;
    std::atomic<int64_t> consumer_sequence_ __attribute__ ((aligned (CACHE_LINE_SIZE)));
};
_

これらのメソッドはどちらも_consumer_sequence_をオブジェクトの開始後64バイトのアドレスにアラインするように見えるため、_consumer_sequence_がキャッシュアラインされるかどうかは、オブジェクト自体がキャッシュアラインされるかどうかによって決まります。ここで私の質問は-同じことをするより良い方法はありますか?

編集:マシンでaligned_allocが機能しなかった理由は、eglibc 2.15(Ubuntu 12.04)を使用していたためです。 eglibcの新しいバージョンで機能しました。

manページThe function aligned_alloc() was added to glibc in version 2.16から。

Eglibc/glibcの最近のバージョンを要求することはできないので、これは私にとってかなり役に立たない。

66
Rajiv

残念ながら、私が見つけた最高の方法は、余分なスペースを割り当ててから、「位置合わせされた」部分を使用することです。したがって、RingBuffer newは追加の64バイトを要求し、その最初の64バイトにアライメントされた部分を返すことができます。スペースを無駄にしますが、必要なアライメントを提供します。割り当てを解除するには、実際の割り当てアドレスに返される値の前にメモリを設定する必要があります。

[Memory returned][ptr to start of memory][aligned memory][extra memory]

(RingBufferからの継承がないと仮定)のようなもの:

void * RingBuffer::operator new(size_t request)
{
     static const size_t ptr_alloc = sizeof(void *);
     static const size_t align_size = 64;
     static const size_t request_size = sizeof(RingBuffer)+align_size;
     static const size_t needed = ptr_alloc+request_size;

     void * alloc = ::operator new(needed);
     void *ptr = std::align(align_size, sizeof(RingBuffer),
                          alloc+ptr_alloc, request_size);

     ((void **)ptr)[-1] = alloc; // save for delete calls to use
     return ptr;  
}

void RingBuffer::operator delete(void * ptr)
{
    if (ptr) // 0 is valid, but a noop, so prevent passing negative memory
    {
           void * alloc = ((void **)ptr)[-1];
           ::operator delete (alloc);
    }
}

RingBufferのデータメンバーも64バイトにアラインするという2番目の要件については、thisの先頭がアラインされていることがわかっている場合は、データメンバーのアラインメントを強制的にアラインできます。

30

あなたの問題の答えは std :: aligned_storage です。トップレベルで、クラスの個々のメンバーに使用できます。

9
rubenvb

いくつかのさらなる調査の後、私の考えは次のとおりです。

1)@TemplateRexが指摘したように、16バイト以上に合わせる標準的な方法はないようです。したがって、標準化されたalignas(..)を使用しても、アライメント境界が16バイト以下でない限り、保証はありません。ターゲットプラットフォームで期待どおりに動作することを確認する必要があります。

2)__attribute ((aligned(#)))またはalignas(..)を使用して、ヒープに割り当てられたオブジェクトを整列させることはできません。つまり、new()はこれらのアノテーションでは何もしません。これらは、静的オブジェクトまたは(1)の注意事項を持つスタック割り当てに対して機能するようです。

posix_memalign(..)(非標準)またはaligned_alloc(..)(標準化されているがGCC 4.8.1で動作させることができませんでした)+配置new(..)が解決策のようです。プラットフォームに依存しないコードが必要な場合の私のソリューションは、コンパイラ固有のマクロです:)

3)構造体/クラスフィールドの配置は、回答に記載されている__attribute ((aligned(#)))alignas()の両方で機能するようです。繰り返しますが、アライメントスタンドの保証に関する(1)の注意点を考えます。

私の現在の解決策は、現在のターゲットプラットフォームがLinuxのみであるため、posix_memalign(..) +配置new(..)を使用してクラスのヒープ割り当てインスタンスを整列させることです。標準化されており、少なくともClangとGCCで動作するため、フィールドの位置合わせにalignas(..)も使用しています。より良い答えが来たら、それを変更させていただきます。

4
Rajiv

それが新しい演算子で割り当てられたメモリを調整する最良の方法であるかどうかはわかりませんが、確かに非常に簡単です!

これは、GCC 6.1.0のスレッドサニタイザーパスで行われる方法です。

#define ALIGNED(x) __attribute__((aligned(x)))

static char myarray[sizeof(myClass)] ALIGNED(64) ;
var = new(myarray) myClass;

さて、sanitizer_common/sanitizer_internal_defs.hには、

// Please only use the ALIGNED macro before the type.
// Using ALIGNED after the variable declaration is not portable!        

したがって、変数宣言の後にここでALIGNEDが使用される理由はわかりません。しかし、それは別の話です。

0
Hugo