Lição
A teoria — Localidade na cache: a ordem das leituras importa
Uma cache é uma memória pequena e rápida que guarda cópias de dados da memória principal em linhas. Cada linha contém um bloco alinhado de L bytes. Uma leitura cujo bloco já está numa linha é um acerto; caso contrário, ocorre uma falha e carrega-se o bloco inteiro. Numa cache de mapeamento direto com S linhas, o bloco que contém o endereço a só pode ocupar a linha ⌊a / L⌋ mod S. Um ciclo que lê endereços vizinhos sucessivamente aproveita cada bloco carregado para várias leituras. A esta propriedade chama-se localidade espacial.
O que significa cada símbolo
L- tamanho da linha em bytes: 16 aqui, 64 na maioria dos processadores de computadores de secretária e servidores
S- número de linhas: 8 aqui
- Pressupõe
- Uma cache de mapeamento direto, inicialmente vazia, inteiros de 4 bytes e uma matriz guardada por linhas, como em C. Em Fortran, as matrizes são guardadas por colunas, pelo que a ordem mais rápida é a inversa. Contam-se apenas as leituras da matriz. As caches reais de dados de primeiro nível são maiores e associativas por conjuntos, tipicamente com 8 vias: um bloco pode ocupar qualquer uma das 8 linhas do seu conjunto. Isto elimina a maioria dos conflitos aqui ilustrados, mas não resolve a falta de espaço. Não se considera a pré-busca por hardware, que carrega a linha seguinte antes de esta ser solicitada.
Problema resolvido na íntegra
-
Quanto tráfego de memória custa a ordem menos favorável? 4 passos
Prima 8 × 8 por colunas e explique os 0 acertos. Considere depois uma matriz de 1024 × 1024 com inteiros de 4 bytes e uma cache de mapeamento direto de 32 KiB, com linhas de 64 bytes. Quantos bytes são transferidos da memória ao somar os elementos por linhas? E por colunas? Acrescentar a cada linha da matriz um espaço equivalente a uma linha de cache ajuda?
Abrir este problema na ferramenta
-
Nesta página, cada linha da matriz ocupa 8 × 4 = 32 bytes, ou seja, 2 linhas de cache. A linha i começa, portanto, na linha de cache 2i mod 8, e a coluna 0 usa apenas as linhas 0, 2, 4 e 6. a[4][0] ocupa a linha onde estava a[0][0]. Quando o ciclo chega a a[0][1], o seu bloco já foi removido: 0 acertos em 64 leituras. Por linhas, há uma falha por bloco de 16 bytes, num total de 16 falhas e 48 acertos: 1 − 4/16 = 75%.
-
A cache maior tem 32 768 / 64 = 512 linhas. Por linhas, cada linha de cache de 64 bytes contém 16 inteiros, e só a primeira leitura falha: 1 048 576 / 16 = 65 536 falhas. A taxa de acertos é 1 − 4/64 = 93,75%, e são transferidos 65 536 × 64 = 4 194 304 bytes, exatamente o tamanho da matriz.
-
Por colunas, cada linha da matriz ocupa 4 096 bytes, ou seja, 64 linhas de cache. A linha i começa na linha de cache 64i mod 512, e uma coluna usa apenas 8 das 512 linhas. a[8][0] remove o bloco de a[0][0], muito antes de o ciclo regressar para ler a[0][1]. Todas as 1 048 576 leituras falham: são transferidos 67 108 864 bytes, 16 vezes mais.
-
Acrescentar 16 inteiros a cada linha da matriz aumenta o seu tamanho para 4 160 bytes, ou seja, 65 linhas de cache. Como 65 e 512 são primos entre si, cada coluna passa a usar todas as 512 linhas de cache. Continua a não haver acertos: uma coluna acede a 1 024 blocos e a cache só comporta 512. Todos os blocos são removidos antes de a coluna seguinte precisar deles.
Resposta
4 MiB por linhas, 64 MiB por colunas. O espaço extra distribui os blocos de cada coluna por toda a cache, mas continua a não haver acertos. Resolve os conflitos; cada coluna desta matriz, porém, ocupa mais espaço do que a cache inteira.
-
Percurso de aprendizagem
Como um computador executa um programa
Referências (1)
- The first insight's measured case, the line size in the definition, and the 8-way first-level caches in `assumes`: Ulrich Drepper, "What Every Programmer Should Know About Memory," Red Hat, Inc., version 1.0, 21 November 2007. §3.2 (p. 15), §3.3.1 (pp. 17–18), §6.2.1 (pp. 49–50).