



Estude fácil! Tem muito documento disponível na Docsity
Ganhe pontos ajudando outros esrudantes ou compre um plano Premium
Prepare-se para as provas
Estude fácil! Tem muito documento disponível na Docsity
Prepare-se para as provas com trabalhos de outros alunos como você, aqui na Docsity
Os melhores documentos à venda: Trabalhos de alunos formados
Prepare-se com as videoaulas e exercícios resolvidos criados a partir da grade da sua Universidade
Responda perguntas de provas passadas e avalie sua preparação.
Ganhe pontos para baixar
Ganhe pontos ajudando outros esrudantes ou compre um plano Premium
Comunidade
Peça ajuda à comunidade e tire suas dúvidas relacionadas ao estudo
Descubra as melhores universidades em seu país de acordo com os usuários da Docsity
Guias grátis
Baixe gratuitamente nossos guias de estudo, métodos para diminuir a ansiedade, dicas de TCC preparadas pelos professores da Docsity
Consumo de memória
Tipologia: Notas de estudo
1 / 7
Esta página não é visível na pré-visualização
Não perca as partes importantes!
2.2.1. Divisão da memória
Inicialmente, temos que entender como funciona a memória do computador, este assunto será melhor explorado em outras disciplinas, em particular na de estrutura de computadores. Mas para entendermos as diferenças de implementações de alguns algoritmos, temos que ter uma ideia inicial de como a memória do computador é organizada pela CPU e pelo compilador.
2.2.1.1 Divisão da memória pela CPU
Em termos de divisão física da memória pela CPU, existem dois tipos de estruturas mostrados na Figura 1.
Figura 1 – Divisão física da memória pela CPU. a) Dados e programa separados. b) Dados e programas juntos.
Na estrutura mostrada no diagrama a , a memória onde o programa é armazenado é completamente distinta da memória onde os dados são armazenados. Isto significa que o mesmo endereço pode ser usado tanto para um acesso a dados quanto para programa, a distinção é feita por uma linha de controle, que quando está em 0 o acesso é a memória de dados e quando está em 1 o acesso é a memória de programa, ou vice versa.
Na estrutura b , a memória é única, para dados e programas, a divisão é feita pelo endereço que deve ser distinto.
Atualmente a estrutura b é a mais empregada, por ter vantagens como, a possibilidade de acessar constantes diretamente da memória de programa, na estrutura a as constantes devem ser colocadas na memória de dados e inicializadas por um seguimento de programa que é executado antes do programa principal. Para o nosso estudo, não importa qual das duas estruturas estamos usando, vamos analisar apenas a memória de dados.
A CPU utiliza o final do seguimento de dados para um controle simples e eficiente feito por uma fila, conhecido como pilha. Ele possui este nome devido a semelhança com uma pilha de
pratos, onde um novo prato é sempre colocado no topo da pilha e sempre que se retira um prato, também é feito do topo. Este tipo de fila é chamada de LIFO (último a entrar primeiro a sair, do inglês last in first out ). Tenenbaum Erro! Fonte de referência não encontrada. faz uma analogia da pilha a um estacionamento de trens com uma linha única, não dá para tirar um vagão do meio, sempre temos que respeitar a ordem de entrada. Outra particularidade da pilha é que ela é sempre inicia no final da memória de dados e cresce em direção ao inicio da memória.
2.2.1.2. Divisão da memória pelo compilador
A pilha é empregada pelos compiladores parra o armazenamento de todo o contexto local de cada função, ou seja, as variáveis locais, os registradores, endereço de retorno, etc.
Na maioria das linguagens de computador, o compilador também divide a memória de dados, exceto a pilha, em ao menos duas áreas. Por motivos históricos, a primeira é chamada de BSS (bloco iniciado por símbolos, do inglês block started by symbol ) onde são armazenadas as variáreis globais e a segunda é a Heap , onde são armazenadas as variáveis alocadas dinamicamente (comando, por exemplo, malloc e new ). Assim, ao todo, a memória de dados é particionadas em 3 partes, como mostra a Figura 2.
Figura 2 – Divisão da memória de dados em BSS, Heap e Pilha ( Stack ).
Além de possuir controles e aplicações distintos, cada uma destas áreas possuem características importantes em termos de capacidade.
BSS : alocada de forma estática, em tempo de compilação, não pode ser alterada ou reaproveitada pelo programa durante a execução. Heap : Tem tamanho variado, inicia logo após a BSS e cresce em direção ao fim da memória. Os sistemas operacionais podem fazer cópias de parte do conteúdo da Heap no HD, isto deixa o sistema mais lento, mas deixa a Heap com tamanho muito grande, limitada apenas pela capacidade do HD. Pilha ( Stack ) : Por ser uma LIFO, é muito simples e rápida, mas tem tamanho máximo bastante limitado^1.
(^1) É possível redefinir o tamanho máximo da Pilha, mas sempre haverá um limite definido pelo CPU.
O conjunto de valores armazenados nos registradores é chamado, de forma genérica de contexto do programa , observe que ele se altera a cada instrução executada pela CPU.
2.2.2. Operações na CPU
Com raras exceções, as CPUs não executam as operações utilizando os valores diretamente da memória externa, ela sempre copia os valores para um registrador interno, executa a operação e copia o resultado de volta na memória externa, por exemplo, a operação
K n-z
é executada na CPU da seguinte forma
EAX n; EBX z; sub EAX, EBX; //EAX é subtraído de EBX e o resultado colocado em EAX. k EAX;
2.2.2.1. Utilização da pilha
A pilha é controlada pelo registrador SP (ponteiro da pilha, do inglês stack pointer ) ou ESP (ponteiro da pilha estendido, do inglês extended stack pointer ). O depósito de um dado na pilha é feito pela instrução push e sua retirada pela instrução pop , a Figura 4 mostra um exemplo de empilhamento e a Figura 5 o desempilhamento de dados na pilha, na parte superior estão as instruções e dados na memória externa e na parte de baixo o conteúdo de cada registrador envolvido na operação.
Figura 4 – Empilhamento passo a passo de dados na pilha. Código colocado a partir da posição 100 da memória e apontado pelo registrador EIP, topo da pilha na posição 999 da memória e apontado pelo registrador ESP.
Figura 5 – Desempilhamento passo a passo de dados na pilha executado na ordem inversa a do empilhamento. Valor final dos registradores invertidos em relação ao inicio do código.
2.2.2.2. Chamada de função
Quando uma função é chamada, todo o contexto atual é salvo na pilha, além disto, um espaço é reservado para a colocação de todas as variáveis locais. Para entender este conceito, vamos analisar a utilização da pilha para o código abaixo que é composto por duas funções.
função myFunc1() declara myVar1 interio ; início 100 myVar1 10; 101 myFunc2(myVar1); 102 myVar1 myVar1 + 15; 103 myFunc2(myVar1); 104 fim.
função myFunc2(myPar interio ) declara myVar2 interio ; início 200 myVar2 10 + myPar; 201 fim.
Para facilitar o entendimento as linhas de código com comandos foram numeradas, vamos assumir que cada uma destas linhas ocupam uma posição de memória, que será apontada por EIP. Assumiremos também, que todas as variáveis inteiras possuem um único Byte, ou seja, também uma única posição de memória. Assim, a execução passo a passo do código acima, partindo a função myFunc1 pode ser acompanhada pela Tabela 1.
FLOOD-FILL(imagem[][], X-1, Y, chave, cor); FLOOD-FILL(imagem [][], X, Y+1, chave, cor); FLOOD-FILL(imagem [][], X, Y-1, chave, cor); fim se. fim.
Este é um conhecido algoritmo de preenchimento de superfície, neste caso, a imagem é definida pela matriz bidimensional imagem[][], as coordenadas X e Y são a posição inicial para inicio do preenchimento. A variável chave possui a cor que deve ser substituída e cor a nova cor que deve ser mostrada. Vamos entender seu funcionamento na sequencia mostrada na Figura 6.
Figura 6 – Sequencia de preenchimento de superfície usando o algoritmo Flood-Fill recursivo.
Na letra a , o pontos em laranja representam o contorno da imagem que será preenchida e os pontos em branco (sem cor) são da cor chave. O ponto em azul é a semente inicial, as coordenadas X e Y de inicio do processo de preenchimento. Este ponto é inicialmente branco também. Ao se chamar o algoritmo FLOOD-FILL com estas coordenadas, está é pintada e de forma recursiva, o mesmo algoritmo é chamado para os seus vizinhos, superior, inferior, antecessor e sucessor. Ou seja, para cada ponto pintado quatro novos são enfileirados para serem comparados. Sempre que o ponto analisado não possui a cor da chave ele retorna sem analisar seus vizinhos. Assim, quando todos os pontos dentro da região delimitada forem pintados o algoritmo será encerrado.
Desta forma, para cada ponto dentro da área demarcada da imagem, ao menos uma chamada da função será feita de forma recursiva e seu contexto (registradores e endereço de retorno), será armazenado na pilha. Isto significa que o tamanho da pilha deverá ser proporcional ao tamanho da imagem o que é inviável nos modelos atuais de computadores.
Isto significa que alguns algoritmos são melhores entendidos quando estudados na forma recursiva, porém, poucos algoritmos recursivos são realmente viáveis.
EXERCÍCIO PARA CASA: