최신 CPU에서 단일 할당 ADT 지향 코드의 성능


32

단일 할당으로 불변 데이터에서 작업하면 더 많은 메모리가 필요하다는 명백한 효과가 있습니다 . 커버 아래 컴파일러가 문제를 덜 만들기 위해 포인터 트릭을 수행하지만 끊임없이 새로운 값을 생성하기 때문 입니다.

그러나 성능의 손실이 CPU (특히 메모리 컨트롤러)가 메모리가 많이 변경되지 않았다는 사실을 활용할 수있는 방식의 이점보다 중요하다는 사실을 몇 번 들었습니다.

나는 이것이 어떻게 사실인지 (또는 그렇지 않은지) 누군가가 밝힐 수 있기를 바랐 습니다.

에서 다른 게시물에 댓글 이 언급 된 추상 데이터 유형 (ADT의), 나를 더욱 궁금하게하는이 함께 할 수있는 방법 ADT를 특별히 메모리 방식에게 CPU의 거래에 영향을합니까? 그러나 이것은 제쳐두고, 주로 언어의 순도가 CPU와 캐시의 성능에 어떻게 영향을 미치는지에 관심이 있습니다.


2
이것은 멀티 스레딩에서 주로 유용합니다. 독자는 스냅 샷을 원자 적으로 잡아서 읽을 때 변경되지 않는다는 지식에 안전합니다.
ratchet freak

@ratchetfreak 나는 프로그래밍 관점에서 코드가 더 안전하다는 것을 얻지 만, 호기심은 CPU의 메모리 컨트롤러에 관한 것입니다. 메모리 컨트롤러에 더 효과적이라고 말한 손에 대해, 그리고 이것이 사실인지 아닌지를 말할 정도로 충분히 낮은 수준의 세부 사항을 알지 못합니다.
Jimmy Hoffa

그것이 사실이더라도, 메모리 수정이 적다는 것이 불변성에 대한 최고의 판매 포인트라고 생각하지 않습니다. 결국 메모리는 수정 될 것이며, CPU와 메모리 관리자는 수년에 걸쳐 꽤 잘 해냈습니다.
Rein Henrichs

1
또한 메모리 효율성이 불변 구조를 사용할 때 컴파일러 최적화에 의존 할 필요는 없다는 점을 지적하고 싶습니다. 이 예제에서는 let a = [1,2,3] in let b = 0:a in (a, b, (-1):c)공유 메모리 요구 사항을 줄일 수 있지만, 정의에 따라 달라집니다 (:)[]하지 컴파일러. 내 생각 엔? 이것에 대해 확실하지 않습니다.

답변:


28

CPU (특히 메모리 컨트롤러)는 메모리가 변경되지 않았다는 사실을 이용할 수 있습니다

이점은이 사실은 데이터에 액세스 할 때 컴파일러가 membar 명령어 를 사용하지 못하게한다는 것 입니다.

메모리 배리어 (멤버, 메모리 펜스 또는 펜스 명령어라고도 함)는 중앙 처리 장치 (CPU) 또는 컴파일러가 배리어 명령 전후에 발행 된 메모리 작업에 순서 제약 조건을 적용하게하는 일종의 배리어 명령입니다. 이는 일반적으로 특정 작업이 장벽 이전과 다른 작업 이후에 수행된다는 것을 의미합니다.

대부분의 최신 CPU는 성능 최적화를 사용하여 순서가 잘못 실행될 수 있으므로 메모리 장벽이 필요합니다. 메모리 작업 (로드 및 저장)의 재정렬은 일반적으로 단일 실행 스레드 내에서 눈에 띄지 않지만 신중하게 제어되지 않으면 동시 프로그램 및 장치 드라이버에서 예기치 않은 동작을 일으킬 수 있습니다 ...


멀티 코어 CPU에서 다른 스레드에서 데이터에 액세스하면 다음과 같이 진행됩니다. 각기 다른 스레드는 다른 코어에서 실행됩니다. 각 스레드는 자체 캐시 (로컬에서 로컬로)를 사용합니다.

데이터가 변경 가능하고 프로그래머가 다른 스레드간에 일관성을 유지해야하는 경우 일관성을 보장하기위한 조치를 취해야합니다. 프로그래머에게 이는 특정 스레드의 데이터에 액세스 할 때 동기화 구문을 사용하는 것을 의미 합니다.

컴파일러의 경우 코드의 동기화 구성 은 다른 코어의 캐시를 보장하기 위해 코어 중 하나의 데이터 사본에 대한 변경 사항이 올바르게 전파 ( "게시")되도록 멤바 명령어 를 삽입해야 을 의미합니다. 동일한 최신 사본을 보유하십시오.

약간 단순화하면 아래 참고 사항을 참조하십시오 . 멤버 용 멀티 코어 프로세서에서 발생하는 작업 은 다음 과 같습니다.

  1. 캐시에 실수로 쓰지 않도록 모든 코어가 처리를 중지 합니다.
  2. 로컬 캐시에 대한 모든 업데이트는 글로벌 캐시에 다시 기록되므로 글로벌 캐시에 최신 데이터가 포함됩니다. 시간이 좀 걸립니다.
  3. 업데이트 된 데이터는 글로벌 캐시에서 로컬 데이터로 다시 기록되므로 로컬 캐시에 최신 데이터가 포함됩니다. 시간이 좀 걸립니다.
  4. 모든 코어가 실행을 재개합니다.

당신은 참조 데이터가 앞뒤로 전역 및 로컬 캐시 사이에 복사되는 동안 모든 코어가 아무것도하지 않고있다 . 변경 가능한 데이터가 올바르게 동기화되도록 (스레드 세이프) 필요합니다. 코어가 4 개이면 캐시가 동기화되는 동안 4 개가 모두 중지되고 대기합니다. 8 개가 있으면 8 개 모두 중지합니다. 16 개가 있다면 ... 15 개 코어가 정확히 아무 것도하지 않고이 중 하나에서해야 할 일을 기다리는 것입니다.

자, 데이터가 불변 인 경우 어떻게되는지 보자? 어떤 스레드가 액세스하는지에 관계없이 동일하게 보장됩니다. 프로그래머의 경우 이는 특정 스레드의 데이터에 액세스 (읽기) 할 때 동기화 구문 을 삽입 할 필요없음을 의미 합니다.

컴파일러의 경우 이는 membar 명령어 를 삽입 할 필요없음 을 의미 합니다 .

결과적으로 데이터에 대한 액세스는 코어를 중지 할 필요가 없으며 전역 캐시와 로컬 캐시간에 데이터를주고받는 동안 기다릴 필요가 없습니다. 그건의 메모리가 변이되지 않는다는 사실을 이용 .


참고 다소 단순화 상기 설명 데이터를 좀 더 복잡하게 부정적인 효과가 예를 들어 가변되는 상품 파이프 라이닝 . 필요한 순서를 보장하기 위해 CPU는 데이터 변경의 영향을받는 말뚝을 무효화해야합니다. 이는 또 다른 성능 저하입니다. 이것이 모든 파이프 라인의 간단한 (따라서 신뢰할 수있는) 무효화에 의해 구현된다면, 부정적인 영향이 더 증폭된다.



당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.