New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Generic memory block allocator #7651
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
some initial comments, and generally: please adapt to RIOT coding style, that is 4 spaces intend (not 2), open braces for function on new line. See wiki.
sys/include/memarray.h
Outdated
#endif | ||
|
||
/** | ||
* @defgroup sys_memarray memory array allocator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please move this comment block up, directly below the copy right statement - just for consistency with RIOT codebase.
sys/include/memarray.h
Outdated
#ifndef MEMARRAY_H | ||
#define MEMARRAY_H | ||
|
||
#include "stdint.h" |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
these standard includes should go into <
and >
sys/include/memarray.h
Outdated
* @brief pseudo dynamic allocation in static memory arrays | ||
* @author Tobias Heider <heidert@nm.ifi.lmu.de> | ||
*/ | ||
typedef struct { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
struct need documentation, plus its elements, i.e.
size_t size; /**< size of memory array */
size_t count; /**< number of ... */
sys/include/memarray.h
Outdated
.first_free = _data_##name, \ | ||
.data = _data_##name}; | ||
|
||
void memarray_init(memarray_t *mem); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add full doxygen style documentation for all functions here and below
How is this different from |
While sth like this useful, I think the implementation has several drawbacks, mostly in the way free blocks are handled. How about this:
That way the actual struct would be down to 4 bytes, and both allocating/deallocating can be done in O(1), also re-using existing code. |
sys/memarray/memarray.c
Outdated
} | ||
} | ||
|
||
uint8_t memarray_free(memarray_t *mem, void *ptr) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This looks like O(n) free(). There must be another way.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
right, i think to achieve O(1) (in the current implementation) it would be necessary to either not check boundaries at all and just assume ptr is in the memory pool, or to check whether ptr >= mem->data
and ptr < mem->data+mem->count*mem->size
which doesn't check whether the pointer is aligned in the pool.
@kaspar030 |
@miri64 TLSF is meant to be a full on malloc replacement, allowing allocations of variable size and thus is way bigger and more complex. This module is meant to solve a simpler problem, where for each pool, the size of blocks as well as the number of blocks in a pool are known at compile time. An example use case would be a network protocol package (like tinydtls), which needs to allocate one structure (holding control information as the peers address etc.) per open connection. Using TLSF would be overkill, as it would require including an external packet which provides a lot of functionality that is not really needed here. |
Since the memarray is a coherent array of blocks, would it be possible to use a bit map of the free blocks, instead of a linked list? I believe this would make free O(1) and malloc O(N) with lower memory overhead than the linked list implementation. |
I've thought about using a bitmap index, i think it is even possible to malloc in O(1) using bitmagic. However I think the allocator list is the least complex solution and does not have notably more overhead than the bitmap in small pools and scales better.
I've thought about what you said and I actually don't see any real advantage of using a clist. The non-clist approach works just as well as the one you described. The only reason |
sys/memarray/memarray.c
Outdated
*/ | ||
|
||
#include "memarray.h" | ||
#include <string.h> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please reverse header sequence
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
I agree, it looks much better now and using clist would complicate things. I'm wondering about the use of |
Indeed, because |
This is introduced by @tobhe in RIOT-OS/RIOT#7651
Small update: |
tests/memarray/README.md
Outdated
At the beginning of the tests is printed the memory being used by the threads. | ||
At the end of the tests, the threads memory usage is printed once more time. | ||
|
||
This test is passed if the memory used by the threads are correct and if the legend |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorry, it's seems that I undid the final sentence by accident. It should be:
This test is passed if the memory used by the main thread remains static.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks, fixed it
tests/memarray/Makefile
Outdated
# Comment this out to disable code in RIOT that does safety checking | ||
# which is not needed in a production environment but helps in the | ||
# development process: | ||
CFLAGS += -DDEVELHELP |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
please remove, not needed since #8080
tests/memarray/README.md
Outdated
Expected result | ||
=============== | ||
|
||
This application should runs a number of tests equal to NUMBER_OF_TESTS (Default 12). |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo /runs/run/ or remove should
tests/memarray/README.md
Outdated
|
||
This application should runs a number of tests equal to NUMBER_OF_TESTS (Default 12). | ||
|
||
At the beginning of the tests is printed the memory being used by the threads. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
wording, consider At the beginning of the tests memory usage of each thread is printed.
tests/memarray/README.md
Outdated
Background | ||
========== | ||
|
||
The module `memarray` is the static memory allocator for RIOT O.S. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
typo: /RIOT O.S./RIOT-OS/
tests/memarray/main.c
Outdated
|
||
#include "memarray.h" | ||
|
||
#define MAX_NUMBER_BLOCKS 10 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
align indention of defines, add parentheses and consider making the unsigned, i.e. like
#define MAX_NUMBER_BLOCKS (10U)
#define MESSAGE_SIZE (8U)
#define NUMBER_OF_TESTS (12U)
tests/memarray/main.c
Outdated
|
||
int main(void) | ||
{ | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
remove empty line
sys/memarray/memarray.c
Outdated
|
||
void memarray_init(memarray_t *mem, size_t size, size_t num) | ||
{ | ||
for (size_t i = 0; i < num - 1; i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
put parentheses: (num - 1)
sys/include/memarray.h
Outdated
} memarray_t; | ||
|
||
/** | ||
* @brief Define static memory pool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
add empty line below
sys/include/memarray.h
Outdated
static memarray_t name = { .first_free = _data_ ## name }; | ||
|
||
/** | ||
* @brief Initialize memarray pool with free list |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
dito, add empty line between @brief
and @param
s, here and below
sys/include/memarray.h
Outdated
/** | ||
* @brief Allocate memory chunk in memarray pool | ||
* @param[in,out] mem memarray pool to allocate block in | ||
* @return pointer to newly allocated memarray chunk |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mhm, I'm unsure if that's correct usage of @return
, bc the function is void
but uses a param as return.
@smlng I think i fixed it all |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I want to converge on the API as its needed for Tinydtls.
The implementation could evolve later in a separate PR as long as the API is stable.
sys/include/memarray.h
Outdated
* @param[in] num number of chunks in pool | ||
*/ | ||
#define MEMARRAY(name, size, num) \ | ||
static uint8_t _data_ ## name[num * size]; \ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why change from structure
to size
here ?
As a memarray
it makes sense to allocate aligned memory element, and here, I am not even sure the array will always be aligned. malloc
returns universally aligned data all the time.
You silently expect size >= sizeof(void *)
, I would just allocate big enough cells all the time.
By using structure
you can create a type with an union to ensure the cell has the right size.
By principle, I think its ok to allocate '8' length items even if requester asks for '5'.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why change from structure to size here ?
I'm not sure why i changed this in the first place. I think i will change it back.
You silently expect size >= sizeof(void *), I would just allocate big enough cells all the time.
By principle, I think its ok to allocate '8' length items even if requester asks for '5'.
agree, thanks for pointing this out
sys/include/memarray.h
Outdated
* @param[in] size size of single memory chunk | ||
* @param[in] num number of chunks in pool | ||
*/ | ||
void memarray_init(memarray_t *mem, size_t num, size_t size); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By removing num/size from memarray_t
, the api now requires repeating them for init, which requires making them public if different modules allocate and init it.
Also, it makes free
unsafe for invalid pointers, it may always be the case on free but few asserts could help debug mistakes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
By removing num/size from memarray_t, the api now requires repeating them for init, which requires making them public if different modules allocate and init it.
@ALL any other opinions on this? otherwise i will just put them back into the memarray_t
Also, it makes free unsafe for invalid pointers, it may always be the case on free but few asserts could help debug mistakes.
i don't know about any free where this is not the case, but we can assert the pointer to be between _data
and _data + (num*size)
, maybe also the alignment
Hm, how about changing the memarray_init() signature back to include the necessary info (memarray_t*, ptr, element size, num of elements)? That way the initialization would look more standard, like:
Or maybe |
I think you're right, this seems to be the better solution. As far as I've seen other modules, e.G. crypto/ciphers do their initialization in a similar way, so it would also add to the consistency of the API as a whole. |
@cladmi @kaspar030 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I am ok with the API and the current code. Sorry to have made you change to removing size and num before.
I have just minor remarks below.
I would like an added assert(size >= sizeof(void *));
in init as its an hidden requirement.
I tested on iotlab-m3
and wsn430-v1_3b
the test works.
There are just some issues when printing on stm32 and wsn430 nodes as %zu
is not implemented. We usually cast it to (unsigned)
and use %u
.
Also for pointers, it prints 0x0x12345
. I will do a PR on your branch for these. Feel free to squash my commits later.
@tobhe ping I'd like to use this in combination with the cn-cbor package :) |
@bergzand As I am back from vaction now and just merged @cladmi's format string fixes I think this might be ready to get merged soon™. I was not planning to make any bigger API changes, and overall everyone seems happy with the current state (or at least no one has raised any concerns). So, feel free to use this, I'm glad there is so much interest in this |
Didn't want to rush you here, thanks for continuing this. :) @kaspar030, @cladmi: Are all your comments addressed? Is this ready to squash? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Some asserts missing (and documentation on them missing)
sys/memarray/memarray.c
Outdated
|
||
void *memarray_alloc(memarray_t *mem) | ||
{ | ||
if (mem->free_data == NULL) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add an assert(mem != NULL);
before this line (and document this with the @pre
command in this function's documentation).
sys/memarray/memarray.c
Outdated
|
||
void memarray_free(memarray_t *mem, void *ptr) | ||
{ | ||
memcpy(ptr, &mem->free_data, sizeof(void *)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Please add an assert((mem != NULL) && (ptr != NULL));
before this line (and document this with the @pre
command in this function's documentation).
sys/memarray/memarray.c
Outdated
|
||
void memarray_init(memarray_t *mem, void *data, size_t size, size_t num) | ||
{ | ||
assert(size >= sizeof(void *)); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mem
and data
should also be checked, if they are not NULL
pointers here ;-). Also please document your assertions with the @pre
command.
@miri64 thanks, i totally forgot the |
tests/memarray/main.c
Outdated
@@ -0,0 +1,117 @@ | |||
/* | |||
* Copyright (C) 2017 Inria |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2018 Tobias Heider heidert@nm.ifi.lmu.de ;-)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That one was initially done by @rfuentess so I don't want to change it without his consent.
I can update the date though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For me is OK to share authorship or given it to @tobhe, after all he had to upgrade the code.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd say we leave it as is, after all you included me in the @author
tag, so I don't see a point in changing the copyright notice. As said before I updated the date to 2018
sys/include/memarray.h
Outdated
@@ -0,0 +1,82 @@ | |||
/* | |||
* Copyright (C) 2017 Tobias Heider <heidert@nm.ifi.lmu.de> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2017-18 ;-)
sys/memarray/memarray.c
Outdated
@@ -0,0 +1,52 @@ | |||
/* | |||
* Copyright (C) 2017 Tobias Heider <heidert@nm.ifi.lmu.de> |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2017-18 ;-)
I'm fine, after the copyright issue is solved. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK
tests/memarray/README.md
Outdated
Background | ||
========== | ||
|
||
The module `memarray` is the static memory allocator for RIOT-OS |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
IMO this needs re-phrasing. What is a "static memory allocator"?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What do you think of "fixed-size blocks allocator"?
Taken from https://en.wikipedia.org/wiki/Memory_management#FIXED-SIZE and I find it clearer than "memory pool allocator"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think "fixed-size block allocator" and "memory pool allocator" both are true, but highlight different properties of the allocator. Also, the fact that it just allocates fixed-size blocks may be the more distinct feature of this implementation, so I'm on your side with this.
mem->size = size; | ||
mem->num = num; | ||
|
||
for (size_t i = 0; i < (mem->num - 1); i++) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This makes mem->num - 1
re-evaluate on every loop iteration.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does it ? has mem->num
is not volatile there is no reason for the compiler to re-read it I think.
When replacing with an intermediate variable I get the same size for samr21-xpro
, wsn430-v1_3b
and native
.
If you have another solution that reduces code size it would be good.
When using (num -1)
in the loop it gets bigger for samr21-xpro
, no idea why.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any function call in between two dereferences usually forces re-read from memory. The called function might have changed what's behind that pointer, after all. But maybe the compiler has outsmarted me (again).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah memcpy
could be doing some stuff to mem->num
by overwriting it if mem
and data
overlap and in that case the compiler should indeed re-read it. Maybe it is re-read but does not cost more.
I found one that reduces code size and also solves the num == 0
case.
But it would print many debug free message at init so maybe not that good.
for (char *cur = data; cur < (char *)data + (num * size); cur += size) {
memarray_free(mem, cur);
}
We can keep the optimizations to another PR this way it is not tied to tobhe.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ACK. Please squash. (feel free to address my last minor comments).
sys/memarray/memarray.c
Outdated
|
||
void memarray_init(memarray_t *mem, void *data, size_t size, size_t num) | ||
{ | ||
assert((mem != NULL) && (data != NULL) && (size >= sizeof(void *))); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In the same part of having assert for the API I also saw that there could be an assert for num != 0
and its @pre
counterpart as its a case that is not handled right now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
True, thanks for the hint
@tobhe Thank you for following this PR all this time. |
🎉 |
@cladmi You're welcome, thanks for all the help |
Nice, this will certainly interest @rfuentess for tinydtls |
🎉 @tobhe congratz! |
This change fixes the invocation of the MEMARRAY macro and memarray_init() to conform with the proposed API in [1]. [1] RIOT-OS/RIOT#7651 Change-Id: Iaede8ac17dfef758e54cd4072d58212c64ca4b08
This PR adds a generic block allocator, allowing "dynamic" allocations of structures of the same size in a static memory pool.
It is meant to replace malloc in cases where only fixed size blocks are allocated and freed (see #7615).
Example usage