Programing

모든 프로그래머가 메모리에 대해 알아야 할 사항

crosscheck 2020. 6. 22. 07:57
반응형

모든 프로그래머가 메모리에 대해 알아야 할 사항


Ulrich Drepper의 2007 년부터 모든 프로그래머가 메모리에 대해 알아야 할 내용 이 여전히 유효한지 궁금 합니다. 또한 1.0 또는 정오표보다 최신 버전을 찾을 수 없습니다.


내가 기억하는 한 Drepper의 내용은 메모리에 대한 기본 개념, 즉 CPU 캐시 작동 방식, 물리적 및 가상 메모리 및 Linux 커널이 해당 동물원을 처리하는 방식에 대해 설명합니다. 아마도 일부 예제에는 오래된 API 참조가 있지만 중요하지 않습니다. 기본 개념의 관련성에는 영향을 미치지 않습니다.

따라서 근본적인 것을 설명하는 책이나 기사는 구식이라고 할 수 없습니다. "모든 프로그래머가 기억해야 할 것"은 반드시 읽을 가치가 있지만, "모든 프로그래머"를위한 것이라고는 생각하지 않습니다. 시스템 / 임베디드 / 커널 사용자에게 더 적합합니다.


PDF 형식의 안내서는 https://www.akkadia.org/drepper/cpumemory.pdf에 있습니다.

여전히 일반적으로 우수하고 강력하게 권장합니다 (저는 다른 성능 조정 전문가가 생각합니다). Ulrich (또는 다른 사람)가 2017 업데이트를 작성하면 멋지지만 많은 작업 (예 : 벤치 마크 다시 실행)이 될 것입니다. 태그 위키의 다른 x86 성능 조정 및 SSE / asm (및 C / C ++) 최적화 링크도 참조하십시오 . Ulrich의 기사는 x86에 국한되지 않지만 대부분의 벤치 마크는 x86 하드웨어에 관한 것입니다.

DRAM 및 캐시 작동 방식에 대한 저수준 하드웨어 세부 사항은 여전히 ​​적용됩니다 . DDR4는 DDR1 / DDR2 (읽기 / 쓰기 버스트)에 대해 설명 된 것과 동일한 명령사용 합니다 . DDR3 / 4 개선은 근본적인 변화가 아닙니다. AFAIK, 모든 아치 독립적 인 내용은 여전히 ​​일반적으로 AArch64 / ARM32에 적용됩니다.

단일 스레드 대역폭에 대한 메모리 / L3 대기 시간의 영향에 대한 중요한 세부 사항은 이 답변Latency Bound Platforms 섹션을 참조하십시오 . bandwidth <= max_concurrency / latency실제로는 Xeon과 같은 현대의 많은 코어 CPU에서 단일 스레드 대역폭의 주요 병목 현상입니다. . 그러나 쿼드 코어 Skylake 데스크톱은 단일 스레드로 DRAM 대역폭을 최대한 활용할 수 있습니다. 이 링크에는 x86의 NT 저장소와 일반 저장소에 대한 아주 좋은 정보가 있습니다. 단일 스레드 메모리 처리량에서 Skylake가 Broadwell-E보다 훨씬 우수한 이유는 무엇입니까? 요약입니다.

따라서 6.5.8 6.5.8 모든 대역폭 활용 에서 다른 NUMA 노드 및 원격 노드의 원격 메모리 사용에 대한 제안은 메모리 컨트롤러가 단일 코어보다 더 많은 대역폭을 갖는 최신 하드웨어에서 비생산적입니다. 대기 시간이 짧은 스레드 간 통신을 위해 동일한 NUMA 노드에서 여러 개의 메모리 부족 스레드를 실행하는 것이 순전히 이점이 있지만 대기 시간에 민감하지 않은 높은 대역폭을 위해 원격 메모리를 사용하는 상황을 상상할 수 있습니다. 그러나 이것은 꽤 모호합니다. 일반적으로 스레드를 NUMA 노드로 나누고 로컬 메모리를 사용하도록하십시오. 코어 당 대역폭은 최대 동시성 제한 (아래 참조)으로 인해 대기 시간에 민감하지만 한 소켓의 모든 코어는 일반적으로 해당 소켓의 메모리 컨트롤러를 포화시킬 수 있습니다.


(보통) 소프트웨어 프리 페치를 사용하지 마십시오

변경된 주요 특징 중 하나는 하드웨어 프리 페치가 펜티엄 4보다 훨씬 뛰어나고 상당히 큰 보폭까지 스트 라이딩 된 액세스 패턴과 한 번에 여러 스트림 (예 : 4k 페이지 당 하나의 앞으로 / 뒤로)을 인식 할 수 있다는 것입니다. 인텔의 최적화 매뉴얼 에서는 Sandybridge 제품군 마이크로 아키텍처를위한 다양한 수준의 캐시에서 HW 프리 페처에 대한 세부 정보를 설명합니다. 아이비 브릿지 이상은 빠른 시작을 트리거하기 위해 새 페이지에서 캐시 미스를 기다리는 대신 다음 페이지 하드웨어 프리 페치를 갖습니다. AMD가 최적화 매뉴얼에 비슷한 내용이 있다고 가정합니다. 인텔 설명서에는 오래된 조언이 많이 포함되어 있으며 그 중 일부는 P4에만 적합합니다. Sandybridge 관련 섹션은 물론 SnB에 대해서는 정확하지만미세 융합 UOP의 라미네이션은 HSW에서 변경되었으며 매뉴얼에는 언급되지 않았습니다 .

요즘 일반적인 조언은 이전 코드에서 모든 SW 프리 페치를 제거하고 프로파일 링에 캐시 누락이 표시되고 메모리 대역폭을 포화시키지 않는 경우에만 다시 삽입하는 것이 좋습니다. 이진 검색 다음 단계에서 양쪽을 프리 페치하면 여전히 도움이 될 수 있습니다. 예를 들어 다음에 볼 요소를 결정하면 1/4 및 3/4 요소를 프리 페치하여로드 / 체킹 중간과 병렬로로드 할 수 있습니다.

별도의 프리 페치 스레드 (6.3.4)를 사용하라는 제안은 전적으로 더 이상 사용되지 않으며 Pentium 4에서만 가능하다고 생각합니다. P4에는 하이퍼 스레딩 (하나의 물리적 코어를 공유하는 2 개의 논리 코어)이 있었지만 충분한 추적 캐시 (및 동일한 코어에서 두 개의 전체 계산 스레드를 실행하는 처리량을 확보 할 수 있습니다. 그러나 최신 CPU (Sandybridge-family 및 Ryzen)는 훨씬 강력 하며 실제 스레드를 실행하거나 하이퍼 스레딩을 사용하지 않아야합니다 (다른 논리 코어를 유휴 상태로 두어 솔로 스레드가 ROB를 분할하는 대신 전체 리소스를 갖도록 함).

소프트웨어 프리 페치는 항상 "취약" 했습니다. 속도 향상을위한 올바른 매직 튜닝 수는 하드웨어의 세부 사항 및 시스템로드에 따라 다릅니다. 너무 일찍 요구 부하가 발생하기 전에 추방되었습니다. 너무 늦었고 도움이되지 않습니다. 이 블로그 기사 는 문제의 비 순차적 부분을 프리 페치하기 위해 Haswell에서 SW 프리 페치를 사용하는 흥미로운 실험에 대한 코드 + 그래프를 보여줍니다. 프리 페치 명령어를 올바르게 사용하는 방법을 참조하십시오 . . NT 프리 페치는 흥미롭지 만, L1에서 조기에 제거하면 L2뿐만 아니라 L3 또는 DRAM으로 가야한다는 의미에서 훨씬 취약합니다. 성능의 모든 마지막 한 방울을 필요로하는 경우 그리고 당신은 SW 프리 페치가 특정 기계에 대한 조정 가치가 순차 액세스에 대한보고입니다 수 있지만, 그것은 할 수있다 메모리에서 병목 현상에 접근하는 동안 ALU 작업이 충분할 경우 여전히 속도가 느려집니다.


캐시 라인 크기는 여전히 64 바이트입니다. (L1D 읽기 / 쓰기 대역폭이 매우 높으며, 현대 CPU는 L1D에서 모두 적중 할 경우 클럭 당 2 개의 벡터로드 + 1 개의 벡터 저장소를 수행 할 수 있습니다. 캐시를 얼마나 빨리 처리 할 수 ​​있습니까?를 참조하십시오 .) AVX512를 사용하면 라인 크기 = 벡터 너비, 하나의 명령으로 전체 캐시 라인을로드 / 저장할 수 있습니다. 따라서 잘못 정렬 된 모든로드 / 스토어는 256b AVX1 / AVX2에 대한 캐시 라인 경계를 가로 지르는 대신 캐시 라인 경계를 가로 지르며, 이는 종종 L1D에 없었던 어레이에 대한 루핑 속도를 늦추지 않습니다.

정렬시 주소가 런타임에 정렬되면 정렬되지 않은로드 명령어는 페널티가 없지만, 컴파일러 (특히 gcc)는 정렬 보장에 대해 알고 있으면 자동 벡터화 할 때 더 나은 코드를 만듭니다. 실제로 정렬되지 않은 op는 일반적으로 빠르지 만 페이지 분할은 여전히 ​​아프다 (Skylake에서는 훨씬 적지 만 100에 비해 추가 사이클 대기 시간은 ~ 11이지만 처리량은 여전히 ​​페널티입니다).


Ulrich가 예측 한 바와 같이, 요즘 모든 멀티 소켓 시스템은 NUMA입니다. 통합 메모리 컨트롤러가 표준입니다. 즉, 외부 노스 브릿지가 없습니다. 그러나 멀티 코어 CPU가 널리 보급되어 있으므로 SMP는 더 이상 멀티 소켓을 의미하지 않습니다. 스카이 레이크에 네 할렘 (Nehalem)에서 인텔 CPU는 큰 사용한 포괄적 코어 사이의 일관성을위한 포수로 L3 캐시를. AMD CPU는 다르지만 자세한 내용은 명확하지 않습니다.

Skylake-X (AVX512)에는 더 이상 포괄적 인 L3이 없지만 스 누프를 실제로 모든 코어에 브로드 캐스트하지 않고 칩의 어느 곳에서 캐시 된 내용을 확인할 수있는 태그 디렉토리가 여전히 있다고 생각합니다. SKX는 불행히도 이전의 많은 코어 Xeon보다 일반적으로 지연 시간이 더 긴 링 버스 대신 메시를 사용합니다 .

기본적으로 메모리 배치 최적화에 대한 모든 조언이 여전히 적용됩니다. 캐시 누락이나 경합을 피할 수 없을 때 발생하는 정확한 세부 사항 만 다릅니다.


6.4.2 Atomic ops : 하드웨어 재조정보다 4 배 더 나쁜 CAS 재시도 루프를 보여주는 벤치 마크 lock add는 여전히 최대 경합 사례를 반영합니다 . 그러나 실제 다중 스레드 프로그램에서는 동기화가 최소로 유지되므로 (비싸기 때문에) 경합이 적으며 CAS 재시도 루프는 일반적으로 재 시도하지 않고 성공합니다.

C ++ 11 std::atomic fetch_addlock add(또는 lock xadd반환 값이 사용되는 경우)로 컴파일 되지만 CAS를 사용하여 locked 명령 으로 수행 할 수없는 작업을 수행하는 알고리즘 은 일반적으로 재앙이 아닙니다. 동일한 위치에 원자 및 비 원자 액세스를 혼합하지 않으려는 경우 gcc 레거시 내장 또는 최신 내장 대신 C ++ 11std::atomic 또는 C11을 사용하십시오 .stdatomic__sync__atomic

8.1 DWCAS ( cmpxchg16b) : gcc를 방출하도록 만들 수 있지만 객체의 절반 만 효율적으로로드하려면 추한 union해킹 이 필요합니다 . c ++ 11 CAS로 ABA 카운터를 어떻게 구현할 수 있습니까? . (DWCAS를 2 개의 개별 메모리 위치의 DCAS 와 혼동하지 마십시오. DCC의 잠금없는 원자 에뮬레이션은 DWCAS에서는 불가능하지만 트랜잭션 메모리 (x86 TSX와 같은)는 가능합니다.)

8.2.4 트랜잭션 메모리 : 몇 번의 잘못된 시작 (드물게 발생하는 버그로 인해 마이크로 코드 업데이트로 릴리스 된 후 비활성화 됨) 후 인텔은 최신 모델 Broadwell 및 모든 Skylake CPU에서 트랜잭션 메모리를 작동시킵니다. 디자인은 여전히 David Kanter가 Haswell에 대해 설명한 것입니다 . 일반 잠금 (특히 컨테이너의 모든 요소에 대해 단일 잠금을 사용하여 동일한 중요 섹션의 여러 스레드가 충돌하지 않는 코드)의 속도를 높이기 위해 잠금 해제 방법이 있습니다. ) 또는 거래에 대해 직접 알고있는 코드를 작성해야합니다.


7.5 Hugepages : 익명의 투명한 hugepages는 hugetlbfs를 수동으로 사용할 필요없이 Linux에서 잘 작동합니다. 2MiB 정렬 (예 : posix_memalign또는aligned_alloc 멍청한 ISO C ++ 17 요구 사항을 강제하지 않는 경우 size % alignment != 0) 으로 2MiB보다 큰 할당을 만듭니다 .

2MiB 정렬 익명 할당은 기본적으로 hugepages를 사용합니다. 일부 워크로드 (예 : 대량 할당을 한 후 일정 시간 동안 계속 사용)는
echo always >/sys/kernel/mm/transparent_hugepage/defrag필요할 때마다 4k 페이지로 돌아 가지 않고 커널이 실제 메모리를 조각 모음하도록하는 것이 좋습니다. ( 커널 문서 참조 ). 또는 madvise(MADV_HUGEPAGE)대규모 할당 후 사용하는 것이 좋습니다 (2MiB 정렬을 사용하는 것이 좋습니다).


Appendix B: Oprofile: Linux perf has mostly superseded oprofile. For detailed events specific to certain microarchitectures, use the ocperf.py wrapper. e.g.

ocperf.py stat -etask-clock,context-switches,cpu-migrations,page-faults,cycles,\
branches,branch-misses,instructions,uops_issued.any,\
uops_executed.thread,idq_uops_not_delivered.core -r2 ./a.out

For some examples of using it, see Can x86's MOV really be "free"? Why can't I reproduce this at all?.


From my quick glance-through it looks quite accurate. The one thing to notice, is the portion on the difference between "integrated" and "external" memory controllers. Ever since the release of the i7 line Intel CPUs are all integrated, and AMD has been using integrated memory controllers since the AMD64 chips were first released.

이 기사가 작성되었으므로 많은 것이 바뀌지 않았고 속도가 빨라졌으며 메모리 컨트롤러가 훨씬 지능적이었습니다 (i7은 변경 사항을 커밋 할 때까지 RAM에 쓰기를 지연시킵니다). . 최소한 소프트웨어 개발자가 신경 쓰는 방식은 아닙니다.

참고 URL : https://stackoverflow.com/questions/8126311/what-every-programmer-should-know-about-memory

반응형