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


164

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


1
내가이 기사를 어딘가에서 mobi 형식으로 다운로드하여 킨들에서 쉽게 읽을 수 있는지 아는 사람이 있습니까? 확대 / 축소 / 포맷 문제로 인해 "pdf"를 읽기가 매우 어렵습니다.
javapowered

1
그것은 mobi가 아니지만 LWN은 전화 / 태블릿에서 쉽게 읽을 수있는 기사 세트로 신문을 운영했습니다. 첫 번째는 lwn.net/Articles/250967입니다
Nathan Nathan

답변:


111

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

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


3
그래, 왜 프로그래머가 SRAM과 DRAM이 아날로그 수준에서 어떻게 작동하는지 알아야하는지 모르겠다. 프로그램 작성시 큰 도움이되지 않는다. 그리고 그 지식을 정말로 필요로하는 사람들은 실제 타이밍 등에 관한 세부 사항에 관한 매뉴얼을 읽는 데 더 나은 시간을 보내고 있습니다. 그러나 HW 저수준에 관심이있는 사람들에게는? 아마도 유용하지는 않지만 최소한 재미있을 것입니다.
Voo

47
오늘날 성능 == 메모리 성능이므로 모든 고성능 응용 프로그램에서 메모리를 이해 하는 것이 가장 중요합니다. 이를 통해 게임 개발, 과학 컴퓨팅, 금융, 데이터베이스, 컴파일러, 대용량 데이터 세트 처리, 시각화, 많은 요청을 처리해야하는 모든 것 등이 관련 문서에 필수적입니다. 따라서 응용 프로그램에서 작업하는 경우에는 가능합니다. 텍스트 편집기와 같이 대부분 유휴 상태입니다. 단어는 단어를 찾고, 단어를 세고, 맞춤법을 검사하는 등의 빠른 작업을 수행해야 할 때까지 완전히 흥미롭지 않습니다.
gnzlbg

144

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 대기 시간의 영향에 대한 중요한 세부 사항은 이 답변지연 시간 바운드 플랫폼 섹션을 참조하십시오 . 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 정렬을 사용하는 것이 좋습니다).


부록 B : Oprofile : Linux perf가 대체되었습니다 oprofile. 특정 마이크로 아키텍처와 관련된 자세한 이벤트를 보려면 래퍼를 사용하십시오ocperf.py . 예 :

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

그것을 사용하는 몇 가지 예는 x86의 MOV가 실제로 "무료"일 수 있습니까?를 참조하십시오 . 왜 이것을 전혀 재현 할 수 없습니까? .


3
매우 유익한 답변과 포인터! 이것은 분명히 더 많은 투표를받을 자격이 있습니다!
claf

@ 피터 코드 (Peter Cordes) 읽을 것을 권장하는 다른 안내서 / 종이가 있습니까? 나는 고성능 프로그래머는 아니지만 그것에 대해 더 많이 배우고 매일 프로그래밍에 통합 할 수있는 방법을 선택하고 싶습니다.
user3927312

4
@ user3927312 : agner.org/optimize 는 구체적으로 x86에 대한 저수준 자료에 대한 가장 우수하고 일관된 안내서 중 하나이지만 일반적인 아이디어 중 일부는 다른 ISA에 적용됩니다. asm 가이드뿐만 아니라 Agner에는 최적화 된 C ++ PDF가 있습니다. 다른 성능 / CPU 아키텍처 링크는 stackoverflow.com/tags/x86/info를 참조하십시오 . 필자는 컴파일러의 asm 출력을 볼 가치가있을 때 컴파일러가 중요한 루프에 대해 더 나은 asm을 만들도록 도와 C ++ 최적화에 대해 작성했습니다. 손으로 쓴 asm보다 Collatz 추측을 더 빨리 테스트하기위한 C ++ 코드?
Peter Cordes

74

빠른 시일 내에서 그것은 매우 정확 해 보입니다. 주목할 것은 "통합"메모리 컨트롤러와 "외부"메모리 컨트롤러의 차이점에 대한 부분입니다. i7 라인이 출시 된 이래 인텔 CPU는 모두 통합되었으며 AMD는 AMD64 칩이 처음 출시 된 이후 통합 메모리 컨트롤러를 사용하고 있습니다.

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


5
둘 다 받아들이고 싶었을 것입니다. 하지만 난 당신의 게시물을 upvoted했습니다.
Framester

5
SW 개발자와 관련하여 가장 큰 변화는 프리 페치 스레드가 나쁜 아이디어라는 것입니다. CPU는 하이퍼 스레딩으로 2 개의 전체 스레드를 실행할 수있을만큼 강력하며 HW 프리 페치가 훨씬 뛰어납니다. 일반적으로 SW 프리 페치는 특히 순차적 액세스에있어 훨씬 덜 중요합니다. 내 대답을 참조하십시오.
Peter Cordes
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.