문제

할 때 쓰는 시뮬레이션을 내 친구가 말한 그가 좋아하는 그를 쓰려고 하는 프로그램에 맞게 충분히 작은 캐시입니다.이 모든 진정한 의미는?을 이해 캐시보다 더 빨리 그리고 메인 메모리이다.이 가능하도록 지정하려면 프로그램에서 실행시 적어도 로드하는 변수로 캐시?우리는 작성 시뮬레이션 그래서 어떤 성능 최적화를 얻는 것은 아주 유용하다고 생각합니다.

당신이 알고 있는 경우의 어떤 좋은 연결을 설명하는 CPU 캐싱한 다음,점에서 나는 방향이다.

도움이 되었습니까?

해결책

에서 최소한으로 전형적인 데스크탑 CPU,당신은 정말 지정에 대해 많은 캐쉬 사용량이 직접 있습니다.당신은 여전히 쓰려고 캐시 친화적 코드 하지만입니다.코드에면,이것은 풀 루프(에 대한 하나만 분명한 예)은 거의 유용한다--확장 코드,그리고 현대 CPU 일반적으로 오버헤드를 최소화합의 보안 시스템을 제공합니다.할 수 있는 일반적으로 더 많은 데이터 면에,개선하는 지역의 참조,보호에 대하여 거짓 공유(예:두 자주 사용되는 조각의 데이터를 사용하려고 동일한 부분의 캐시 동안,다른 부분품을 사용하지 않).

편집(몇 가지 포인트를 좀 더 명시적으):

일반적인 CPU 가 다양합니다.현대 데스크탑 프로세서 일반적으로 적어도 2 자주 3 수준의 캐시입니다.여(적어도 거의)유니버설 계약,"1"은 캐시"가"을 처리하는 요소,그리고 번호 거기에서 이동(수준 2 은 다음 레벨 3 그 후,etc.)

대부분의 경우에,(적어도)레벨 1 캐시이 두 개로 분할 반:는 지시와 데이터 캐시(Intel486 은 거의 유일한 예외는 내가 알고 있어요,하나의 캐시 모두를 위해 지침과 데이터-그러나 그것은 그래서 철저하게 사용되지 않 그것은 아마 하지 않는 장점이 많이 생각했).

대부분의 경우,캐시 구성되어의 집합으로"선".의 내용을 캐시적으로 읽기,쓰 및 추적 한 줄니다.다른 말로 하면,CPU 사용하려는 데이터의 어떤 부분에서 캐쉬 라인,는 전체 캐시 라인에서 읽은 다음 낮은 수준의 스토리지.캐시 가까이 있는 CPU 에는 일반적으로 작아지고 작은 캐쉬 라인입니다.

이 기본적인 건물을 리드하는 특성의 대부분의 캐쉬에 문제가 쓰는 코드입니다.가능한 한 많이,당신이 원하는 무언가를 읽으로 캐시면,모든 것을 가진 그것은 당신을,그 다음에 이동하는 다른 뭔가가 있습니다.

즉,당신은 데이터를 처리하는,그것은 일반적으로 더 나은 읽을 비교적 적은 양의 데이터(작은에 캐시),할만큼 처리하는 데이터 수 있듯이,그 다음에 이동의 덩어리이다.같은 알고리즘 퀵는 신속하게 휴식을 많은 양의 입력에 점차적으로 작은 조각이 더 많거나 적은 자동으로,그래서 그들은 매우 캐싱하기에,거의 관계없이 정확한 정보의 삭제합니다.

이것은 또한 의미를 가지고있는 방법에 대한 당신이 쓰는 코드입니다.이 있는 경우 루프 다음과 같:

for i = 0 to whatever
   step1(data);
   step2(data);
   step3(data);
end for

당신은 일반적으로 좋 모인의 많은 단계를 함께 할 수 있는 금액 맞는 것입니다.분에 당신은 오버플로우시는 성능 수 있/은 급격히 하락합니다.코드가 단계 3 위 큰 충분히 맞지 않을 것으로 캐쉬에,당신은 일반적으로 더 나을 것을 깨고 반복으로 두 개의 조각 같은 이(가능하면):

for i = 0 to whatever
    step1(data);
    step2(data);
end for

for i = 0 to whatever
    step3(data);
end for

루프 풀은 상당히 뜨겁게 경쟁 주제입니다.한 손으로,그것은 으로 이어질 코드는 훨씬 더 CPU 를 친절한,오버헤드를 줄임 지침의 실행을 위한 루프를 자체입니다.동일한 시간에,그것은 할 수 있습니다(그리고 일반적으로 않습니다)코드 크기가 증가,그래서 그것은 상대적으로 캐시인할 수 있게 되었습니다.내 자신의 경험에서 합성는 벤치마킹하는 경향이 정말 작은 금액의 처리에 정말 많은 양의 데이터를 얻는 것에서 많은 루프 언.에서 더 많은 실용적인 코드에 당신은 더 많이 처리하는 개인 데이터의 조각을 얻을,당신은 훨씬 덜--과 넘치는 캐쉬를 선도하는 심각한 성능의 손실 없는 특히 희소에서 모두.

데이터 캐시는 또한 제한된 크기가 있습니다.즉,당신은 일반적으로 원하는 데이터는 포장으로 밀도가 가능한 그래서 가능한 많은 데이터를 맞는 것입니다.그냥 하나 분명한 예제는 데이터 구조의 연결되어 함께 포인터 필요가 꽤 얻을 측면에서 전산모사의 복잡성을 최대한 양의 데이터 캐시 공간에 의해 사용되는 포인터입니다.만약 당신이 이용하기 위하여 려고 하고 있 연결된 데이터 구조는,당신은 일반적으로하고 싶어 있는지 확인하세요 함께 연결하는 상대적으로 큰 조각의 데이터입니다.

많은 경우에,그러나 내가 찾는 트릭 내가 원래를 배웠 피팅에 대한 데이터로 작은 양의 메모리에서 작은 프로세서는(주)폐기는 수십 년 동안,매우 현대적인 프로세서를 지원합니다.의 목적은 지금에 맞게 더 많은 데이터 캐시는 대신의 주 메모리,하지만 효과가 거의 동일합니다.꽤 몇 가지 경우에,당신은 당신의 생각할 수 있는 CPU 지침으로 거의 무료,및 전반적인 속도로 실행에 의해 지배되는 대역폭을 캐시(또는 주기억),그래서 여분의 처리를 풀고에서 데이터는 고밀도 형식으로 작동합니다.때 이것은 특히 사실 당신과 함께 다루고 있어 충분한 데이터는 모두에 맞게 캐시 모두에서 더 이상,전반적인 속도가 적용되는 대역폭을 메인 메모리이다.이 경우에는 실행할 수 있습니다 의 지침을 저장하려면 몇 가지 메모리에 읽고,여전히 상황이 개선됩니다.

병렬처리를 약화시킬 수 있는 문제입니다.많은 경우에,다시 쓰기 코드를 허용한 병렬 처리로 이어질 수 있습 거의 없는 성능 향상,또는 때로는 성능 손실이다.면 전반적인 속도가 적용되는 대역폭 CPU 에서 메모리를 많이 있는 코어에 대해 경쟁하는 대역폭을 할 가능성이 어떤 좋은(고 할 수 있을 정당).이러한 경우에 사용 여러 개의 코어 속도를 향상하는 자주 하는 심지어 더 팩 데이터를 더 세밀한,및 활용을 더욱 처리 능력을 압축을 풀고,데이터 그래서 진짜 속도로 증가에서 대역폭을 줄이는 것이 소모되고,추가 코어에 유지에서 잃고 시간을 데이터 압축을 풀서 더 조밀 형식입니다.

다른 cache-기반으로 문제가 발생할 수 있는 병렬 코딩은 공유(및 거짓 공유)의 변수입니다.는 경우 두개(혹은 많이)중핵 필요를 작성하여 동일한 위치에는 메모리 캐시줄을 잡고는 데이터는 끝을 왕복하고 코어 각 핵심스 공유된 데이터입니다.결과는 종종 코드를 실행하는 느린에서 병렬했던 것보다 시리얼(즉,하나의 코어에서).거의 변형이라"거짓 공유",에는 코드는 다른 코어가 작성하는 별도의 데이터 에 대한 데이터는 다른 중핵 끝에 캐시 라인입니다.부터 캐시트의 데이터 순수의 측면에서 전체의 라인을 데이터를 데이터를 얻을 단행하고 코어 어쨌든 선도,정확히 동일한 문제입니다.

다른 팁

여기 정말 좋은 링크가 있습니다 종이 Christer Ericsson의 캐시/메모리 최적화 (전쟁 I/II/III 명성의 신). 몇 살이지만 여전히 매우 관련이 있습니다.

캐시에 대해 알고 싶었던 것보다 더 많은 것을 알려주는 유용한 논문은 모든 프로그래머가 기억에 대해 알아야 할 것 Ulrich Drepper에 의해. 헤네시 그것을 매우 철저하게 다룹니다. Christer와 Mike Acton은 이것에 대해 많은 좋은 것들을 썼습니다.

나는 당신이 명령 캐시보다 데이터 캐시에 대해 더 걱정해야한다고 생각합니다. 내 경험상 DCache Misses는 더 빈번하고 고통스럽고 유용하게 고정되어 있습니다.

업데이트 : 2014 년 1 월 13 일이 시니어 칩 디자이너에 따르면, 캐시 미스는 이제 코드 성능의 압도적으로 지배적 인 요소이므로 기본적으로로드, 매장, 정수의 상대적인 성능 병목 현상 측면에서 80 년대 중반 및 빠른 286 칩으로 돌아갑니다. 산술 및 캐시가 누락됩니다.

Cliff의 현대 하드웨어의 충돌 코스 클릭 @ azul . . . . .

--- 이제 정기적으로 예정된 프로그램으로 돌아갑니다 ---

때로는 예가 무언가를하는 방법에 대한 설명보다 낫습니다. 그 정신으로 여기에는 칩 캐시에서 더 잘 사용할 수 있도록 코드를 어떻게 변경했는지에 대한 특히 성공적인 예가 있습니다. 이것은 얼마 전에 486 CPU에서 이루어졌으며 후자는 1 세대 Pentium CPU로 마이그레이션되었습니다. 성능에 미치는 영향은 비슷했습니다.

예 : 첨자 매핑

다음은 데이터를 범용 유틸리티를 가진 칩의 캐시에 맞추는 데 사용한 기술의 예입니다.

나는 길이가 1,250 개의 원소 인 이중 플로트 벡터를 가졌으며, 이는 매우 긴 꼬리가있는 역학 곡선이었습니다. 곡선의 "흥미로운"부분은 약 200 개의 고유 한 값 만 가지고 있지만 CPU 파이프 라인을 혼란스럽게하기 위해 2면 if () 테스트를 원하지 않았습니다 (따라서 첨자로 사용될 수있는 긴 꼬리는 가장 극단적입니다. Monte Carlo 코드가 뱉어 낼 것입니다), 코드의 "핫스팟"내부에 12 개의 다른 조건부 테스트에 대한 지점 예측 논리가 필요했습니다.

나는 이중 벡터의 첨자로 8 비트 ints의 벡터를 사용한 계획에 정착하여 256 개의 요소로 단축되었습니다. 작은 INT는 모두 0보다 128 앞에서 동일한 값을 가졌으며 0은 0 이후 128 개를 가졌으므로 중간 256 값을 제외하고는 모두 이중 벡터의 첫 번째 또는 마지막 값을 가리 켰습니다.

이로 인해 스토리지 요구 사항은 복식의 경우 2K, 8 비트 첨자의 경우 1,250 바이트를 줄였습니다. 이것은 10,000 바이트가 3,298로 줄어 듭니다. 이 프로그램 이이 내부 루프에서 90% 이상을 소비했기 때문에 2 개의 벡터는 8K 데이터 캐시에서 벗어나지 않았습니다. 이 프로그램은 즉시 성능을 두 배로 늘 렸습니다. 이 코드는 1 백만 개의 모기지 대출에 대한 OAS 가치를 계산하는 과정에서 ~ 1,000 억 번에 도달했습니다.

곡선의 꼬리는 거의 터치되지 않았기 때문에 작은 int 벡터의 중간 200-300 요소 만 실제로 캐시에 보관되었으며 160-240 개의 중간 복식이 1/8 분기에 관심 지급을 나타냅니다. 오후에 1 년 이상 최적화를 보냈던 프로그램에서 성능이 급격히 증가했습니다.

나는 또한 내 경험이었던 것처럼 Jerry에 동의합니다. 명령 캐시에 대한 코드를 기울이는 것은 데이터 캐시/s에 대한 최적화만큼 성공적이지 않습니다. 이것이 제가 AMD의 일반적인 캐시가 인텔의 별도 데이터 및 명령 캐시만큼 도움이되지 않는다고 생각하는 한 가지 이유입니다. IE : 캐시를 어기는 지시를 원하지 않습니다. 부분적으로 이것은 CISC 명령 세트가 원래 CPU와 메모리 속도의 큰 차이를 보충하기 위해 만들어 졌기 때문에 80 년대 후반의 수차를 제외하고는 항상 사실이었습니다.

내가 데이터 캐시를 선호하고 명령 캐시를 즐기기 위해 사용하는 또 다른 좋아하는 기술은 구조 정의에서 많은 비트 인트를 사용하는 것입니다. 4 비트 INT를 마스킹하기 위해 연중 한 달을 잡거나 연중 날을 보유하기 위해 9 비트 등을 가리려면 CPU 사용 마스크는 비트가 사용중인 호스트 정수를 마스크하기 위해 마스크를 사용해야합니다. 데이터는 캐시와 버스 크기를 효과적으로 증가 시키지만 더 많은 지침이 필요합니다. 이 기술은 합성 벤치 마크, 사용자와 프로세스가 리소스를 놓고 경쟁하는 바쁜 시스템에서는 잘 수행되지 않는 코드를 생성하지만 훌륭하게 작동합니다.

이 주제는이 주제 정의를 할 시간이 될 때까지 자리 표시 자 역할을 할 것이지만, 나는 진정으로 획기적인 이정표로 간주되는 것을 공유하고 싶었습니다. 즉, 새로운 인텔 Hazwell 마이크로 프로세서에서 전용 비트 조작 지침의 도입.

StackoverFlow에 여기에 일부 코드를 썼을 때 PC가 도입 된 후 30 세 이상의 비트를 역전시키기 위해 여기에 코드를 썼을 때, 마이크로 프로세서는 비트에 많은 관심이나 자원을 바치지 않습니다. 변화. 특히, 우선, Bool 유형은 현재의 엄청나게 낭비적인 바이트 대신 C/C ++의 실제 비트 데이터 유형이되는 것을보고 싶습니다.

Hazwell's new Bit Manipulation Instructions

업데이트 : 12/29/2013

나는 최근에 밀리 초의 세분화의 시스템에 대한 512 개의 다른 리소스 사용자의 요구를 추적하는 링 버퍼를 최적화 할 기회가있었습니다. 현재 슬라이스의 리소스 요청의 합을 추가하고 1,000 밀리 초의 자원 요청으로 구성된 1,000 번째 시간 슬라이스의 요청을 빼는 모든 밀리 초를 발사하는 타이머가 있습니다.

머리, 꼬리 벡터는 머리가 처음있을 때를 제외하고는 메모리에서 서로 바로 옆에 있었고, 꼬리가 배열 시작 부분에서 래핑하고 다시 시작되었습니다. 그러나 (롤링) 요약 슬라이스는 고정 된 정적으로 할당 된 배열로 있었는데, 그 중 하나에 특히 가깝지 않았으며 힙에서 할당되지 않았다.

이것에 대해 생각하고 코드를 연구하는 몇 가지 세부 사항이 저의 관심을 끌었습니다.

  1. 들어온 요구는 인접한 코드 라인에서 서로 바로 옆에 헤드와 요약 슬라이스에 추가되었습니다.

  2. 타이머가 발사되면 꼬리가 요약 슬라이스에서 빼고 결과는 예상대로 요약 슬라이스에 남겨졌습니다.

  3. 두 번째 함수는 타이머가 발사되었을 때 호출됩니다. 특히 .... 헤드가 꼬리를 덮어 쓰여서 동일한 메모리 위치를 차지하여 새 꼬리가 다음 512 메모리 위치를 차지하거나 포장했습니다.

  4. 사용자는 512에서 4098 또는 더 많은 수요 수가 더 많은 유연성을 원했습니다. 나는 이것을하는 가장 강력하고 바보 방지 방법은 1,000 타임 슬라이스와 요약 슬라이스를 하나의 연속 메모리 블록으로 함께 할당하여 요약 슬라이스가 다른 길이로 끝나는 것이 불가능할 수 있도록하는 것이 었습니다. 다른 1,000 타임 슬라이스보다.

  5. 위의 점을 감안할 때, 요약 슬라이스가 한 위치에 남아있는 대신 더 많은 성능을 얻을 수 있는지 궁금해하기 시작했습니다. 새로운 요구 사항을 추가하고 타이머가 발사되고 꼬리 값이 요약에서 빼면 꼬리 바로 옆에 있습니다.

나는 이것을 정확히했지만 그 과정에서 몇 가지 추가 최적화를 발견했습니다. 요약 슬라이스 대신 결과를 꼬리에 남겨 두도록 롤링 요약을 계산 한 코드를 변경했습니다. 왜요? 바로 다음 함수는 요약 슬라이스를 꼬리가 차지하는 메모리로 이동하기 위해 memcpy ()를 수행하는 것이었기 때문입니다. (이상하지만 사실, 꼬리는 링의 끝까지 머리를 감싸게됩니다). 요약 결과를 꼬리에 남겨두면 memcpy ()를 수행 할 필요가 없었으므로 Psummary에 ptail을 할당해야했습니다.

비슷한 방식으로, 새로운 헤드는 현재 오래된 요약 Slice의 오래된 메모리 위치를 차지했기 때문에 Psummary를 Phead에 할당하고 모든 값을 밈 세트로 0으로 제로화했습니다.

링의 끝으로가는 길을 이끌고 (실제로 드럼, 512 트랙 너비)는 꼬리 였지만, 나는 그 조건을 감지하기 위해 포인터를 일정한 펜 토링 포인터와 비교해야했습니다. 다른 모든 포인터에는 벡터의 포인터 값이 바로 앞에 할당 될 수 있습니다. IE : 포인터의 1 : 3에 대한 조건부 테스트 만 필요했습니다.

초기 설계는 BYTE INT를 사용하여 캐시 사용을 최대화했지만이 제약 조건을 완화 할 수있었습니다. 사용자가 밀리 초당 사용자 당 높은 리소스 카운트를 처리하도록 사용자의 요청을 충족 시켜서 서명되지 않은 반바지를 사용하고 여전히 사용합니다. 이중 성능, 512 개의 서명되지 않은 반바지의 3 개의 인접 벡터가 있더라도 L1 캐시의 32K 데이터 캐시는 필요한 3,720 바이트를 쉽게 보유 할 수 있으며 그 중 2/3은 방금 사용 된 위치에있었습니다. 꼬리, 요약 또는 헤드 포장 된 경우에만 8MB L3Cache에서 중요한 "단계"로 분리 된 3 개 중 하나였습니다.

이 코드의 총 런타임 메모리 풋 프린트는 2MB 미만이므로 온칩 캐시에서 완전히 실행되며 4 개의 코어가있는 i7 칩 에서도이 프로세스의 4 인스턴스가 성능이 저하되지 않고 실행될 수 있습니다. 및 총 처리량은 5 개의 프로세스가 실행되면 약간 증가합니다. 캐시 사용에 대한 Opus Magnum입니다.

대부분의 C/C ++ 컴파일러는 "속도"보다는 크기를 최적화하는 것을 선호합니다. 즉, 작은 코드는 일반적으로 캐시 효과로 인해 롤드되지 않은 코드보다 빠르게 실행됩니다.

내가 당신이라면, 나는 어떤 코드의 코드 부분이 핫스팟인지 알 수있을 것입니다.

  • 함수 호출이 포함되지 않은 단단한 루프.
  • 이는 프로파일 러에서 결정할 수있는 실행 시간 (예 :> = 10%)의 상당 부분을 차지합니다. (스택을 수동으로 샘플링합니다.)

핫스팟이 있다면 캐시에 맞아야합니다. 당신이 어떻게 그렇게한다고 말하는지 잘 모르겠지만 자동이라고 생각합니다.

라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top