Skip to content

Commit a508bf9

Browse files
committed
Fixed allocator initialization order error. Updated docs.
1 parent 87b7e5a commit a508bf9

File tree

3 files changed

+80
-49
lines changed

3 files changed

+80
-49
lines changed

README.md

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -50,18 +50,7 @@ arr_cmplx y6 = x1 * 2i;
5050
### Slicing
5151
The behavior of slices is as close as possible to numpy. Except for cases with invalid indexes, in which case numpy does not throw an exception.
5252
```cpp
53-
arr_real x = {0, 1, 2, 3, 4, 5, 6};
54-
x.slice(0, 2) ///{0, 1}
55-
x.slice(2, -1) ///{2, 3, 4, 5}
56-
x.slice(-1, 0, -1) ///{6, 5, 4, 3, 2, 1}
57-
x.slice(-1, 0) ///OUT_OF_RANGE, but numpy returns []
58-
x.slice(0, -1, -1) ///OUT_OF_RANGE, but numpy returns []
59-
x.slice(-8, 7) ///OUT_OF_RANGE, but numpy returns [0 1 2 3 4 5 6]
60-
```
61-
62-
### Fast Fourier Transform:
63-
The FFT/IFFT calculation table is cached on first run. To eliminate this behavior, you can use the fft_plan object.
64-
```cpp
53+
using namespace dsplib;
6554
arr_real x = randn(512);
6655
arr_cmplx y = fft(x);
6756
```
@@ -102,7 +91,7 @@ arr_real x2 = awgn(x1, 10);
10291
arr_real y = xcorr(x1, x2);
10392
```
10493

105-
### Spectrum Analyze (16-bit scale):
94+
Simple Spectrum Analyze (16-bit scale):
10695
```cpp
10796
int nfft = 1024;
10897
arr_real x = randn(nfft) * 1000;
@@ -112,3 +101,7 @@ arr_cmplx y = fft(x) / (nfft / 2);
112101
arr_real z = abs(y);
113102
z = log10(z / 0x7FFF) * 20;
114103
```
104+
105+
## Allocation
106+
107+
On some platforms (for example, Android) there may be performance issues due to slow memory allocation for the vector. In this case, try enabling a custom allocator via the DSPLIB_POOL_ALLOCATOR option and estimate its peak consumption using the dsplib::pool_state function.

include/dsplib/allocator.h

Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,38 @@
22

33
#include <vector>
44
#include <memory>
5+
#include <cassert>
56

67
namespace dsplib {
78

9+
// Implementation of an allocator as a pool of memory blocks.
10+
// Used to allocate memory for a vector on platforms where frequent malloc() calls are critical.
11+
// For each memory request, a free block is checked.
12+
// If there are no free blocks, then memory is allocated using malloc(), the pointer is placed in the pool
13+
// and returned as a temporary object.
14+
// When the memory allocation limit is exceeded, the allocation will be performed in the standard way.
15+
// Block sizes are multiples of 512 bytes, i.e. 512, 1024, 1536 ... MAX_POOLED_SIZE
16+
// There is also a separate pool of 64 bytes for small objects.
17+
// Alignment of the real_t/cmplx_t pointer is guaranteed by the malloc implementation.
18+
819
template<class T>
920
inline void unused(T&&) {
1021
}
1122

12-
//init pool table
23+
// Init pool table
24+
// Prepare a pool of objects if the block sizes are known in advance.
25+
// For evaluation, you can use function pool_info() after running your algorithm.
1326
void pool_init(std::vector<int> map);
1427

15-
//release all free memory blocks
28+
// Release all free memory blocks
1629
void pool_reset();
1730

18-
//allocate memory block from pool
31+
// Allocate memory block from pool
1932
void* pool_alloc(size_t size);
2033

21-
//free (return) memory block to pool
34+
// Free (return) memory block to pool
2235
void pool_free(void* ptr);
2336

24-
//current pool state
25-
//TODO: allocated/free
2637
struct pool_info_t
2738
{
2839
pool_info_t(bool used, int size)
@@ -33,6 +44,7 @@ struct pool_info_t
3344
int size{0};
3445
};
3546

47+
// Current pool state
3648
std::vector<pool_info_t> pool_info();
3749

3850
template<typename T>
@@ -49,7 +61,10 @@ class pool_allocator : public std::allocator<T>
4961

5062
pointer allocate(size_type n, const void* hint = 0) {
5163
unused(hint);
52-
return reinterpret_cast<pointer>(pool_alloc(n * sizeof(T)));
64+
auto ptr = pool_alloc(n * sizeof(T));
65+
auto r_ptr = reinterpret_cast<pointer>(ptr);
66+
assert(uintptr_t(ptr) == uintptr_t(r_ptr));
67+
return r_ptr;
5368
}
5469

5570
void deallocate(pointer p, size_type n) {

lib/allocator.cpp

Lines changed: 52 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,11 @@
22

33
#include <memory>
44
#include <vector>
5-
#include <cassert>
65
#include <unordered_map>
76
#include <stdexcept>
87

98
namespace dsplib {
109

11-
//----------------------------------------------------------------------------------------
12-
constexpr int MAX_POOLED_SIZE = (1L << 15);
13-
constexpr int SMALL_BLOCK_SIZE = 64;
14-
constexpr int BLOCK_SIZE = 512;
15-
constexpr int STORAGE_COUNT = MAX_POOLED_SIZE / BLOCK_SIZE + 1;
16-
constexpr size_t MEMORY_ALLOCATION_LIMIT = 1024 * 1024; //1 MB (TODO: set from options)
17-
constexpr int NOT_POOLED_KEY = -1;
18-
1910
//----------------------------------------------------------------------------------------
2011
class storage_t
2112
{
@@ -39,10 +30,31 @@ class storage_t
3930
};
4031

4132
//----------------------------------------------------------------------------------------
42-
static thread_local std::vector<storage_t> _storage(STORAGE_COUNT);
43-
static thread_local size_t _bytes_allocated{0};
44-
static thread_local std::unordered_map<void*, int> _alocate_map;
33+
//TODO: Move constants MAX_POOLED_SIZE, SMALL_BLOCK_SIZE, BLOCK_SIZE, MEMORY_ALLOCATION_LIMIT to config
34+
constexpr int MAX_POOLED_SIZE = (1L << 15);
35+
constexpr int SMALL_BLOCK_SIZE = 64;
36+
constexpr int BLOCK_SIZE = 512;
37+
constexpr int STORAGE_COUNT = MAX_POOLED_SIZE / BLOCK_SIZE + 1;
38+
constexpr size_t MEMORY_ALLOCATION_LIMIT = 1024 * 1024; //1 MB (TODO: set from options)
39+
constexpr int NOT_POOLED_KEY = -1;
40+
41+
//----------------------------------------------------------------------------------------
42+
static std::vector<storage_t>& get_storage() {
43+
static std::vector<storage_t> _storage(STORAGE_COUNT);
44+
return _storage;
45+
}
4546

47+
static std::unordered_map<void*, int>& get_allocmap() {
48+
static std::unordered_map<void*, int> _alocate_map{};
49+
return _alocate_map;
50+
}
51+
52+
static size_t& allocated() {
53+
static size_t _bytes_allocated{0};
54+
return _bytes_allocated;
55+
}
56+
57+
//----------------------------------------------------------------------------------------
4658
static int key_by_size(int size) {
4759
assert(size <= MAX_POOLED_SIZE);
4860
if (size <= SMALL_BLOCK_SIZE) {
@@ -67,7 +79,7 @@ void pool_init(std::vector<int> map) {
6779
bytes_needed += pool_size;
6880
}
6981

70-
if ((bytes_needed + _bytes_allocated) > MEMORY_ALLOCATION_LIMIT) {
82+
if ((bytes_needed + allocated()) > MEMORY_ALLOCATION_LIMIT) {
7183
throw std::runtime_error("Limit is exceeded. Memory has not been allocated.");
7284
}
7385

@@ -80,66 +92,77 @@ void pool_init(std::vector<int> map) {
8092

8193
//----------------------------------------------------------------------------------------
8294
void pool_reset() {
83-
for (int key = 0; key < _storage.size(); ++key) {
84-
auto& pool = _storage[key];
95+
auto& storage = get_storage();
96+
for (int key = 0; key < storage.size(); ++key) {
97+
auto& pool = storage[key];
8598
while (pool.size() > 0) {
8699
auto ptr = pool.pop();
87100
free(ptr);
88-
_bytes_allocated -= size_by_key(key);
101+
allocated() -= size_by_key(key);
89102
}
90103
}
91104
}
92105

93106
//----------------------------------------------------------------------------------------
94107
void* pool_alloc(size_t size) {
108+
auto& storage = get_storage();
109+
auto& allocmap = get_allocmap();
110+
95111
if (size > MAX_POOLED_SIZE) {
96112
auto ptr = malloc(size);
97-
_alocate_map[ptr] = NOT_POOLED_KEY;
113+
allocmap[ptr] = NOT_POOLED_KEY;
98114
return ptr;
99115
}
100116

101117
const int key = key_by_size(size);
102-
auto& pool = _storage[key];
118+
auto& pool = storage[key];
103119
const int cap = size_by_key(key);
104120

105121
//TODO: get first free object from pool (not equal size)
106-
if ((pool.size() == 0) && (_bytes_allocated > MEMORY_ALLOCATION_LIMIT)) {
122+
if ((pool.size() == 0) && (allocated() > MEMORY_ALLOCATION_LIMIT)) {
107123
auto ptr = malloc(size);
108-
_alocate_map[ptr] = NOT_POOLED_KEY;
124+
allocmap[ptr] = NOT_POOLED_KEY;
109125
return ptr;
110126
}
111127

112128
//add block to pool
113129
if (pool.size() == 0) {
114130
auto ptr = malloc(cap);
115131
pool.push(ptr);
116-
_bytes_allocated += cap;
132+
allocated() += cap;
117133
}
118134

119135
auto ptr = pool.pop();
120-
_alocate_map[ptr] = key;
136+
allocmap[ptr] = key;
121137
return ptr;
122138
}
123139

124140
//----------------------------------------------------------------------------------------
125141
void pool_free(void* ptr) {
126-
assert(_alocate_map.count(ptr) == 1);
127-
auto key = _alocate_map.at(ptr);
128-
_alocate_map.erase(ptr);
142+
auto& storage = get_storage();
143+
auto& allocmap = get_allocmap();
144+
145+
assert(allocmap.count(ptr) == 1);
146+
147+
auto key = allocmap.at(ptr);
148+
allocmap.erase(ptr);
129149
if (key == NOT_POOLED_KEY) {
130150
free(ptr);
131151
} else {
132-
auto& pool = _storage[key];
152+
auto& pool = storage[key];
133153
pool.push(ptr);
134154
}
135155
}
136156

137157
std::vector<pool_info_t> pool_info() {
158+
auto& storage = get_storage();
159+
auto& allocmap = get_allocmap();
160+
138161
std::vector<pool_info_t> out;
139162

140163
//free blocks
141-
for (int k = 0; k < _storage.size(); ++k) {
142-
auto& pool = _storage[k];
164+
for (int k = 0; k < storage.size(); ++k) {
165+
auto& pool = storage[k];
143166
for (int i = 0; i < pool.size(); ++i) {
144167
auto pool_size = size_by_key(k);
145168
pool_info_t info = {false, pool_size};
@@ -148,7 +171,7 @@ std::vector<pool_info_t> pool_info() {
148171
}
149172

150173
//used blocks
151-
for (auto it : _alocate_map) {
174+
for (auto it : allocmap) {
152175
auto key = it.second;
153176
auto pool_size = size_by_key(key);
154177
pool_info_t info = {true, pool_size};

0 commit comments

Comments
 (0)