C ++ 11에서 메모리를 정렬하는 권장 방법은 무엇입니까?
단일 생산자 단일 소비자 링 버퍼 구현을 작업 중입니다. 두 가지 요구 사항이 있습니다.
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
이 모든 것이 동일한 작업을 보장합니까? 내 목표는 캐시 라인 정렬이므로 정렬에 약간의 제한이있는 방법 (예 : 이중 단어)은 수행하지 않습니다. 표준화 된 사용을 가리키는 플랫폼 독립성 alignas(..)
은 부차적 인 목표입니다.
나는에 취소 여부 아니에요 alignas(..)
및 __attribute__((aligned(#)))
시스템에서 캐시 라인 아래에있을 수있는 몇 가지 제한이 있습니다. 더 이상 재현 할 수 없지만 주소를 인쇄하는 동안 항상 64 바이트로 정렬 된 주소를 alignas(..)
. 반대로 posix_memalign(..)
항상 작동하는 것 같았습니다. 다시는 이것을 더 이상 재현 할 수 없으므로 실수를했을 수도 있습니다.
두 번째 목표는 클래스 / 구조체 내의 필드 를 캐시 라인 에 정렬하는 것입니다 . 나는 허위 공유를 방지하기 위해 이것을하고 있습니다. 다음과 같은 방법을 시도했습니다.
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의 최신 버전을 요구할 수 없기 때문에 나를 위해 꽤 쓸모 없게 만듭니다.
Unfortunately the best I have found is allocating extra space and then using the "aligned" part. So the RingBuffer new
can request an extra 64 bytes and then return the first 64 byte aligned part of that. It wastes space but will give the alignment you need. You will likely need to set the memory before what is returned to the actual alloc address to unallocate it.
[Memory returned][ptr to start of memory][aligned memory][extra memory]
(assuming no inheritence from RingBuffer) something like:
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);
}
}
For the second requirement of having a data member of RingBuffer
also 64 byte aligned, for that if you know that the start of this
is aligned, you can pad to force the alignment for data members.
The answer to your problem is std::aligned_storage. It can be used top level and for individual members of a class.
After some more research my thoughts are:
1) Like @TemplateRex pointed out there does not seem to be a standard way to align to more than 16 bytes. So even if we use the standardized alignas(..)
there is no guarantee unless the alignment boundary is less than or equal to 16 bytes. I'll have to verify that it works as expected on a target platform.
2) __attribute ((aligned(#)))
or alignas(..)
cannot be used to align a heap allocated object as I suspected i.e. new()
doesn't do anything with these annotations. They seem to work for static objects or stack allocations with the caveats from (1).
Either posix_memalign(..)
(non standard) or aligned_alloc(..)
(standardized but couldn't get it to work on GCC 4.8.1) + placement new(..)
seems to be the solution. My solution for when I need platform independent code is compiler specific macros :)
3) Alignment for struct/class fields seems to work with both __attribute ((aligned(#)))
and alignas()
as noted in the answer. Again I think the caveats from (1) about guarantees on alignment stand.
So my current solution is to use posix_memalign(..)
+ placement new(..)
for aligning a heap allocated instance of my class since my target platform right now is Linux only. I am also using alignas(..)
for aligning fields since it's standardized and at least works on Clang and GCC. I'll be happy to change it if a better answer comes along.
I don't know if it is the best way to align memory allocated with a new operator, but it is certainly very simple !
This is the way it is done in thread sanitizer pass in GCC 6.1.0
#define ALIGNED(x) __attribute__((aligned(x)))
static char myarray[sizeof(myClass)] ALIGNED(64) ;
var = new(myarray) myClass;
Well, in sanitizer_common/sanitizer_internal_defs.h, it is also written
// Please only use the ALIGNED macro before the type.
// Using ALIGNED after the variable declaration is not portable!
So I do not know why the ALIGNED here is used after the variable declaration. But it is an other story.
참고URL : https://stackoverflow.com/questions/20791428/what-is-the-recommended-way-to-align-memory-in-c11
'Development Tip' 카테고리의 다른 글
C ++ 최적화 프로그램이 clock ()에 대한 호출을 재정렬하는 것이 합법적입니까? (0) | 2020.11.15 |
---|---|
Objective-C 프로젝트에서 Swift Pod Framework를 가져오고 사용하는 방법 (0) | 2020.11.15 |
스레드 안전성을위한 단위 테스트? (0) | 2020.11.15 |
C # (. NET)에서 SFTP 서버에 파일을 업로드하려면 어떻게해야합니까? (0) | 2020.11.15 |
가상 키보드가 활성화 된 경우 화면 스타일링 (0) | 2020.11.15 |