최신 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.