CPU 캐시를 사용하여 코드 속도를 높이는 방법

CPU의 캐시는 기본 시스템 메모리에서 데이터에 액세스 할 때 메모리 대기 시간을 줄입니다. 개발자는 CPU 캐시를 활용하여 애플리케이션 성능을 향상시킬 수 있습니다.

CPU 캐시 작동 방식

최신 CPU에는 일반적으로 L1, L2 및 L3으로 레이블이 지정된 세 가지 수준의 캐시가 있으며, 이는 CPU가이를 확인하는 순서를 반영합니다. CPU에는 종종 데이터 캐시, 명령 캐시 (코드 용) 및 통합 캐시 (무엇이든)가 있습니다. 이러한 캐시에 액세스하는 것은 RAM에 액세스하는 것보다 훨씬 빠릅니다. 일반적으로 L1 캐시는 데이터 액세스를위한 RAM보다 약 100 배 빠르며 L2 캐시는 데이터 액세스를위한 RAM보다 25 배 빠릅니다.

소프트웨어가 실행되고 데이터 또는 명령을 가져와야 할 때 CPU 캐시를 먼저 확인한 다음 느린 시스템 RAM을 확인하고 마지막으로 훨씬 느린 디스크 드라이브를 확인합니다. 그렇기 때문에 먼저 CPU 캐시에서 필요한 것을 찾기 위해 코드를 최적화하려는 것입니다.

코드는 데이터 명령어와 데이터가있는 위치를 지정할 수 없습니다. 컴퓨터 하드웨어가 그렇게하므로 특정 요소를 CPU 캐시에 강제로 넣을 수 없습니다. 그러나 WMI (Windows Management Instrumentation)를 사용하여 시스템에서 L1, L2 또는 L3 캐시의 크기를 검색하도록 코드를 최적화하여 애플리케이션이 캐시에 액세스하는시기와 성능을 최적화 할 수 있습니다.

CPU는 캐시에 바이트 단위로 액세스하지 않습니다. 대신 그들은 일반적으로 크기가 32, 64 또는 128 바이트 인 메모리 청크 인 캐시 라인에서 메모리를 읽습니다.

다음 코드 목록은 시스템에서 L2 또는 L3 CPU 캐시 크기를 검색하는 방법을 보여줍니다.

public static uint GetCPUCacheSize (string cacheType) {try {using (ManagementObject managementObject = new ManagementObject ( "Win32_Processor.DeviceID = 'CPU0'")) {return (uint) (managementObject [cacheType]); }} catch {return 0; }} static void Main (string [] args) {uint L2CacheSize = GetCPUCacheSize ( "L2CacheSize"); uint L3CacheSize = GetCPUCacheSize ( "L3CacheSize"); Console.WriteLine ( "L2CacheSize :"+ L2CacheSize.ToString ()); Console.WriteLine ( "L3CacheSize :"+ L3CacheSize.ToString ()); Console.Read (); }

Microsoft에는 Win32_Processor WMI 클래스에 대한 추가 설명서가 있습니다.

성능 프로그래밍 : 예제 코드

스택에 개체가 있으면 가비지 수집 오버 헤드가 없습니다. 힙 기반 개체를 사용하는 경우 힙에서 개체를 수집 또는 이동하거나 힙 메모리를 압축하기위한 세대 별 가비지 수집과 관련된 비용이 항상 발생합니다. 가비지 컬렉션 오버 헤드를 피하는 좋은 방법은 클래스 대신 구조체를 사용하는 것입니다.

캐시는 배열과 같은 순차 데이터 구조를 사용하는 경우 가장 잘 작동합니다. 순차적 순서를 지정하면 CPU가 미리 읽을 수 있고 다음에 요청 될 가능성이있는 예측을 미리 읽을 수도 있습니다. 따라서 메모리에 순차적으로 액세스하는 알고리즘은 항상 빠릅니다.

임의의 순서로 메모리에 액세스하면 메모리에 액세스 할 때마다 CPU에 새 캐시 라인이 필요합니다. 이는 성능을 저하시킵니다.

다음 코드 스 니펫은 클래스에 대한 구조체 사용의 이점을 보여주는 간단한 프로그램을 구현합니다.

 struct RectangleStruct {public int 폭; public int 높이; } class RectangleClass {public int widthth; public int 높이; }

다음 코드는 클래스 배열에 대해 구조체 배열을 사용하는 성능을 프로파일 링합니다. 설명을 위해 두 가지 모두에 백만 개의 개체를 사용했지만 일반적으로 응용 프로그램에 그렇게 많은 개체가 필요하지는 않습니다.

static void Main (string [] args) {const int size = 1000000; var structs = new RectangleStruct [size]; var classes = new RectangleClass [size]; var sw = new Stopwatch (); sw.Start (); for (var i = 0; i <size; ++ i) {structs [i] = new RectangleStruct (); structs [i] .breadth = 0 structs [i] .height = 0; } var structTime = sw.ElapsedMilliseconds; sw.Reset (); sw.Start (); for (var i = 0; i <size; ++ i) {classes [i] = new RectangleClass (); 클래스 [i] .breadth = 0; 클래스 [i] .height = 0; } var classTime = sw.ElapsedMilliseconds; sw.Stop (); Console.WriteLine ( "클래스 배열에 걸린 시간 :"+ classTime.ToString () + "밀리 초."); Console.WriteLine ( "구조체 배열에 걸린 시간 :"+ structTime.ToString () + "밀리 초."); Console.Read (); }

이 프로그램은 간단합니다. 1 백만 개의 구조체 객체를 만들어 배열에 저장합니다. 또한 1 백만 개의 클래스 개체를 만들어 다른 배열에 저장합니다. 속성의 너비와 높이에는 각 인스턴스에서 0 값이 할당됩니다.

보시다시피 캐시 친화적 인 구조체를 사용하면 성능이 크게 향상됩니다.

더 나은 CPU 캐시 사용을위한 경험 규칙

그렇다면 CPU 캐시를 가장 잘 사용하는 코드를 어떻게 작성합니까? 불행히도 마법의 공식은 없습니다. 그러나 몇 가지 경험 규칙이 있습니다.

  • 불규칙한 메모리 액세스 패턴을 나타내는 알고리즘 및 데이터 구조를 사용하지 마십시오. 대신 선형 데이터 구조를 사용하십시오.
  • 더 작은 데이터 유형을 사용하고 정렬 구멍이 없도록 데이터를 구성합니다.
  • 액세스 패턴을 고려하고 선형 데이터 구조를 활용하십시오.
  • 캐시에 매핑되면 각 캐시 라인을 최대 범위까지 사용하는 공간 지역성을 개선합니다.