Skocz do zawartości

Memory pool, dynamiczna alokacja i fragmentacja


Gieneq

Pomocna odpowiedź

Zastanawiam się na ile dużym problemem jest używanie dynamicznej alokacji do np. std::string, std::vector w embedded. Podobno fragmentacja może namieszać ale w jeszcze nie widzę jej skutków. Zamierzam zrobić coś z profilerem ale na to przyjdzie czas. Jednym ze sposobów poradzenia sobie z tym jest memory pool ale nie mam z tym większego doświadczenia. Np w ThreadX do założenia wątku, albo kolejki potrzeba przydzielić ręcznie pamięć, tu statycznie:

#if (USE_STATIC_ALLOCATION == 1)
  UINT status = TX_SUCCESS;
  VOID *memory_ptr;

  if (tx_byte_pool_create(&tx_app_byte_pool, "Tx App memory pool", tx_byte_pool_buffer, TX_APP_MEM_POOL_SIZE) != TX_SUCCESS)
  {
    /* USER CODE BEGIN TX_Byte_Pool_Error */
	  Error_Handler();
    /* USER CODE END TX_Byte_Pool_Error */
  }
  else
  {
    /* USER CODE BEGIN TX_Byte_Pool_Success */

    /* USER CODE END TX_Byte_Pool_Success */

    memory_ptr = (VOID *)&tx_app_byte_pool;
    status = App_ThreadX_Init(memory_ptr);
    if (status != TX_SUCCESS)
    {
      /* USER CODE BEGIN  App_ThreadX_Init_Error */
    	Error_Handler();

      /* USER CODE END  App_ThreadX_Init_Error */
    }

    /* USER CODE BEGIN  App_ThreadX_Init_Success */

    /* USER CODE END  App_ThreadX_Init_Success */

  }

A jak to może wyglądać z alokacją wspomnianych stringów czy wektorów? W mojej aplikacji przetwarzam stringi JSON i dynamiczne struktury są niezbędne. Nie jestem też w stanie określić jak duże sądane wejściowe więc statyczna alokacja bufora może się nie udać.

Link do komentarza
Share on other sites

43 minuty temu, Gieneq napisał:

Zastanawiam się na ile dużym problemem jest używanie dynamicznej alokacji do np. std::string, std::vector w embedded.

O tym to już chyba milion razy pisali... co prawda w kontekście malloc/realloc/free, ale w sumie to właśnie siedzi w string i vector.

44 minuty temu, Gieneq napisał:

Podobno fragmentacja może namieszać ale w jeszcze nie widzę jej skutków.

Wspominałem o tym przy okazji "czytaka" (o różnicy między ilością wolnej pamięci a najdłuższym wolnym fragmentem, czyli różnicy między "zmieści się" a "nie zmieści się").

45 minut temu, Gieneq napisał:

W mojej aplikacji przetwarzam stringi JSON i dynamiczne struktury są niezbędne.

To znaczy parsujesz te stringi czy je tworzysz? Bo ZTCW ani tu, ani tu nie jest potrzebna żadna dynamiczna alokacja...

Przykładowy parser który nie używa dynamicznej alokacji: https://github.com/zserge/jsmn

A w ogóle napisanie parsera JSON to takie przyjemne zajęcie na popołudnie przy kawie... czegoś podobnego do XML-owego SAX (kiedyś coś podobnego pisałem ale chyba kod gdzieś przepadł, a szkoda).

O stringify czy jakimś odpowiedniku to już w ogóle nie warto wspominać, bo tam dynamiczna alokacja w ogóle nie jest potrzebna.

 

 

 

Link do komentarza
Share on other sites

Cześć @ethanak

Dzięki za odpowiedź. Tak wiem że dużo już o tym było, ale gdy mikrokontroler ma dość dużo zasobów to wydaje się że można sobie na więcej pozwolić. Temat największego bloku też znam.

Do parsowania używam cJSON, która jest częścią ESP-IDF. Spodobała mi się ta biblioteka i używam też w ThreadX. Funkcje do zarządzania pamięcią wyglądają tak:

CJSON_PUBLIC(void *) cJSON_malloc(size_t size){
    return global_hooks.allocate(size);
}

CJSON_PUBLIC(void) cJSON_free(void *object){
    global_hooks.deallocate(object);
}

.....

CJSON_PUBLIC(void) cJSON_InitHooks(cJSON_Hooks* hooks){
    if (hooks == NULL){
        /* Reset hooks */
        global_hooks.allocate = malloc;
        global_hooks.deallocate = free;
        global_hooks.reallocate = realloc;
        return;
    }

    global_hooks.allocate = malloc;
    if (hooks->malloc_fn != NULL)
    {
        global_hooks.allocate = hooks->malloc_fn;
    }

    global_hooks.deallocate = free;
    if (hooks->free_fn != NULL)
    {
        global_hooks.deallocate = hooks->free_fn;
    }

    /* use realloc only if both free and malloc are used */
    global_hooks.reallocate = NULL;
    if ((global_hooks.allocate == malloc) && (global_hooks.deallocate == free))
    {
        global_hooks.reallocate = realloc;
    }
}

Parsowanie sprowadza się do tej funkcji która przetwarza cały cstring i wyrzuca drzewo węzłów na stercie, węzły z napisami są alokowane jako nowe cstringi:

CJSON_PUBLIC(cJSON *) cJSON_ParseWithLengthOpts(const char *value, size_t buffer_length, const char **return_parse_end, cJSON_bool require_null_terminated)

Do zebrania danych z UARTu podchodziłem na 2 sposoby:

1. statyczny bufor char i interfejs funkcji do obsługi ringbufera w C++:

#pragma once
#include <array>
#include <string>

class RingBuffer {
public:
	RingBuffer();
	~RingBuffer();
	void clear();
	void push(char ch);
	bool has_string();
	size_t get_head_index();
	std::string get_head_string();
	uint8_t at(size_t index);
	static constexpr size_t BUFFER_SIZE = 512;
private:
	std::array<uint8_t, BUFFER_SIZE> data;
	size_t head_index{0};
	int string_start_index{-1};
	int string_end_index{-1};
};

2. dynamiczny bufor w C z realokacją przy przepełnieniu:

typedef enum buffer_err_t {
	BUFFER_OK, BUFFER_EMPTY, BUFFER_BAD_ALLOC, BUFFER_MISSING_VALUE, BUFFER_FULL,
} buffer_err_t;

typedef enum buffer_type_t {
	BUFF_ALLOC_STATIC, BUFF_ALLOC_DYNAMIC, BUFF_ALLOC_DYNAMIC_GROWABLE
} buffer_type_t;

typedef struct buffer_t {
	size_t free_index;
	size_t capacity;
	uint32_t type;
	char *memory;
} buffer_t;

typedef buffer_t *buffer_handler_t;

buffer_err_t buffer_create_static(buffer_handler_t *buffer, char *src,
		const size_t initial_size);

buffer_err_t buffer_create_dynamic(buffer_handler_t *buffer,
		const size_t initial_size, buffer_type_t type);
void buffer_clear(const buffer_handler_t buffer);
int buffer_to_int(const buffer_handler_t buffer);

buffer_err_t buffer_push(const buffer_handler_t buffer, const char c);
buffer_err_t buffer_destroy(const buffer_handler_t buffer);
size_t buffer_get_elements_count(const buffer_handler_t buffer);
void buffer_print_bytes(const buffer_handler_t buffer, const size_t max_columns);
int buffer_is_full(const buffer_handler_t buffer);

funkcja push:

buffer_err_t buffer_push(const buffer_handler_t buffer, const char c) {

	/* Not enough free space */
	if (buffer->free_index >= buffer->capacity) {
		/* Cannot resize */
		if (buffer->type != BUFF_ALLOC_DYNAMIC_GROWABLE) {
			ESP_LOGW(TAG, "Buffer full, data can be lost");
			return BUFFER_FULL;
		}
		/* Resize */
		else {
			size_t new_capacity = buffer->capacity * 2;
			if ((buffer->memory = realloc(buffer->memory, new_capacity)) == NULL)
				return BUFFER_BAD_ALLOC;
			buffer->capacity = new_capacity;
		}
	}

	/* Enough free space */
	buffer->memory[buffer->free_index++] = c;

	return BUFFER_OK;
}

 

Jak teraz na to patrzę to ringbuffer wydaje się bardziej sensowny - w najgorszy przypadku stracę dane, ale mogę o tym fakcie poinformować mastera żeby jakoś wysłał informacje w częściach. Lepiej założyć scenariusz, że przyjdzie najdłuższy możliwy string niż realokować gdy się pojawi.

 

  • Lubię! 1
Link do komentarza
Share on other sites

Dobra a inne pytanie, które pewnie było wałkowane miliony razy. Czy da się bezpiecznie używać stringów na stosie? Np. robię funkcję która zamiast sprintf korzysta z konkatanacji stringów, wysyła do api cstring z .c_str() i niszczy dane. Chcę używać std::string bo jest wygodny.

Czy jest jakaś opcja żebym podał że nawet niech ten string zajmuje 10x więcej pamięci ale na stosie? Mogę podać w argumencie rozmiar danych, ale wtedy bym musiał jakoś je nadpisywać co nie? Może sstream?

Link do komentarza
Share on other sites

Zarejestruj się lub zaloguj, aby ukryć tę reklamę.
Zarejestruj się lub zaloguj, aby ukryć tę reklamę.

jlcpcb.jpg

jlcpcb.jpg

Produkcja i montaż PCB - wybierz sprawdzone PCBWay!
   • Darmowe płytki dla studentów i projektów non-profit
   • Tylko 5$ za 10 prototypów PCB w 24 godziny
   • Usługa projektowania PCB na zlecenie
   • Montaż PCB od 30$ + bezpłatna dostawa i szablony
   • Darmowe narzędzie do podglądu plików Gerber
Zobacz również » Film z fabryki PCBWay

Dołącz do dyskusji, napisz odpowiedź!

Jeśli masz już konto to zaloguj się teraz, aby opublikować wiadomość jako Ty. Możesz też napisać teraz i zarejestrować się później.
Uwaga: wgrywanie zdjęć i załączników dostępne jest po zalogowaniu!

Anonim
Dołącz do dyskusji! Kliknij i zacznij pisać...

×   Wklejony jako tekst z formatowaniem.   Przywróć formatowanie

  Dozwolonych jest tylko 75 emoji.

×   Twój link będzie automatycznie osadzony.   Wyświetlać jako link

×   Twoja poprzednia zawartość została przywrócona.   Wyczyść edytor

×   Nie możesz wkleić zdjęć bezpośrednio. Prześlij lub wstaw obrazy z adresu URL.

×
×
  • Utwórz nowe...

Ważne informacje

Ta strona używa ciasteczek (cookies), dzięki którym może działać lepiej. Więcej na ten temat znajdziesz w Polityce Prywatności.