Cache sizes with CPUID on Intel CPU's

After working a while on Graphical RamVerk and working my way around D the last couple of weeks I realized something. Getting the cache sizes of an Intel CPU is not an easy task. Or more like it is an easy task, but the internet is not really helpful with how you do it. After some googling and reading in the Intel Architecture Manual (it's almost 4k pages, but the relevant information is on Vol. 2A 3-203) I realized how it's done. In C++ I haven't really found anyone explaining how this works. In D, there is a cpuid module that can retrieve cache information, but from what I've found it's incorrect. I have found a way to retrieve this information both in C++ and D, that correctly matches the values on the specs of my CPU (Intel i7-4770k) and the values I retrieved from CPU-Z.

C++

Since I am on a Windows machine, I can will be using __cpuid and __cpuidex, but it can easily be translated to Assembly. This code only covers the L1 instruction cache, but to retrieve the rest, you simply pass another index to __cpuidex as the subfunction.

int GetCacheSize(int cache_level)  
{
    // Intel stores it's cache information in eax4, with ecx as index
    // The information received is as following:
    // ebx[31:22] = Ways of associativity
    // ebx[21:12] = Physical line partitions
    // ebx[11: 0] = Line size
    int cpuInfo[4];
    __cpuidex(cpu_info, 4, cache_level); // Index 0 is L1 data cache
    int ways = cpu_info[1] & 0xffc00000; // This receives bit 22 to 31 from the ebx register
    ways >>= 22; // Bitshift it 22 bits to get the real value, since we started reading from bit 22
    int partitions = cpu_info[1] & 0x3ff800; // This receives bit 12 to 21
    partitions >>= 12; // Same here, bitshift 12 bits
    int line_size = cpu_info[1] & 0x7ff; // This receives bit 0 to 11
    int sets = cpu_info[2]; // The sets are the value of the ecx register
    // All of these values needs one appended to them to get the real value
    return (ways + 1) * (partitions + 1) * (line_size + 1) * (sets + 1); // Calculate the cache size by multiplying the values
}

D

In D, there is no function wrapper around cpuid, so we need some inline assembly. Otherwise it's pretty much the same.

int getCacheSize(int cacheLevel)  
{
    // Intel stores it's cache information in eax4, with ecx as index
    // The information received is as following:
    // ebx[31:22] = Ways of associativity
    // ebx[21:12] = Physical line partitions
    // ebx[11: 0] = Line size
    int cpuInfo[4] = 0;
    asm
    {
        mov EAX, functionID;
        mov ECX, cacheLevl; // The index here is the cache level (0 = L1d, 1 = L1i, 2 = L2 etc.)
        cpuid;
        mov cpuInfo, EAX;
        mov cpuInfo + 4, EBX;
        mov cpuInfo + 8, ECX;
        mov cpuInfo + 12, EDX;
    }
    int ways = cpuInfo[1] & 0xffc00000; // This receives bit 22 to 31 from the ebx register
    ways >>= 22; // Bitshift it 22 bits to get the real value, since we started reading from bit 22
    int partitions = cpuInfo[1] & 0x3ff800; // This receives bit 12 to 21
    partitions >>= 12; // Same here, bitshift 12 bits
    int lineSize = cpuInfo[1] & 0x7ff; // This receives bit 0 to 11
    int sets = cpuInfo[2]; // The sets are the value of the ecx register
    // All of these values needs one appended to them to get the real value
    return (ways + 1) * (partitions + 1) * (lineSize + 1) * (sets + 1);
}

So there you have it. This simple function takes the desired cache level as parameter and returns the size of that cache.

Edit: Thanks to /u/MCPtz for discovering my typo in the partition calculation!
Edit2: Thanks to /u/BeltfedGames for discovering the fact that index 0 is L1 data cache, and for the missing definition of cpu_info/cpuInfo!

Melker Litsgård

Read more posts by this author.

Falun, Sweden