잘못된 공유 존재 를 증명하는 다음 예를 고려하십시오 .
using type = std::atomic<std::int64_t>;
struct alignas(128) shared_t
{
type a;
type b;
} sh;
struct not_shared_t
{
alignas(128) type a;
alignas(128) type b;
} not_sh;
한 스레드 a
는 1 단계 씩 증가 하고 다른 스레드는 b
. lock xadd
결과가 사용되지 않더라도 증분 은 MSVC로 컴파일됩니다 .
구성 내용 a
과 b
분리되어, 몇 초에 축적되어있는 값에 대한 약 10 배 이상 not_shared_t
보다 shared_t
.
지금까지 예상 된 결과 : 별도의 캐시 라인이 L1d 캐시에서 핫 상태를 유지하고, lock xadd
처리량에 병목 현상이 증가하고 , 잘못된 공유는 캐시 라인을 핑퐁하는 성능 재앙입니다. (편집자 주 : 최신 MSVC 버전 lock inc
은 최적화가 활성화 된 경우 사용합니다. 이로 인해 경합과 비경쟁 간의 차이가 벌어 질 수 있습니다.)
이제 나는 using type = std::atomic<std::int64_t>;
평범한std::int64_t
(비원 자적 증분은로 컴파일됩니다 inc QWORD PTR [rcx]
. 루프의 원자 적로드는 컴파일러가 루프가 종료 될 때까지 레지스터에 카운터를 유지하는 것을 막습니다.)
에 대한 도달 횟수 not_shared_t
는 여전히 for 보다 크지 shared_t
만 이제 두 번 미만입니다.
| type is | variables are | a= | b= |
|---------------------------|---------------|-------------|-------------|
| std::atomic<std::int64_t> | shared | 59’052’951| 59’052’951|
| std::atomic<std::int64_t> | not_shared | 417’814’523| 416’544’755|
| std::int64_t | shared | 949’827’195| 917’110’420|
| std::int64_t | not_shared |1’440’054’733|1’439’309’339|
비 원자 사례가 성능면에서 훨씬 더 가까운 이유는 무엇입니까?
다음은 재현 가능한 최소 예제를 완성하기위한 나머지 프로그램입니다. (또한 MSVC를 사용하는 Godbolt 에서 컴파일 / 실행 준비 완료)
std::atomic<bool> start, stop;
void thd(type* var)
{
while (!start) ;
while (!stop) (*var)++;
}
int main()
{
std::thread threads[] = {
std::thread( thd, &sh.a ), std::thread( thd, &sh.b ),
std::thread( thd, ¬_sh.a ), std::thread( thd, ¬_sh.b ),
};
start.store(true);
std::this_thread::sleep_for(std::chrono::seconds(2));
stop.store(true);
for (auto& thd : threads) thd.join();
std::cout
<< " shared: " << sh.a << ' ' << sh.b << '\n'
<< "not shared: " << not_sh.a << ' ' << not_sh.b << '\n';
}
비 원자 메모리 증분은 자체 저장된 값을 다시로드 할 때 저장 전달의 이점을 누릴 수 있습니다. 이는 캐시 라인이 유효하지 않은 경우에도 발생할 수 있습니다. 코어는 상점이 결국 발생한다는 것을 알고 있으며 메모리 순서 지정 규칙을 통해이 코어는 전 세계적으로 표시되기 전에 자체 상점을 볼 수 있습니다.
저장 전달은 원자 적 RMW 증분을 수행하기 위해 캐시 라인에 대한 배타적 액세스를 필요로하는 대신 정지하기 전에 증분의 저장 버퍼 수의 길이를 제공합니다 .
이 코어가 결국 캐시 라인의 소유권을 얻게되면 1 시간에 여러 저장소를 커밋 할 수 있습니다. 이는 메모리 대상 증분으로 생성 된 종속성 체인보다 6 배 더 빠릅니다. 저장 / 재로드 대기 시간 ~ 5 회 + ALU 대기 시간 1 회입니다. 따라서 실행은 비원 자적 경우에서 코어가 소유하는 동안 배수 할 수있는 비율의 1/6로 SB에 새 저장소를 넣는 것 입니다. 이것이 공유 원자와 비공유 원자 사이에 큰 차이가없는 이유입니다.
확실히 메모리 순서 지정 기계가 지워질 것입니다. 그 및 / 또는 SB 가득 참은 잘못된 공유 사례에서 처리량을 낮추는 원인 일 수 있습니다. 하이퍼 형제와 비 하이퍼 형제간에 메모리 위치를 공유하는 생산자-소비자의 대기 시간 및 처리량 비용은 얼마입니까? 에 대한 답변과 의견을 참조하십시오 . 이와 같은 다른 실험을 위해.
A lock inc
또는 lock xadd
작업 전에 저장소 버퍼를 강제로 비우고 작업의 일부로 L1d 캐시에 대한 커밋을 포함합니다. 이로 인해 스토어 포워딩이 불가능하고 캐시 라인이 Exclusive 또는 Modified MESI 상태에있는 경우에만 발생할 수 있습니다.
관련 :
최신 x86 구현이 둘 이상의 이전 저장소에서 저장 될 수 있습니까? (아니요,하지만 여기에있는 세부 정보는 저장소 버퍼가 수행하는 작업과 다시로드가 저장소와 정확히 겹치는이 경우 저장소 전달이 어떻게 작동하는지 정확히 이해하는 데 도움이 될 수 있습니다.)
이 기사는 인터넷에서 수집됩니다. 재 인쇄 할 때 출처를 알려주십시오.
침해가 발생한 경우 연락 주시기 바랍니다[email protected] 삭제
몇 마디 만하겠습니다