Experimentos com a memória cache do CPU



Documentos relacionados
UNIVERSIDADE FEDERAL DO RIO GRANDE DO SUL INSTITUTO DE INFORMÁTICA INFORMÁTICA APLICADA

Orientação a Objetos

ARRAYS. Um array é um OBJETO que referencia (aponta) mais de um objeto ou armazena mais de um dado primitivo.

Algoritmos e Programação Estruturada

PROGRAMAÇÃO ESTRUTURADA. CC 2º Período

Diminui o gargalo existente entre processador e memória principal; 5 a 10 vezes mais rápidas que a memória principal; Ligada diretamente à MP;

Na disciplina de Cálculo Numérico, vamos trabalhar com a linguagem C++ e o compilador que vamos usar é o Dev C++.

5 - Vetores e Matrizes Linguagem C CAPÍTULO 5 VETORES E MATRIZES

Prof. Yandre Maldonado - 1 PONTEIROS. Prof. Yandre Maldonado e Gomes da Costa

Computadores XXI: Busca e execução Final

Programação de Computadores I. Linguagem C Vetores

Algoritmos de Busca em Tabelas

Vetores. Vetores. Figura 1 Exemplo de vetor com 10 elementos

PROGRAMAÇÃO ESTRUTURADA. CC 2º Período

Memória Flash. PdP. Autor: Tiago Lone Nível: Básico Criação: 11/12/2005 Última versão: 18/12/2006. Pesquisa e Desenvolvimento de Produtos

Busca. Pesquisa sequencial

Implementando uma Classe e Criando Objetos a partir dela

Estrutura da linguagem de programação C Prof. Tiago Eugenio de Melo tiago@comunidadesol.org

MC102 Algoritmos e programação de computadores Aula 3: Variáveis

JSP - ORIENTADO A OBJETOS

Sistemas Operacionais

Linguagem C Tipos de Dados. void; escalares; sizeof Vectores; strings em C Estruturas Introdução ao pré-processador

9 Comandos condicionais

O hardware é a parte física do computador, como o processador, memória, placamãe, entre outras. Figura 2.1 Sistema Computacional Hardware

Introdução às Linguagens de Programação

PARA A CONSTRUÇÃO DOS GRÁFICOS

5. EXPERIÊNCIAS E ANÁLISE DOS RESULTADOS Os Programas de Avaliação

LP II Estrutura de Dados. Introdução e Linguagem C. Prof. José Honorato F. Nunes honorato.nunes@ifbaiano.bonfim.edu.br

Persistência de Dados

Dadas a base e a altura de um triangulo, determinar sua área.

Algoritmos e Programação

Capacidade = 512 x 300 x x 2 x 5 = ,72 GB

Sistemas Operacionais e Introdução à Programação. Vetores e matrizes

DAS5102 Fundamentos da Estrutura da Informação

Organização e Arquitetura de Computadores I

Estruturas de Dados. Prof. Gustavo Willam Pereira Créditos: Profa. Juliana Pinheiro Campos

Análises Geração RI (representação intermediária) Código Intermediário

Resumo da Matéria de Linguagem de Programação. Linguagem C

Tabela de Símbolos. Análise Semântica A Tabela de Símbolos. Principais Operações. Estrutura da Tabela de Símbolos. Declarações 11/6/2008

Um processo sob UNIX ocupa uma área de memória formada basicamente por 3 partes:

3 Classificação Resumo do algoritmo proposto

Comandos Sequenciais if else, e Switch

Orientação a Objetos

Comparativo de desempenho do Pervasive PSQL v11

IBM1018 Física Básica II FFCLRP USP Prof. Antônio Roque Aula 6. O trabalho feito pela força para deslocar o corpo de a para b é dado por: = =

Introdução a Java. Hélder Nunes

GUIA DE FUNCIONAMENTO DA UNIDADE CURRICULAR

Tutorial: Programando no Linux

Árvores Binárias de Busca

MÓDULO 6 INTRODUÇÃO À PROBABILIDADE

Ministério da Educação Secretaria de Educação Profissional e Tecnológica Instituto Federal de Educação, Ciência e Tecnologia do Rio Grande do Sul

Algoritmos e Programação (Prática) Profa. Andreza Leite andreza.leite@univasf.edu.br

INF 1620 P1-10/04/02 Questão 1 Nome:

Alocação dinâmica de memória

Projeção ortográfica de modelos com elementos paralelos e oblíquos

RESOLUÇÃO DAS QUESTÕES DE RACIOCÍNIO LÓGICO-MATEMÁTICO

Estrutura de Dados Básica

EXEMPLO DE COMO FAZER UMA MALA DIRETA

Programador/a de Informática

ARQUITETURA DE COMPUTADORES

PROPRIEDADES DOS DETERMINANTES E O CÁLCULO DA ÁREA DE TRIÂN- GULOS: EXEMPLOS SIGNIFICATIVOS

OPERADORES E ESTRUTURAS DE CONTROLE

BARRAMENTO DO SISTEMA

SUMÁRIO 1. AULA 6 ENDEREÇAMENTO IP:... 2

Introdução a Programação. Ponteiros e Strings, Alocação Dinâmica

Programação de Computadores I. Ponteiros

Introdução à Programação

Organização e Arquitetura de Computadores I

Leandro Ramos RAID.

Computadores de Programação (MAB353)

Microsoft Project 2003

Processamento de Dados

Componentes da linguagem C++

Escalonamento no Linux e no Windows NT/2000/XP

20 Caracteres - Tipo char

10 DICAS DE TECNOLOGIA PARA AUMENTAR SUA PRODUTIVIDADE NO TRABALHO

Arquitetura de Rede de Computadores

BUSCA EM LISTAS LISTAS SEQÜENCIAIS, LISTAS SIMPLESMENTE E DUPLAMENTE ENCADEADAS E LISTAS CIRCULARES

Geração e Otimização de Código

Organização e Arquitetura de Computadores

Cálculo utilizando variáveis do tipo DATA

ORGANIZAÇÃO DE COMPUTADORES MÓDULO 10

Prof. Rafael Gross.

Tutorial de Matlab Francesco Franco

Operador de Computador. Informática Básica

Backup. Permitir a recuperação de sistemas de arquivo inteiros de uma só vez. Backup é somente uma cópia idêntica de todos os dados do computador?

A4 Projeto Integrador e Lista de Jogos

Computação II Orientação a Objetos

IFPE. Disciplina: Sistemas Operacionais. Prof. Anderson Luiz Moreira

Desenvolvendo Websites com PHP

Soluções Nível 1 5 a e 6 a séries (6º e 7º anos) do Ensino Fundamental

Excel Planilhas Eletrônicas

Introdução a Informática. Prof.: Roberto Franciscatto

Conceitos Importantes:

17 - Funções e Procedimentos em C Programação Modular

Java. Marcio de Carvalho Victorino

Transcrição:

Experimentos com a memória cache do CPU Alberto Bueno Júnior & Andre Henrique Serafim Casimiro Setembro de 2010 1

Contents 1 Introdução 3 2 Desvendando o cache 3 2.1 Para que serve o cache?..................... 3 2.2 Funcionamento básico...................... 3 2.3 Cache misses........................... 4 3 Causando falhas de cache 4 3.1 Teste 1: Percorrendo matrizes.................. 6 3.1.1 O código.......................... 6 3.1.2 O programa........................ 7 3.1.3 Testes........................... 8 3.1.4 Conclusões......................... 10 3.2 Teste 2: Somando vetores.................... 11 3.2.1 O código.......................... 11 3.2.2 O programa........................ 12 3.2.3 Testes........................... 13 3.2.4 Conclusões......................... 16 3.3 Teste 3: Memória compartilhada entre cores.......... 18 3.3.1 O código.......................... 18 3.3.2 O programa........................ 19 3.3.3 Testes........................... 20 3.3.4 Conclusões......................... 21 4 Conclusões gerais 23 5 Bibliografia 24 2

1 Introdução Este é um trabalho feito para a disciplina MAC 0412 - Organização de Computadores, ministrada, em 2010, pelo professor Alfredo Goldman (http://www.ime.usp.br/~gold). Esta disciplina é oferecida aos alunos de graduação em Ciência da Computação do Instituto de Matemática e Estatística (IME - http://www.ime.usp.br) da Universidade de São Paulo (USP - http://www.usp.br). O objetivo é mostrar algumas experiências que podem ser feitas com a memória cache do CPU. 2 Desvendando o cache 2.1 Para que serve o cache? A memória cache é uma forma utilizada pelo CPU para diminuir o tempo de acesso à memória. O cache é muito menor do que a memória principal, tanto em bytes quanto espaço físico, muitas vezes cabendo no próprio chip do processador. Além disso, o tempo de acesso ao cache é muito menor. 2.2 Funcionamento básico O cache é dividido nas chamadas linhas de cache, onde cada uma delas contém 3 campos: data: dados index: um índice que indica a posição daquela linha no cache tag: um índice que indica qual é a posição da memória principal que contém a outra cópia do dado Dessa forma, quando o processador recebe uma instrução que precisa fazer uma leitura ou escrita na posição de memória x, ele verifica se existe alguma linha de cache cujo campo tag contém o valor x. Se ele encontrar, dizemos que ocorreu um cache hit. Caso contrário, ocorreu um cache miss. Quando ocorre um cache miss, uma nova linha é criada no cache, colocando o valor x no campo tag. Depois disso o processador tem que copiar os dados da memória principal para essa nova linha (no caso em que a instrução foi de leitura) ou copiar para a memória (caso de escrita). Um detalhe importante 3

é que para criar uma nova linha no cache, o mesmo precisa abrir mão de outra linha. A heurística utilizada pelo processador para escolher tal linha é chamada de Política de substituição. 2.3 Cache misses Existem três tipos de cache miss: Leitura de instrução: É quando o processador não encontra um instrução no cache e então tem que buscá-la na memória principal, fazendo com que o processo tenha que esperar até que a instrução seja obtida. É o que causa mais delay. Leitura de dados: É quando o processador não encontra um determinado dado no cache. Enquanto o dado não é retornado da memória principal, as instruções que não dependem daquela leitura podem ser executadas. Assim que o dado vier da memória, as instruções dependendes daquela leitura podem voltar à execução. É o que causa delay mediano. Escrita de dados: Semelhante ao item acima, com a diferença de que a escrita na memória principal pode ser enfileirada (para ser escrita, de fato, mais tarde). Assim, o processador só escreve os dados na memória principal quando esta fila está cheia. É o que causa menos delay. 3 Causando falhas de cache Nesta seção estão alguns exemplos de como se pode causar falhas de cache. Algumas considerações preliminares acerca dos testes: Computador: A máquina utilizada tem o processador Intel Core 2 Duo P7550 @ 2.26 GHz com 3 MB de cache L2. Sistema operacional: Ubuntu Linux 10.04. Linguagem de programação: A linguagem utilizada foi C. Compilação de código: O compilador utilizado foi o gcc, com as opções -O1, -O2, -O3, -O4, dependendo do teste. 4

Medida de consumo de tempo: A ferramenta utilizada para medir o tempo de execução dos programas foi /usr/bin/time, do Linux, com a opção %U, que mede a quantidade de tempo que o processador gastou processando as instruções do programa (não conta o tempo de criação do processo no sistema operacional nem o tempo em que o processo está esperando na fila do S.O., por exemplo). Medida do número de cache misses: Foi utilizada a ferramenta valgrind para medir o número de falhas de cache, com a opção tool=cachegrind. 5

3.1 Teste 1: Percorrendo matrizes 3.1.1 O código teste1.c: 1 #include <s t d i o. h> 2 #include <s t d l i b. h> 3 4 5 int main ( int argc, char argv [ ] ) { 6 int i, j, s i z e ; 7 int p ; 8 9 s i z e = a t o i ( argv [ 1 ] ) ; / matriz de s i z e l i n h a e s i z e colunas / 10 11 p = ( int ) malloc ( s i z e sizeof ( int ) ) ; 12 for ( i = 0 ; i < s i z e ; i ++) 13 p [ i ] = ( int ) malloc ( s i z e sizeof ( int ) ) ; 14 15 i f ( argc == 2) { / Se f o i passado apenas um argumento, p e r c o r r e por l i n h a s / 16 for ( i = 0 ; i < s i z e ; i++) { 17 for ( j = 0 ; j < s i z e ; j++) { 18 p [ i ] [ j ] = 0 ; 19 } 20 } 21 } 22 23 else {/ Senao, p e r c o r r e por colunas / 24 for ( i = 0 ; i < s i z e ; i++) { 25 for ( j = 0 ; j < s i z e ; j++) { 26 p [ j ] [ i ] = 0 ; 27 } 28 } 29 } 30 31 for ( i = 0 ; i < s i z e ; i ++) 32 f r e e ( p [ i ] ) ; 33 f r e e ( p ) ; 34 return 0 ; 35 } 6

3.1.2 O programa O programa teste1.c aloca na memória uma matriz de inteiros com size linhas e size colunas, onde size é um argumento passado para o programa pela linha de comando. Em seguida, a matriz é preenchida com zeros. A principal questão do problema é o modo como a matriz é percorrida. Se nenhum argumento, além de size, for passado, a matriz será percorrida por linhas (isto é, serão zerados os elementos p[0][0], p[0][1],...,p[0][size - 1], p[1][0],..., p[size - 1][size - 1], nesta ordem). Caso contrário, ela será percorrida por colunas (p[0][0], p[1][0],..., p[size - 1][0], p[0][1],..., p[size - 1][size - 1]). 7

3.1.3 Testes Consumo de tempo No gráfico acima, as linhas tracejadas representam os testes por linha, enquanto as linhas continuas representam os testes por coluna. As cores representam o nível de otimização usado pelo compilador gcc. O tamanho da matriz (a variável size) foi variado de 0 até 15000, de 1000 em 1000. 8

Falhas de cache O esquema de cores e linhas do gráfico acima é o mesmo do gráfico por tempo. A diferença agora é que o eixo vertical representa a porcentagem de misses do L2 que ocorreram durante a execução do programa (n o de misses por n o de acessos ao cache). 9

3.1.4 Conclusões Nesse teste estamos atacando uma característica da linguagem de programação C, que é a de armazenar as matrizes na memória alocadas em blocos de linhas. Assim, posições adjacentes em uma mesma linha estão também fisicamente adjacentes na memória. Independentemente da política de substituição dos dados do cache, a escrita que é feita no cache é feita por blocos de memória. Assim, ao acessarmos as posições de memória da matriz, percorrendo-a tanto por linha quanto por coluna, sempre teremos trechos inteiros das linhas da matriz copiadas da memória principal no cache. Isso explica o comportamento das curvas referentes a percorrer a matriz por linha nos gráficos. Vale notar, entretanto, alguns detalhes sobre as curvas referentes a percorrê-la por coluna. No 1 o gráfico, o formato exponencial da curva de gasto de tempo é decorrente do fato que o número de operações cresce conforme o tamanho da matriz, e este cresce exponencialmente conforme size varia linearmente. No 2 o gráfico, o salto que a curva dá ao variar size de 2000 para 3000 é devido o fato de que com size = 2000 a matriz cabe inteira no cache L2, e apartir de size = 3000 já não cabe mais. É isso que faz com que tantos cache misses aconteçam. 10

3.2 Teste 2: Somando vetores 3.2.1 O código teste2.c: 1 #include <s t d i o. h> 2 #include <s t d l i b. h> 3 4 #define SIZE 10000000 5 6 int main ( int argc, char argv [ ] ) { 7 int o f f s e t, array1, array2, ans, i, j ; 8 9 / Se nenhum argumento f o r passado, roda com o f f s e t 1 / 10 i f ( argc > 1) 11 o f f s e t = a t o i ( argv [ 1 ] ) ; 12 else 13 o f f s e t = 1 ; 14 15 array1 = ( int ) malloc ( SIZE sizeof ( int ) ) ; 16 array2 = ( int ) malloc ( SIZE sizeof ( int ) ) ; 17 ans = ( int ) malloc ( SIZE sizeof ( int ) ) ; 18 19 for ( i = 0 ; i < SIZE ; i ++) { 20 array1 [ i ] = rand ( ) %1000; 21 array2 [ i ] = rand ( ) %1000; 22 } 23 24 for ( i = 1 ; i <= o f f s e t ; i++) { 25 for ( j = i 1; j < SIZE ; j += o f f s e t ) 26 ans [ j ] = array1 [ j ] + array2 [ j ] ; 27 } 28 29 f r e e ( array1 ) ; 30 f r e e ( array2 ) ; 31 f r e e ( ans ) ; 32 return 0 ; 33 } 11

3.2.2 O programa O programa teste2.c aloca três vetores de 10 milhões de posições na memória, inicializa os dois primeiros e depois soma, de posição em posição, os dois e guarda no terceiro. Se um número offset for passado como argumento, o programa soma de offset em offset (isto é, soma a posição 0, depois a posição offset, depois a posição 2 * offset, até y = k * offset tal que y 10 milhões, depois soma 1, 1 + offset, etc). 12

3.2.3 Testes Consumo de tempo v1 Para melhorar a legibilidade do gráfico acima, omitimos as linhas referentes às otimizações -O2, -O3, -O4, pois elas apresentaram o mesmo comportamento que a otimizaçao -O1. O gráfico foi feito com o offset variando de 1 até 3001, de 10 em 10. 13

Consumo de tempo v2 O gráfico acima é basicamente a mesma coisa do anterior, mas estendendo a variação até 46051. 14

Falhas de cache O gráfico acima mostra, no eixo vertical, a porcentagem de cache misses que ocorreram no cache L2 e, no eixo horizontal, o offset variando de 1 até 4501, de 10 em 10. 15

3.2.4 Conclusões Este teste ilustra dois fatores muito importantes nas políticas de substituição de dados na memória cache: o tamanho dos blocos a serem copiados para a memória de cache e o tempo de permanência dos mesmos lá até que sejam substituídos. Como essas políticas não são muito fáceis de se definir para cada processador, nossa conclusão aqui não é totalmente certa. O 1 o (consumo de tempo) e o 3 o gráfico (cache miss) revelam um comportamento bastante semelhante entre consumo de tempo e cache misses. Dessa forma, vamos nos concentrar em analisar trechos importantes do 2 o gráfico (consumo de tempo - extendido): offset <1000 Aqui encontramos o pico do consumo de tempo. Os valores de offset aqui são bastante pequenos em relação ao tamanho do vetor, o que significa que são necessárias muitas iterações de tamanho offset para percorrê-lo completamente. Conforme o vetor é percorrido de offset em offset posições, blocos do vetor são copiados para o cache. O aumento da taxa de cache misses se deve ao fato de que, na próxima varredura do vetor, os blocos que haviam sido copiados já foram sobreescritos por outros dados. Assim, o mesmo bloco precisa ser recopiado para o cache. 2000 <offset <3000 Conforme offset vai aumentando, as varreduras do vetor inteiro demoram menos e então blocos de memória passam a utilizados em mais de uma varredura. O fato de essa região também apresentar o vale de consumo de tempo (e o mesmo estar logo após o pico) tem a ver com o equilíbrio entre tempo de retorno ao mesmo bloco de dados e a quantidade de blocos distintos do vetor que estão distribuídos no cache (que neste ponto ainda são muitos, pois offset ainda não cresceu muito). 3000 <offset <15000 Conforme offset vai aumentando, o número de blocos distintos do vetor que estão distribuídos no cache também vai diminuindo. E isso faz com que aumente um pouco o número de cache misses. Este crescimento é linear pois a proporção do blocos é diminui lineramente com o crescimento linear de offset. 16

offset >15000 O processo tende a ficar constante. Provavelmente aqui já não há mais influências diretas das falhas de cache. Este tempo deve ser somente o tempo de processamento para um número mais ou menos fixo delas. 17

3.3 Teste 3: Memória compartilhada entre cores 3.3.1 O código teste3.c: 1 #include <s t d l i b. h> 2 #include <sched. h> 3 #include <s t d i o. h> 4 #define SIZE 2000 5 6 struct s h a r e d d a t a s t r u c t { 7 unsigned int data1 ; 8 unsigned int data2 ; 9 } ; 10 11 struct s h a r e d d a t a s t r u c t shared data ; 12 13 static int i n c s e c o n d ( struct s h a r e d d a t a s t r u c t ) ; 14 15 int main ( int argc, char argv [ ] ) { 16 17 int i, j, pid, f l a g = 0 ; 18 void c h i l d s t a c k ; 19 20 i f ( argc > 1) f l a g = 1 ; 21 22 / Aloca a memoria da p i l h a do segundo processo / 23 i f ( ( c h i l d s t a c k = ( void ) malloc (4096) ) == NULL) { 24 p e r r o r ( Cannot a l l o c a t e stack f o r c h i l d ) ; 25 e x i t ( 1 ) ; 26 } 27 28 / Cria um c l o n e que roda no mesmo espaco de memoria / 29 / Se por acaso e s t e programa der f a l h a de segmentacao ao rodar, t e n t e t i r a r o 30 +4096 da chamada da funcao / 31 i f ( f l a g == 1) { 32 i f ( ( pid = c l o n e ( ( void )&inc second, c h i l d s t a c k +4096, 33 CLONE VM, &shared data ) ) < 0) { 34 p e r r o r ( c l o n e c a l l e d f a i l e d. ) ; 35 e x i t ( 1 ) ; 36 } 37 } 38 39 / Modifica a primeira v a r i a v e l da s t r u c t compartilhada / 40 for ( j = 0 ; j < SIZE ; j++) { 41 for ( i = 0 ; i < 100000; i++) { 18

42 shared data. data1++; 43 } 44 } 45 46 return 0 ; 47 48 } 49 50 / Funcao que r e p r e s e n t a a execucao do processo f i l h o / 51 int i n c s e c o n d ( struct s h a r e d d a t a s t r u c t sd ) 52 { 53 int i, j ; 54 / Modifica a segunda v a r i a v e l da s t r u c t compartilhada / 55 for ( j = 1 ; j < SIZE ; j++) { 56 for ( i = 1 ; i < 100000; i++) { 57 sd >data2++; 58 } 59 } 60 } 3.3.2 O programa O programa teste3.c possui a estrutura shared data struct, que possui duas variáveis. Se nenhum argumento for passado, o programa teste3.c realiza SIZE 100, 000 incrementos na primeira variável. Caso contrário, ele cria um processo filho, que compartilha a estrutura shared data struct e realiza o mesmo número de incrementos na segunda variável, e depois realiza os incrementos na primeira variável. 19

3.3.3 Testes Consumo de tempo No gráfico acima, o tempo consumido pelo programa compilado com as otimazações -O1, -O2, -O3, -O4 é praticamente zero e por isso as barras não aparecem no gráfico. 20

3.3.4 Conclusões O Teste 3 mostra que compartilhar memória cache entre processos (ou entre cores, se o processador for multicore) pode causar falhas de cache. A estrutura criada no programa tem dois inteiros (4 bytes cada um), totalizando 8 bytes. Essa quantidade é pequena se comparada ao tamanho do cache line do cache L2, que é de 64 bytes. Isso quer dizer que toda a estrutura cabe em apenas uma cache line. Assim, quando o primeiro processo (processo pai) altera a primeira variável da estrutura, o cache marca aquela linha como suja. Assim, quando o segundo processo (processo filho) tenta alterar a segunda variável da estrutura (que está na mesma cache line) ele percebe que aquela linha está marcada como suja e daí é necessário fazer dois acessos à memória: um para escrever a linha suja na memória principal e outra para copiar para o cache a nova informação da memória principal. Por esse motivo, o caso em que o processo filho é criado (quando passamos um argumento para o programa) leva mais que o dobro do tempo do caso em que ele não é criado. Uma possível solução para esse problema é fazer com que as duas variáveis da estrutura estejam em cache lines diferentes. Um jeito de fazer isso é substituindo as linhas 1 struct s h a r e d d a t a s t r u c t { 2 unsigned int data1 ; 3 unsigned int data2 ; 4 } ; por 1 struct s h a r e d d a t a s t r u c t { 2 unsigned int data1 ; 3 int aux [ 2 0 ] ; 4 unsigned int data2 ; 5 } ; Dessa forma, o vetor aux ocupará 20 4 = 80 bytes na memória, fazendo com que as variáveis data1 e data2 estejam em cache lines diferentes. 21

O gráfico abaixo mostra o que acontece com o consumo de tempo fazendo essa modificação. Como a máquina utilizada no teste possui dois cores, os processos rodam em paralelo, consumindo praticamente o mesmo tempo que apenas um processo. 22

4 Conclusões gerais A melhor conclusão que pode ser feita a partir dos resultados apresentados é que o programador deve conhecer as formas como o cache é utilizado e, dependendo da aplicação, conhecer a máquina onde tal aplicação irá rodar. Ter esse conhecimento ajuda a diminuir os cache misses e, consequentemente, aumentar o desempenho (em relação ao tempo consumido) da aplicação. Essa não é uma tarefa fácil, pois o simples fato de percorrer uma matriz por colunas na linguagem C pode aumentar em muito o tempo de execução, como foi mostrado aqui. Contudo, qualquer conhecimento pode vir a ser útil. 23

5 Bibliografia http://en.wikipedia.org/wiki/cpu_cache http://en.wikipedia.org/wiki/cache_algorithms 24