programing

캐시 지연 시간 측정

elecom 2023. 9. 15. 20:45
반응형

캐시 지연 시간 측정

그래서 저는 C를 이용하여 L1, L2, L3 캐시의 레이턴시를 측정하려고 합니다.저는 그것들의 크기를 알고 있고 그것을 하는 방법을 개념적으로 이해하고 있다고 생각합니다. 그러나 저는 제 구현에 문제가 있습니다.프리페칭과 같은 다른 하드웨어의 복잡성이 문제를 일으키고 있는지 궁금합니다.

#include <time.h>
#include <stdio.h>
#include <string.h>

int main(){
    srand(time(NULL));  // Seed ONCE
    const int L1_CACHE_SIZE =  32768/sizeof(int);
    const int L2_CACHE_SIZE =  262144/sizeof(int);
    const int L3_CACHE_SIZE =  6587392/sizeof(int);
    const int NUM_ACCESSES = 1000000;
    const int SECONDS_PER_NS = 1000000000;
    int arrayAccess[L1_CACHE_SIZE];
    int arrayInvalidateL1[L1_CACHE_SIZE];
    int arrayInvalidateL2[L2_CACHE_SIZE];
    int arrayInvalidateL3[L3_CACHE_SIZE];
    int count=0;
    int index=0;
    int i=0;
    struct timespec startAccess, endAccess;
    double mainMemAccess, L1Access, L2Access, L3Access;
    int readValue=0;

    memset(arrayAccess, 0, L1_CACHE_SIZE*sizeof(int));
    memset(arrayInvalidateL1, 0, L1_CACHE_SIZE*sizeof(int));
    memset(arrayInvalidateL2, 0, L2_CACHE_SIZE*sizeof(int));
    memset(arrayInvalidateL3, 0, L3_CACHE_SIZE*sizeof(int));

    index = 0;
    clock_gettime(CLOCK_REALTIME, &startAccess); //start clock
    while (index < L1_CACHE_SIZE) {
        int tmp = arrayAccess[index];               //Access Value from L2
        index = (index + tmp + ((index & 4) ? 28 : 36));   // on average this should give 32 element skips, with changing strides
        count++;                                           //divide overall time by this 
    }
    clock_gettime(CLOCK_REALTIME, &endAccess); //end clock
    mainMemAccess = ((endAccess.tv_sec - startAccess.tv_sec) * SECONDS_PER_NS) + (endAccess.tv_nsec - startAccess.tv_nsec);
    mainMemAccess /= count;

    printf("Main Memory Access %lf\n", mainMemAccess);

    index = 0;
    count=0;
    clock_gettime(CLOCK_REALTIME, &startAccess); //start clock
    while (index < L1_CACHE_SIZE) {
        int tmp = arrayAccess[index];               //Access Value from L2
        index = (index + tmp + ((index & 4) ? 28 : 36));   // on average this should give 32 element skips, with changing strides
        count++;                                           //divide overall time by this 
    }
    clock_gettime(CLOCK_REALTIME, &endAccess); //end clock              
    L1Access = ((endAccess.tv_sec - startAccess.tv_sec) * SECONDS_PER_NS) + (endAccess.tv_nsec - startAccess.tv_nsec);
    L1Access /= count;

    printf("L1 Cache Access %lf\n", L1Access);

    //invalidate L1 by accessing all elements of array which is larger than cache
    for(count=0; count < L1_CACHE_SIZE; count++){
        int read = arrayInvalidateL1[count]; 
        read++;
        readValue+=read;               
    }

    index = 0;
    count = 0;
    clock_gettime(CLOCK_REALTIME, &startAccess); //start clock
    while (index < L1_CACHE_SIZE) {
        int tmp = arrayAccess[index];               //Access Value from L2
        index = (index + tmp + ((index & 4) ? 28 : 36));   // on average this should give 32 element skips, with changing strides
        count++;                                           //divide overall time by this 
    }
    clock_gettime(CLOCK_REALTIME, &endAccess); //end clock
    L2Access = ((endAccess.tv_sec - startAccess.tv_sec) * SECONDS_PER_NS) + (endAccess.tv_nsec - startAccess.tv_nsec);
    L2Access /= count;

    printf("L2 Cache Acces %lf\n", L2Access);

    //invalidate L2 by accessing all elements of array which is larger than cache
    for(count=0; count < L2_CACHE_SIZE; count++){
        int read = arrayInvalidateL2[count];  
        read++;
        readValue+=read;                        
    }

    index = 0;
    count=0;
    clock_gettime(CLOCK_REALTIME, &startAccess); //sreadValue+=read;tart clock
    while (index < L1_CACHE_SIZE) {
        int tmp = arrayAccess[index];               //Access Value from L2
        index = (index + tmp + ((index & 4) ? 28 : 36));   // on average this should give 32 element skips, with changing strides
        count++;                                           //divide overall time by this 
    }
    clock_gettime(CLOCK_REALTIME, &endAccess); //end clock
    L3Access = ((endAccess.tv_sec - startAccess.tv_sec) * SECONDS_PER_NS) + (endAccess.tv_nsec - startAccess.tv_nsec);
    L3Access /= count;

    printf("L3 Cache Access %lf\n", L3Access);

    printf("Read Value: %d", readValue);

}

데이터를 원하는 배열의 값에 액세스하는 것으로 시작합니다.이것은 첫 번째 액세스이기 때문에 분명히 메인 메모리에서 나와야 합니다.배열은 작은(페이지 크기 미만)이므로 L1, L2, L3에 복사해야 합니다.나는 L1이어야 할 동일한 배열의 값에 액세스합니다.그런 다음 L1 캐시와 동일한 크기의 배열에서 모든 값에 액세스하여 액세스할 데이터를 무효화합니다(이제 L2/3에 있어야 함).그리고 L2와 L3에 대해서도 이 과정을 반복합니다.접속 시간이 분명히 어긋났는데, 그건 제가 뭔가 잘못하고 있다는 뜻입니다.

클록 소요 시간에 문제가 있을 수 있습니다(시작 및 중지 시간은 ns 단위로 약간의 시간이 소요되며 캐시/언치되면 변경됩니다).

누가 제가 뭘 잘못하고 있는지 조언을 좀 해주실 수 있나요?

업데이트 1: 그래서 저는 많은 액세스를 함으로써 타이머 비용을 상각했고, 캐시의 크기를 고정했으며, 고정된 전진을 피하기 위해 더 복잡한 색인 체계를 만들라는 조언도 받았습니다.불행하게도 시대는 아직 안 가고 있습니다.그들은 모두 L1을 위해 오는 것 같습니다.액세스 대신 무효화하는 것이 문제가 될 수도 있다고 생각합니다.랜덤 대 LRU 방식이 무효화되는 데이터에 영향을 미칠까요?

업데이트 2: 멤셋 수정(L3의 데이터를 무효화하기 위해 L3 멤셋을 추가하여 첫 번째 액세스가 메인 메모리에서 시작되도록 함) 및 인덱싱 스킴, 여전히 운이 없습니다.

업데이트 3: 이 방법을 사용할 수는 없었지만 몇 가지 좋은 제안된 답변이 있었고 몇 가지 해결책을 게시했습니다.

또한 캐쉬그라인드를 실행하여 히트/미스를 확인했습니다.

 ==6710== I   refs:      1,735,104
==6710== I1  misses:        1,092
==6710== LLi misses:        1,084
==6710== I1  miss rate:      0.06%
==6710== LLi miss rate:      0.06%
==6710== 
==6710== D   refs:      1,250,696  (721,162 rd   + 529,534 wr)
==6710== D1  misses:      116,492  (  7,627 rd   + 108,865 wr)
==6710== LLd misses:      115,102  (  6,414 rd   + 108,688 wr)
==6710== D1  miss rate:       9.3% (    1.0%     +    20.5%  )
==6710== LLd miss rate:       9.2% (    0.8%     +    20.5%  )
==6710== 
==6710== LL refs:         117,584  (  8,719 rd   + 108,865 wr)
==6710== LL misses:       116,186  (  7,498 rd   + 108,688 wr)
==6710== LL miss rate:        3.8% (    0.3%     +    20.5%  )


        Ir I1mr ILmr      Dr  D1mr  DLmr     Dw D1mw DLmw 

      .    .    .       .     .     .      .    .    .  #include <time.h>
      .    .    .       .     .     .      .    .    .  #include <stdio.h>
      .    .    .       .     .     .      .    .    .  #include <string.h>
      .    .    .       .     .     .      .    .    .  
      6    0    0       0     0     0      2    0    0  int main(){
      5    1    1       0     0     0      2    0    0      srand(time(NULL));  // Seed ONCE
      1    0    0       0     0     0      1    0    0      const int L1_CACHE_SIZE =  32768/sizeof(int);
      1    0    0       0     0     0      1    0    0      const int L2_CACHE_SIZE =  262144/sizeof(int);
      1    0    0       0     0     0      1    0    0      const int L3_CACHE_SIZE =  6587392/sizeof(int);
      1    0    0       0     0     0      1    0    0      const int NUM_ACCESSES = 1000000;
      1    0    0       0     0     0      1    0    0      const int SECONDS_PER_NS = 1000000000;
     21    2    2       3     0     0      3    0    0      int arrayAccess[L1_CACHE_SIZE];
     21    1    1       3     0     0      3    0    0      int arrayInvalidateL1[L1_CACHE_SIZE];
     21    2    2       3     0     0      3    0    0      int arrayInvalidateL2[L2_CACHE_SIZE];
     21    1    1       3     0     0      3    0    0      int arrayInvalidateL3[L3_CACHE_SIZE];
      1    0    0       0     0     0      1    0    0      int count=0;
      1    1    1       0     0     0      1    0    0      int index=0;
      1    0    0       0     0     0      1    0    0      int i=0;
      .    .    .       .     .     .      .    .    .      struct timespec startAccess, endAccess;
      .    .    .       .     .     .      .    .    .      double mainMemAccess, L1Access, L2Access, L3Access;
      1    0    0       0     0     0      1    0    0      int readValue=0;
      .    .    .       .     .     .      .    .    .  
      7    0    0       2     0     0      1    1    1      memset(arrayAccess, 0, L1_CACHE_SIZE*sizeof(int));
      7    1    1       2     2     0      1    0    0      memset(arrayInvalidateL1, 0, L1_CACHE_SIZE*sizeof(int));
      7    0    0       2     2     0      1    0    0      memset(arrayInvalidateL2, 0, L2_CACHE_SIZE*sizeof(int));
      7    1    1       2     2     0      1    0    0      memset(arrayInvalidateL3, 0, L3_CACHE_SIZE*sizeof(int));
      .    .    .       .     .     .      .    .    .  
      1    0    0       0     0     0      1    1    1      index = 0;
      4    0    0       0     0     0      1    0    0      clock_gettime(CLOCK_REALTIME, &startAccess); //start clock
    772    1    1     514     0     0      0    0    0      while (index < L1_CACHE_SIZE) {
  1,280    1    1     768   257   257    256    0    0          int tmp = arrayAccess[index];               //Access Value from L2
  2,688    0    0     768     0     0    256    0    0          index = (index + tmp + ((index & 4) ? 28 : 36));   // on average this should give 32 element skips, with changing strides
    256    0    0     256     0     0      0    0    0          count++;                                           //divide overall time by this 
      .    .    .       .     .     .      .    .    .      }
      4    0    0       0     0     0      1    0    0      clock_gettime(CLOCK_REALTIME, &endAccess); //end clock
     14    1    1       5     1     1      1    1    1      mainMemAccess = ((endAccess.tv_sec - startAccess.tv_sec) * SECONDS_PER_NS) + (endAccess.tv_nsec - startAccess.tv_nsec);
      6    0    0       2     0     0      1    0    0      mainMemAccess /= count;
      .    .    .       .     .     .      .    .    .  
      6    1    1       2     0     0      2    0    0      printf("Main Memory Access %lf\n", mainMemAccess);
      .    .    .       .     .     .      .    .    .  
      1    0    0       0     0     0      1    0    0      index = 0;
      1    0    0       0     0     0      1    0    0      count=0;
      4    1    1       0     0     0      1    0    0      clock_gettime(CLOCK_REALTIME, &startAccess); //start clock
    772    1    1     514     0     0      0    0    0      while (index < L1_CACHE_SIZE) {
  1,280    0    0     768   240     0    256    0    0          int tmp = arrayAccess[index];               //Access Value from L2
  2,688    0    0     768     0     0    256    0    0          index = (index + tmp + ((index & 4) ? 28 : 36));   // on average this should give 32 element skips, with changing strides
    256    0    0     256     0     0      0    0    0          count++;                                           //divide overall time by this 
      .    .    .       .     .     .      .    .    .      }
      4    0    0       0     0     0      1    0    0      clock_gettime(CLOCK_REALTIME, &endAccess); //end clock              
     14    1    1       5     0     0      1    1    0      L1Access = ((endAccess.tv_sec - startAccess.tv_sec) * SECONDS_PER_NS) + (endAccess.tv_nsec - startAccess.tv_nsec);
      6    1    1       2     0     0      1    0    0      L1Access /= count;
      .    .    .       .     .     .      .    .    .  
      6    0    0       2     0     0      2    0    0      printf("L1 Cache Access %lf\n", L1Access);
      .    .    .       .     .     .      .    .    .  
      .    .    .       .     .     .      .    .    .      //invalidate L1 by accessing all elements of array which is larger than cache
 32,773    1    1  24,578     0     0      1    0    0      for(count=0; count < L1_CACHE_SIZE; count++){
 40,960    0    0  24,576   513   513  8,192    0    0          int read = arrayInvalidateL1[count]; 
  8,192    0    0   8,192     0     0      0    0    0          read++;
 16,384    0    0  16,384     0     0      0    0    0          readValue+=read;               
      .    .    .       .     .     .      .    .    .      }
      .    .    .       .     .     .      .    .    .  
      1    0    0       0     0     0      1    0    0      index = 0;
      1    1    1       0     0     0      1    0    0      count = 0;
      4    0    0       0     0     0      1    1    0      clock_gettime(CLOCK_REALTIME, &startAccess); //start clock
    772    1    1     514     0     0      0    0    0      while (index < L1_CACHE_SIZE) {
  1,280    0    0     768   256     0    256    0    0          int tmp = arrayAccess[index];               //Access Value from L2
  2,688    0    0     768     0     0    256    0    0          index = (index + tmp + ((index & 4) ? 28 : 36));   // on average this should give 32 element skips, with changing strides
    256    0    0     256     0     0      0    0    0          count++;                                           //divide overall time by this 
      .    .    .       .     .     .      .    .    .      }
      4    1    1       0     0     0      1    0    0      clock_gettime(CLOCK_REALTIME, &endAccess); //end clock
     14    0    0       5     1     0      1    1    0      L2Access = ((endAccess.tv_sec - startAccess.tv_sec) * SECONDS_PER_NS) + (endAccess.tv_nsec - startAccess.tv_nsec);
      6    1    1       2     0     0      1    0    0      L2Access /= count;
      .    .    .       .     .     .      .    .    .  
      6    0    0       2     0     0      2    0    0      printf("L2 Cache Acces %lf\n", L2Access);
      .    .    .       .     .     .      .    .    .  
      .    .    .       .     .     .      .    .    .      //invalidate L2 by accessing all elements of array which is larger than cache
262,149    2    2 196,610     0     0      1    0    0      for(count=0; count < L2_CACHE_SIZE; count++){
327,680    0    0 196,608 4,097 4,095 65,536    0    0          int read = arrayInvalidateL2[count];  
 65,536    0    0  65,536     0     0      0    0    0          read++;
131,072    0    0 131,072     0     0      0    0    0          readValue+=read;                        
      .    .    .       .     .     .      .    .    .      }
      .    .    .       .     .     .      .    .    .  
      1    0    0       0     0     0      1    0    0      index = 0;
      1    0    0       0     0     0      1    0    0      count=0;
      4    0    0       0     0     0      1    1    0      clock_gettime(CLOCK_REALTIME, &startAccess); //sreadValue+=read;tart clock
    772    1    1     514     0     0      0    0    0      while (index < L1_CACHE_SIZE) {
  1,280    0    0     768   256     0    256    0    0          int tmp = arrayAccess[index];               //Access Value from L2
  2,688    0    0     768     0     0    256    0    0          index = (index + tmp + ((index & 4) ? 28 : 36));   // on average this should give 32 element skips, with changing strides
    256    0    0     256     0     0      0    0    0          count++;                                           //divide overall time by this 
      .    .    .       .     .     .      .    .    .      }
      4    0    0       0     0     0      1    0    0      clock_gettime(CLOCK_REALTIME, &endAccess); //end clock
     14    1    1       5     1     0      1    1    0      L3Access = ((endAccess.tv_sec - startAccess.tv_sec) * SECONDS_PER_NS) + (endAccess.tv_nsec - startAccess.tv_nsec);
      6    0    0       2     0     0      1    0    0      L3Access /= count;
      .    .    .       .     .     .      .    .    .  
      6    1    1       2     0     0      2    0    0      printf("L3 Cache Access %lf\n", L3Access);
      .    .    .       .     .     .      .    .    .  
      6    0    0       1     0     0      1    0    0      printf("Read Value: %d", readValue);
      .    .    .       .     .     .      .    .    .  
      3    0    0       3     0     0      0    0    0  }

나는 차라리 하드웨어 시계를 척도로 사용하려고 합니다.rdtsc명령어는 CPU의 전원이 켜진 이후의 현재 사이클 수를 알려줍니다.또한 사용하는 것이 더 좋습니다.asm측정 주행과 건조 주행 모두에서 항상 동일한 지침이 사용되는지 확인합니다.그것과 오래전에 내가 만든 몇 가지 현명한 통계를 이용해서 말입니다.

#include <stdlib.h>
#include <stdio.h>
#include <stdint.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/mman.h>


int i386_cpuid_caches (size_t * data_caches) {
    int i;
    int num_data_caches = 0;
    for (i = 0; i < 32; i++) {

        // Variables to hold the contents of the 4 i386 legacy registers
        uint32_t eax, ebx, ecx, edx; 

        eax = 4; // get cache info
        ecx = i; // cache id

        asm (
            "cpuid" // call i386 cpuid instruction
            : "+a" (eax) // contains the cpuid command code, 4 for cache query
            , "=b" (ebx)
            , "+c" (ecx) // contains the cache id
            , "=d" (edx)
        ); // generates output in 4 registers eax, ebx, ecx and edx 

        // taken from http://download.intel.com/products/processor/manual/325462.pdf Vol. 2A 3-149
        int cache_type = eax & 0x1F; 

        if (cache_type == 0) // end of valid cache identifiers
            break;

        char * cache_type_string;
        switch (cache_type) {
            case 1: cache_type_string = "Data Cache"; break;
            case 2: cache_type_string = "Instruction Cache"; break;
            case 3: cache_type_string = "Unified Cache"; break;
            default: cache_type_string = "Unknown Type Cache"; break;
        }

        int cache_level = (eax >>= 5) & 0x7;

        int cache_is_self_initializing = (eax >>= 3) & 0x1; // does not need SW initialization
        int cache_is_fully_associative = (eax >>= 1) & 0x1;


        // taken from http://download.intel.com/products/processor/manual/325462.pdf 3-166 Vol. 2A
        // ebx contains 3 integers of 10, 10 and 12 bits respectively
        unsigned int cache_sets = ecx + 1;
        unsigned int cache_coherency_line_size = (ebx & 0xFFF) + 1;
        unsigned int cache_physical_line_partitions = ((ebx >>= 12) & 0x3FF) + 1;
        unsigned int cache_ways_of_associativity = ((ebx >>= 10) & 0x3FF) + 1;

        // Total cache size is the product
        size_t cache_total_size = cache_ways_of_associativity * cache_physical_line_partitions * cache_coherency_line_size * cache_sets;

        if (cache_type == 1 || cache_type == 3) {
            data_caches[num_data_caches++] = cache_total_size;
        }

        printf(
            "Cache ID %d:\n"
            "- Level: %d\n"
            "- Type: %s\n"
            "- Sets: %d\n"
            "- System Coherency Line Size: %d bytes\n"
            "- Physical Line partitions: %d\n"
            "- Ways of associativity: %d\n"
            "- Total Size: %zu bytes (%zu kb)\n"
            "- Is fully associative: %s\n"
            "- Is Self Initializing: %s\n"
            "\n"
            , i
            , cache_level
            , cache_type_string
            , cache_sets
            , cache_coherency_line_size
            , cache_physical_line_partitions
            , cache_ways_of_associativity
            , cache_total_size, cache_total_size >> 10
            , cache_is_fully_associative ? "true" : "false"
            , cache_is_self_initializing ? "true" : "false"
        );
    }

    return num_data_caches;
}

int test_cache(size_t attempts, size_t lower_cache_size, int * latencies, size_t max_latency) {
    int fd = open("/dev/urandom", O_RDONLY);
    if (fd < 0) {
        perror("open");
        abort();
    }
    char * random_data = mmap(
          NULL
        , lower_cache_size
        , PROT_READ | PROT_WRITE
        , MAP_PRIVATE | MAP_ANON // | MAP_POPULATE
        , -1
        , 0
        ); // get some random data
    if (random_data == MAP_FAILED) {
        perror("mmap");
        abort();
    }

    size_t i;
    for (i = 0; i < lower_cache_size; i += sysconf(_SC_PAGESIZE)) {
        random_data[i] = 1;
    }


    int64_t random_offset = 0;
    while (attempts--) {
        // use processor clock timer for exact measurement
        random_offset += rand();
        random_offset %= lower_cache_size;
        int32_t cycles_used, edx, temp1, temp2;
        asm (
            "mfence\n\t"        // memory fence
            "rdtsc\n\t"         // get cpu cycle count
            "mov %%edx, %2\n\t"
            "mov %%eax, %3\n\t"
            "mfence\n\t"        // memory fence
            "mov %4, %%al\n\t"  // load data
            "mfence\n\t"
            "rdtsc\n\t"
            "sub %2, %%edx\n\t" // substract cycle count
            "sbb %3, %%eax"     // substract cycle count
            : "=a" (cycles_used)
            , "=d" (edx)
            , "=r" (temp1)
            , "=r" (temp2)
            : "m" (random_data[random_offset])
            );
        // printf("%d\n", cycles_used);
        if (cycles_used < max_latency)
            latencies[cycles_used]++;
        else 
            latencies[max_latency - 1]++;
    }

    munmap(random_data, lower_cache_size);

    return 0;
} 

int main() {
    size_t cache_sizes[32];
    int num_data_caches = i386_cpuid_caches(cache_sizes);

    int latencies[0x400];
    memset(latencies, 0, sizeof(latencies));

    int empty_cycles = 0;

    int i;
    int attempts = 1000000;
    for (i = 0; i < attempts; i++) { // measure how much overhead we have for counting cyscles
        int32_t cycles_used, edx, temp1, temp2;
        asm (
            "mfence\n\t"        // memory fence
            "rdtsc\n\t"         // get cpu cycle count
            "mov %%edx, %2\n\t"
            "mov %%eax, %3\n\t"
            "mfence\n\t"        // memory fence
            "mfence\n\t"
            "rdtsc\n\t"
            "sub %2, %%edx\n\t" // substract cycle count
            "sbb %3, %%eax"     // substract cycle count
            : "=a" (cycles_used)
            , "=d" (edx)
            , "=r" (temp1)
            , "=r" (temp2)
            :
            );
        if (cycles_used < sizeof(latencies) / sizeof(*latencies))
            latencies[cycles_used]++;
        else 
            latencies[sizeof(latencies) / sizeof(*latencies) - 1]++;

    }

    {
        int j;
        size_t sum = 0;
        for (j = 0; j < sizeof(latencies) / sizeof(*latencies); j++) {
            sum += latencies[j];
        }
        size_t sum2 = 0;
        for (j = 0; j < sizeof(latencies) / sizeof(*latencies); j++) {
            sum2 += latencies[j];
            if (sum2 >= sum * .75) {
                empty_cycles = j;
                fprintf(stderr, "Empty counting takes %d cycles\n", empty_cycles);
                break;
            }
        }
    }

    for (i = 0; i < num_data_caches; i++) {
        test_cache(attempts, cache_sizes[i] * 4, latencies, sizeof(latencies) / sizeof(*latencies));

        int j;
        size_t sum = 0;
        for (j = 0; j < sizeof(latencies) / sizeof(*latencies); j++) {
            sum += latencies[j];
        }
        size_t sum2 = 0;
        for (j = 0; j < sizeof(latencies) / sizeof(*latencies); j++) {
            sum2 += latencies[j];
            if (sum2 >= sum * .75) {
                fprintf(stderr, "Cache ID %i has latency %d cycles\n", i, j - empty_cycles);
                break;
            }
        }

    }

    return 0;

}

내 Core2Duo에서의 출력:

Cache ID 0:
- Level: 1
- Type: Data Cache
- Total Size: 32768 bytes (32 kb)

Cache ID 1:
- Level: 1
- Type: Instruction Cache
- Total Size: 32768 bytes (32 kb)

Cache ID 2:
- Level: 2
- Type: Unified Cache
- Total Size: 262144 bytes (256 kb)

Cache ID 3:
- Level: 3
- Type: Unified Cache
- Total Size: 3145728 bytes (3072 kb)

Empty counting takes 90 cycles
Cache ID 0 has latency 6 cycles
Cache ID 2 has latency 21 cycles
Cache ID 3 has latency 168 cycles

캐시 지연 시간에 대해 널리 사용되는 고전적인 테스트는 링크된 목록을 반복하는 것입니다.현대식 슈퍼스칼라/슈퍼파이프라인 CPU와 ARM Cortex-A9+ 및 Intel Core 2/ix와 같은 Out-of-order 코어에서도 작동합니다.- 이스은 lmbench서다서에서 사용됩니다.lat_mem_rd(man page) 및 CPU-Z 지연 시간 측정 도구: http://cpuid.com/medias/files/softwares/misc/latency.zip (Windows 바이너리 실행)

lmbench: https://github.com/foss-for-synopsys-dwc-arc-processors/lmbench/blob/master/src/lat_mem_rd.c 에서 lat_mem_rd 테스트 소스가 있습니다.

그리고 주요 테스트는

#define ONE p = (char **)*p;
#define FIVE    ONE ONE ONE ONE ONE
#define TEN FIVE FIVE
#define FIFTY   TEN TEN TEN TEN TEN
#define HUNDRED FIFTY FIFTY

void
benchmark_loads(iter_t iterations, void *cookie)
{
    struct mem_state* state = (struct mem_state*)cookie;
    register char **p = (char**)state->p[0];
    register size_t i;
    register size_t count = state->len / (state->line * 100) + 1;

    while (iterations-- > 0) {
        for (i = 0; i < count; ++i) {
            HUNDRED;
        }
    }

    use_pointer((void *)p);
    state->p[0] = (char*)p;
}

따라서 매크로를 해독한 후 다음과 같은 선형 연산을 많이 수행합니다.

 p = (char**) *p;  // (in intel syntax) == mov eax, [eax]
 p = (char**) *p;
 p = (char**) *p;
 ....   // 100 times total
 p = (char**) *p;

모든 에 로 로 .stride요소 순방향

http://www.bitmover.com/lmbench/lat_mem_rd.8.html 에 남자가 말한 것처럼.

벤치마크는 두 개의 중첩 루프로 실행됩니다.바깥쪽 루프는 스트라이드 사이즈입니다.내부 루프는 배열 크기입니다.각 배열 크기에 대해 벤치마크는 한 보폭 앞을 가리키는 포인터의 고리를 만듭니다.배열을 횡단하는 작업은 다음과 같습니다.

 p = (char **)*p;

for 루프(for 루프의 오버헤드는 중요하지 않으며, 루프는 롤링되지 않은 루프 1000 로드 길이임).100만 번의 부하를 걸면 루프가 멈춥니다.어레이의 크기는 512바이트에서 8메가바이트까지 다양합니다.작은 크기의 경우 캐시가 영향을 미치며 부하가 훨씬 빨라질 것입니다.이는 데이터를 플롯할 때 훨씬 더 명확해집니다.

POWER에 대한 예시와 함께 보다 자세한 설명은 IBM의 Wiki에서 확인할 수 있습니다.Jenifer Hopper 2013의 엉킴 없는 메모리 액세스 측정 - lat_mem_rd -

lat_mem_rd 테스트(http://www.bitmover.com/lmbench/lat_mem_rd.8.html) 는 배열 크기(MB)와 스트라이드 크기)의 두 가지 인수를 사용합니다.벤치마크는 배열을 통과하기 위해 두 개의 루프를 사용하며, 스트라이드를 하나의 스트라이드 앞으로 가리키는 포인터 링을 생성하여 증분으로 사용합니다.이 테스트는 메모리 크기 범위에 대해 메모리 읽기 지연 시간(나노초)을 측정합니다.출력은 두 개의 열로 구성됩니다. 첫 번째 열은 MB 단위의 배열 크기(플로팅 포인트 값)이고 두 번째 열은 배열의 모든 점에 대한 로드 레이턴시입니다.결과를 그래프로 나타내면 각 캐시 레벨의 더 빠른 지연 시간과 주요 메모리 지연 시간을 포함하여 전체 메모리 계층의 상대적인 지연 시간을 명확하게 볼 수 있습니다.

추신: lat_mem_rd: ftp://download.intel.com/design/intarch/PAPERS/321074.pdf - sorry right url은 2008년 12월 Joshua Ruggiero의 http://www.intel.com/content/dam/www/public/us/en/documents/white-papers/ia-cache-latency-bandwidth-paper.pdf "Measurement Cache and Memory Latency and CPU to Memory Bandwidth - For Use with Intel Architecture"입니다.

네, 코드에 몇 가지 문제가 있습니다.

  1. 말씀하신 대로 측정에 시간이 오래 걸립니다.실제로 단일 액세스 자체보다 훨씬 오래 걸릴 가능성이 높기 때문에 유용한 측정은 하지 않고 있습니다.이를 완화하기 위해 여러 요소에 액세스하고 상각합니다(전체 시간을 액세스 횟수로 나눕니다).지연 시간을 측정하려면 이러한 액세스를 직렬화하고, 그렇지 않으면 병렬로 수행할 수 있으며 관련 없는 액세스의 처리량만 측정할 수 있습니다.이를 위해 액세스 간에 잘못된 종속성을 추가할 수 있습니다.

    예를 들어 배열을 0으로 초기화하고 다음을 수행합니다.

    clock_gettime(CLOCK_REALTIME, &startAccess); //start clock
    for (int i = 0; i < NUM_ACCESSES; ++i) {
        int tmp = arrayAccess[index];                             //Access Value from Main Memory
        index = (index + i + tmp) & 1023;   
    }
    clock_gettime(CLOCK_REALTIME, &endAccess); //end clock
    

    그리고 을 ...로 해야 합니다. 그리고 물론 시간을 나누어야 한다는 것을 기억하세요.NUM_ACCESSES.
    지수를 의도적으로 복잡하게 만들어서 프리페처를 유발할 수 있는 고정된 보폭을 피하게 했습니다. (약간의 과잉반응은 눈치채지 못하겠지만, 시연을 위해...)당신은 아마 간단한 것으로 만족할 수 있을 것입니다.index += 32의 캐시 라인의 진보를 하고, 인접 수 128k (2π )는 를 의 의 의 의 의 .% 1000와 함께& 1023&빠르지만, 합니다. 만더한로면의다이의하려면 2이다es는,t만2더로한fooee,rACCESS_SIZE로 하면 1024로 하면 됩니다.

  2. 다른 것을 로딩하여 L1을 무효화하는 것도 좋지만, 사이즈가 우습게 보입니다.만,256000L1치고는 꽤 큰 것 같습니다.L2는 많은 일반적인 최신 x86 CPU에서 256k입니다.또한 256k는 그렇지 않습니다. 256000256*1024=262144. 두번째 사이즈도 마찬가지입니다: 1M은 아닙니다.1024000 예를 들어…1024*1024=1048576크기라고 합니다. (이 더 , 너무 도 있습니다 그것이 실제로 당신의 L2 크기라고 가정합니다. (L3일 가능성이 더 높지만, 그것에 비해 너무 작을 수도 있습니다.)

  3. 은 된 이 입니다 입니다 입니다.int 각 는 단일이 높습니다 즉, 는 에 4 에 당신은 사실 무효화 하고 있습니다.L1_CACHE_SIZE*sizeof(int)바이트 값(L2 무효화 루프도 마찬가지)

업데이트:

  1. memset하며, 기 를며는로다위다으로 ),는,s며yd트를 )로 나눕니다.sizeof(int)

  2. 무효화 판독값은 사용되지 않으며 최적화된 것일 수도 있습니다.이러한 가능성을 방지하기 위해 판독값을 어떤 값으로 축적하고 마지막에 인쇄해 보십시오.

  3. 처음의 멤셋도 데이터에 액세스하므로 첫 번째 루프는 L3에서 데이터에 액세스합니다(다른 두 멤셋은 크기 오류로 인해 부분적으로만 L1+L2에서 제거하는 데 여전히 효과적이었으므로).

  4. 보폭이 너무 작아서 동일한 캐시 라인(L1 적중)에 두 번 액세스할 수 있습니다.32개의 요소(x4바이트)를 추가하여 충분히 퍼졌는지 확인합니다. 이는 2개의 캐시라인이므로 인접한 캐시라인 프리페치(prefetch) 이점도 얻을 수 없습니다.

  5. NUM_ACCESS는 ACCESS_SIZE보다 크기 때문에 기본적으로 동일한 요소를 반복하고 있으며 이 요소에 대해 L1 히트를 얻을 수 있습니다(따라서 평균 시간은 L1 액세스 지연 시간 대신 이동됩니다).대신 L1 크기를 사용하여 L1 전체(스킵 제외)에 한 번만 액세스해 보십시오.예를 들어, 이런 식으로-

    index = 0;
    while (index < L1_CACHE_SIZE) {
        int tmp = arrayAccess[index];               //Access Value from L2
        index = (index + tmp + ((index & 4) ? 28 : 36));   // on average this should give 32 element skips, with changing strides
        count++;                                           //divide overall time by this 
    }
    

늘이는 것을 잊지 마세요.arrayAccessL1 사이즈까지.

위와 같은 변화(상당히)를 통해 다음과 같은 결과를 얻을 수 있습니다.

L1 Cache Access 7.812500
L2 Cache Acces 15.625000
L3 Cache Access 23.437500

아직은 좀 길어 보이지만 산술 연산에 대한 추가 의존성이 포함되어 있기 때문일 수도 있습니다.

답은 아니지만 어쨌든 이미 다른 답과 댓글에 언급된 것이 있습니다.

며칠 전에 제가 이 질문에 답했습니다.

에 입니다 입니다 측정에 관한 것입니다.L1/L2/.../L?/MEMORY은 문제의 더.더은을해다을은다을해r의을더ase은rtt .

[참고]

  1. 시간 측정을 위해 RDTSC 명령을 사용할 것을 강력히 권고합니다.

    특히 L1은 너무 느리기 때문에.프로세스 친화도를 단일 CPU에 설정하는 것을 잊지 마십시오. 모든 코어에는 고유 카운터가 있고 같은 입력에서도 카운트가 많이 다릅니다. Clock!!!

    가변 클럭 컴퓨터의 경우 CPU 클럭을 Maximum으로 조정하고 다음을 고려해야 합니다.RDTSC32비트 부분만 사용할 경우 오버플로가 발생합니다(현대 CPU 오버플로 32비트 카운터는 1초 만에 발생).시간 계산을 위해 CPU 클럭을 사용합니다(측정 또는 레지스트리 값 사용).

    t0 <- RDTSC
    Sleep(250);
    t1 <- RDTSC
    CPU f=(t1-t0)<<2 [Hz]
    
  2. 프로세스 선호도를 단일 CPU로 설정

    모든 CPU 코어는 대개 자체적인 L1,L2 캐시를 가지고 있으므로 멀티태스킹 OS에서는 이것을 하지 않으면 혼란스러운 것들을 측정할 수 있습니다.

  3. 그래픽 출력(출력)을 합니다.

    그러면 위의 링크에서 실제로 무슨 일이 일어나는지 볼 수 있을 것입니다. 저는 꽤 많은 줄거리를 올렸습니다.

  4. OS에서 사용 가능한 가장 높은 프로세스 우선 순위 사용

관심 있는 분들을 위해, 저는 첫 번째 코드 세트를 작동시킬 수 없었기 때문에 좋은 결과를 만들어 내는 몇 가지 대안적인 접근법을 시도했습니다.

연속 메모리 공간에서 스트라이드 바이트가 할당된 노드가 있는 처음 사용된 링크된 목록.노드들의 역참조는 프리페처의 효율성을 완화시켜 주고, 캐시 라인이 여러 개인 경우 캐시 히트를 방지하기 위해 상당히 큰 발전을 이루었습니다.할당된 목록의 크기가 커지면 캐시 또는 메모리 구조로 이동하여 대기 시간에 명확한 구분을 보여 줍니다.

#include <time.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>

//MACROS
#define ONE iterate = (char**) *iterate;
#define FIVE ONE ONE ONE
#define TWOFIVE FIVE FIVE FIVE FIVE FIVE
#define HUNDO TWOFIVE TWOFIVE TWOFIVE TWOFIVE

//prototype
void allocateRandomArray(long double);
void accessArray(char *, long double, char**);

int main(){
    //call the function for allocating arrays of increasing size in MB
    allocateRandomArray(.00049);
    allocateRandomArray(.00098);
    allocateRandomArray(.00195);
    allocateRandomArray(.00293);
    allocateRandomArray(.00391);
    allocateRandomArray(.00586);
    allocateRandomArray(.00781);
    allocateRandomArray(.01172);
    allocateRandomArray(.01562);
    allocateRandomArray(.02344);
    allocateRandomArray(.03125);
    allocateRandomArray(.04688);
    allocateRandomArray(.0625);
    allocateRandomArray(.09375);
    allocateRandomArray(.125);
    allocateRandomArray(.1875);
    allocateRandomArray(.25);
    allocateRandomArray(.375);
    allocateRandomArray(.5);
    allocateRandomArray(.75);
    allocateRandomArray(1);
    allocateRandomArray(1.5);
    allocateRandomArray(2);
    allocateRandomArray(3);
    allocateRandomArray(4);
    allocateRandomArray(6);
    allocateRandomArray(8);
    allocateRandomArray(12);
    allocateRandomArray(16);
    allocateRandomArray(24);
    allocateRandomArray(32);
    allocateRandomArray(48);
    allocateRandomArray(64);
    allocateRandomArray(96);
    allocateRandomArray(128);
    allocateRandomArray(192);
}

void allocateRandomArray(long double size){
    int accessSize=(1024*1024*size); //array size in bytes
    char * randomArray = malloc(accessSize*sizeof(char));    //allocate array of size allocate size
    int counter;
    int strideSize=4096;        //step size

    char ** head = (char **) randomArray;   //start of linked list in contiguous memory
    char ** iterate = head;         //iterator for linked list
    for(counter=0; counter < accessSize; counter+=strideSize){      
        (*iterate) = &randomArray[counter+strideSize];      //iterate through linked list, having each one point stride bytes forward
        iterate+=(strideSize/sizeof(iterate));          //increment iterator stride bytes forward
    }
    *iterate = (char *) head;       //set tailf to point to head

    accessArray(randomArray, size, head);
    free(randomArray);
}

void accessArray(char *cacheArray, long double size, char** head){
    const long double NUM_ACCESSES = 1000000000/100;    //number of accesses to linked list
    const int SECONDS_PER_NS = 1000000000;      //const for timer
    FILE *fp =  fopen("accessData.txt", "a");   //open file for writing data
    int newIndex=0;
    int counter=0;
    int read=0;
    struct timespec startAccess, endAccess;     //struct for timer
    long double accessTime = 0;
    char ** iterate = head;     //create iterator

    clock_gettime(CLOCK_REALTIME, &startAccess); //start clock
    for(counter=0; counter < NUM_ACCESSES; counter++){
        HUNDO       //macro subsitute 100 accesses to mitigate loop overhead
    }
    clock_gettime(CLOCK_REALTIME, &endAccess); //end clock
    //calculate the time elapsed in ns per access
    accessTime = (((endAccess.tv_sec - startAccess.tv_sec) * SECONDS_PER_NS) + (endAccess.tv_nsec - startAccess.tv_nsec)) / (100*NUM_ACCESSES);
    fprintf(fp, "%Lf\t%Lf\n", accessTime, size);  //print results to file
    fclose(fp);  //close file
}

이를 통해 가장 일관된 결과가 도출되었으며, 다양한 어레이 크기를 사용하고 각각의 지연 시간을 플롯하여 존재하는 캐시 크기를 매우 명확하게 구분할 수 있었습니다.

다음 방법은 이전에 할당된 증가하는 크기 배열과 같습니다.그러나 메모리 액세스를 위해 링크된 목록을 사용하는 대신 각 인덱스를 각각의 숫자로 채우고 배열을 무작위로 섞습니다.그런 다음 이 인덱스를 사용하여 접근을 위해 배열 내에서 무작위로 뛰어다니며 프리페처의 영향을 완화했습니다.그러나 인접한 여러 캐시 라인이 당겨져 부딪힐 때 액세스 시간에 가끔 큰 편차가 있었습니다.

#include <time.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <math.h>

//prototype
void allocateRandomArray(long double);
void accessArray(int *, long int);

int main(){
    srand(time(NULL));  // Seed random function
    int i=0;
    for(i=2; i < 32; i++){
        allocateRandomArray(pow(2, i));         //call latency function on arrays of increasing size
    }


}

void allocateRandomArray(long double size){
    int accessSize = (size) / sizeof(int);
    int * randomArray = malloc(accessSize*sizeof(int));
    int counter;

    for(counter=0; counter < accessSize; counter ++){
        randomArray[counter] = counter; 
    }
    for(counter=0; counter < accessSize; counter ++){
        int i,j;
        int swap;
        i = rand() % accessSize;
        j = rand() % accessSize;
        swap = randomArray[i];
        randomArray[i] = randomArray[j];
        randomArray[j] = swap;
    } 

    accessArray(randomArray, accessSize);
    free(randomArray);
}

void accessArray(int *cacheArray, long int size){
    const long double NUM_ACCESSES = 1000000000;
    const int SECONDS_PER_NS = 1000000000;
    int newIndex=0;
    int counter=0;
    int read=0;
    struct timespec startAccess, endAccess;
    long double accessTime = 0;

    clock_gettime(CLOCK_REALTIME, &startAccess); //start clock
    for(counter = 0; counter < NUM_ACCESSES; counter++){
        newIndex=cacheArray[newIndex];
    }
    clock_gettime(CLOCK_REALTIME, &endAccess); //end clock
    //calculate the time elapsed in ns per access
    accessTime = (((endAccess.tv_sec - startAccess.tv_sec) * SECONDS_PER_NS) + (endAccess.tv_nsec - startAccess.tv_nsec)) / (NUM_ACCESSES);
    printf("Access time: %Lf for size %ld\n", accessTime, size);
} 

여러 시행에 걸쳐 평균을 낸 이 방법은 비교적 정확한 결과도 도출했습니다.첫 번째 선택은 확실히 둘 중에서 더 나은 선택이지만 이것은 또한 잘 작동하는 대체 접근법입니다.

언급URL : https://stackoverflow.com/questions/21369381/measuring-cache-latencies

반응형