MCU.TEC C,Padrões de Projetos Pool Allocation Pattern

Pool Allocation Pattern


📖 Origem: Real-Time Design Patterns: Robust Scalable Architecture for Real-Time Systems – Bruce Powel Douglass

Resumo

O Pool Allocation Pattern é um padrão que gerencia a memória de forma eficiente ao pré-alocar blocos de tamanho fixo para reutilização. Ele resolve problemas comuns da alocação dinâmica, como fragmentação e imprevisibilidade no tempo de acesso, tornando-se essencial para sistemas embarcados e de tempo real. Esse padrão melhora a eficiência do uso da RAM ao garantir que blocos de memória sejam reutilizados conforme necessário, sem necessidade de alocações dinâmicas constantes.

Problema a ser resolvido

A alocação dinâmica de memória (malloc() e free()) pode levar à fragmentação e latências imprevisíveis, o que é inaceitável para sistemas embarcados de tempo real. Além disso, quando a memória se esgota ou está altamente fragmentada, pode ocorrer falha na alocação de novos blocos, resultando em comportamento inesperado do sistema.

Outro problema comum é a sobrecarga de gerenciamento de memória, onde a alocação e desalocação frequente de pequenos blocos gera um overhead significativo. Isso reduz a eficiência do sistema e pode impactar o tempo de resposta em aplicações críticas. Em sistemas embarcados com recursos limitados, é crucial garantir que a memória seja usada de forma previsível e eficiente.

O Pool Allocation Pattern resolve esses problemas ao manter um conjunto fixo de blocos de memória pré-alocados, permitindo que o sistema reutilize esses blocos de maneira eficiente. Isso elimina a necessidade de alocação e desalocação dinâmica, garantindo tempos de resposta constantes e previsíveis.

Estrutura do Padrão

A estrutura desse padrão se baseia em um gerenciador de pool de memória, que mantém uma lista de blocos livres prontos para serem usados. Cada bloco tem um tamanho fixo e pode ser alocado rapidamente sem a necessidade de recorrer ao heap. A fábrica de objetos pode ser usada para criar e inicializar os blocos, enquanto um gerenciador de heap dimensionado garante que os blocos sejam usados de maneira eficiente.

Os dados são armazenados no segmento de memória, garantindo que a RAM seja utilizada de forma previsível. O acesso ao pool pode ser gerenciado por mecanismos como filas FIFO (First In, First Out) ou LIFO (Last In, First Out), dependendo das necessidades da aplicação.

Papéis de Colaboração

  • Cliente: Solicita e libera blocos de memória conforme necessário.
  • Lista de Blocos Livres: Mantém o controle dos blocos de memória disponíveis para reutilização.
  • Gerenciador de Pool: Supervisiona a alocação e liberação dos blocos, garantindo que a memória seja usada eficientemente.
  • Segmento de Memória: Armazena fisicamente os blocos alocados.
  • Fábrica de Objetos: Pode ser usada para criar instâncias de objetos que utilizam a alocação por pool.
  • Heap Dimensionado: Garante que os blocos tenham um tamanho fixo e que a memória seja utilizada de maneira eficiente.

Consequências

O Pool Allocation Pattern traz diversas vantagens para sistemas embarcados. Ele reduz drasticamente o tempo de alocação de memória, pois os blocos já estão pré-alocados e prontos para uso. Isso melhora o tempo de resposta do sistema e evita problemas de fragmentação. Além disso, ao reutilizar blocos já existentes, o padrão reduz a necessidade de operações de gerenciamento de heap, economizando processamento.

Entretanto, esse padrão pode levar ao desperdício de memória se o número de blocos pré-alocados for maior do que o necessário. Se a quantidade de blocos for insuficiente, pode ser necessário um mecanismo de fallback para lidar com situações onde não há memória disponível. Além disso, a implementação deve ser bem planejada para evitar vazamentos de memória devido a blocos que não são liberados corretamente.

Estratégias de Implementação

Para implementar o Pool Allocation Pattern, um array de blocos de memória é criado no momento da inicialização do sistema. Um gerenciador de pool mantém uma lista encadeada de blocos disponíveis, permitindo alocação e liberação rápidas. Em sistemas que exigem eficiência máxima, um buffer circular pode ser utilizado para gerenciar os blocos de forma ainda mais eficiente.

A implementação pode incluir verificações para evitar acessos indevidos a blocos não alocados. Além disso, em sistemas multitarefa, mutexes ou semáforos podem ser usados para garantir que múltiplas tarefas acessem o pool de maneira segura. Em casos onde há necessidade de realocação dinâmica, pode-se combinar esse padrão com o Fixed-Sized Buffer Pattern, garantindo que cada bloco tenha um tamanho fixo e previsível.

Padrões Relacionados

O Pool Allocation Pattern está diretamente relacionado a outros padrões de gerenciamento de memória e sincronização:

  • Fixed-Sized Buffer Pattern: Ambos evitam alocação dinâmica e melhoram a previsibilidade do uso da memória.
  • Message Queuing Pattern: Pode ser usado em conjunto para gerenciar filas de mensagens sem consumir memória excessiva.
  • Garbage Compactor Pattern: Pode ser útil quando há necessidade de reorganizar a memória para evitar fragmentação.
  • Critical Section Pattern: Pode ser necessário para evitar acesso concorrente a blocos do pool.

Modelo de Amostragem (Exemplo de Código)

Aqui está um exemplo de implementação do Pool Allocation Pattern em C:

#include <stdio.h>
#include <stdint.h>

#define POOL_SIZE 1000 // Número máximo de objetos TempData

// Estrutura de dados de temperatura
typedef struct {
    int id;
    float temperature;
} TempData;

// Estrutura para o Pool de Recursos
typedef struct {
    TempData pool[POOL_SIZE];  // Array de objetos
    uint8_t allocated[POOL_SIZE];  // Flags para indicar quais objetos estão em uso
} TempDataPool;

// Inicializa o pool de dados
void initPool(TempDataPool* pool) {
    for (int i = 0; i < POOL_SIZE; i++) {
        pool->allocated[i] = 0;  // Nenhum objeto está alocado inicialmente
    }
}

// Aloca um objeto TempData do pool
TempData* allocateTempData(TempDataPool* pool) {
    for (int i = 0; i < POOL_SIZE; i++) {
        if (!pool->allocated[i]) {  // Se não estiver em uso
            pool->allocated[i] = 1; // Marca como alocado
            return &pool->pool[i];
        }
    }
    return NULL; // Pool esgotado
}

// Libera um objeto TempData e o devolve ao pool
void releaseTempData(TempDataPool* pool, TempData* data) {
    int index = data - pool->pool;  // Calcula o índice do objeto
    if (index >= 0 && index < POOL_SIZE) {
        pool->allocated[index] = 0; // Marca como livre
    }
}

// Simulação de sensores e histórico
void simulateUsage() {
    TempDataPool tempPool;
    initPool(&tempPool);

    TempData* temp1 = allocateTempData(&tempPool);
    if (temp1) {
        temp1->id = 1;
        temp1->temperature = 23.5;
        printf("TempData %d alocado: %.2f°C\n", temp1->id, temp1->temperature);
    }

    TempData* temp2 = allocateTempData(&tempPool);
    if (temp2) {
        temp2->id = 2;
        temp2->temperature = 24.0;
        printf("TempData %d alocado: %.2f°C\n", temp2->id, temp2->temperature);
    }

    releaseTempData(&tempPool, temp1);
    printf("TempData %d liberado\n", temp1->id);

    TempData* temp3 = allocateTempData(&tempPool);
    if (temp3) {
        temp3->id = 3;
        temp3->temperature = 22.8;
        printf("TempData %d alocado: %.2f°C\n", temp3->id, temp3->temperature);
    }
}

int main() {
    simulateUsage();
    return 0;
}

Abaixo segue o diagrama Estrutural:

Diagrama Estrutural

A seguir o diagrama de sequência:

0 0 votos
Classificação do artigo
Inscrever-se
Notificar de
guest
0 Comentários
mais antigos
mais recentes Mais votado
Feedbacks embutidos
Ver todos os comentários

Related Post

0
Adoraria saber sua opinião, comente.x