Skip to content

Commit 874bc92

Browse files
committed
Optimize ID management for biquad objects linked to table rows
1 parent f590c5d commit 874bc92

File tree

7 files changed

+399
-7
lines changed

7 files changed

+399
-7
lines changed

src/3rdparty/makeid/LICENSE

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
makeid.h was written by Emil Persson and is PUBLIC DOMAIN.
2+
3+
http://www.humus.name/3D/MakeID.h

src/3rdparty/makeid/makeid.h

Lines changed: 365 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,365 @@
1+
#ifndef MAKEID_H
2+
#define MAKEID_H
3+
4+
/*
5+
6+
Author:
7+
Emil Persson, A.K.A. Humus.
8+
http://www.humus.name
9+
10+
Version history:
11+
1.0 - Initial release.
12+
1.01 - Code review fixes. Code reviewed by Denis A. Gladkiy.
13+
1.02 - Fixed an off-by-one error in DestroyRange() found by Markus Billeter
14+
15+
License:
16+
Public Domain
17+
18+
This file is released in the hopes that it will be useful. Use in whatever way you like, but no guarantees that it
19+
actually works or fits any particular purpose. It has been unit-tested and benchmarked though, and seems to do
20+
what it was designed to do, and seems pretty quick at it too.
21+
22+
Notes:
23+
There are many applications where it is desired to generate unique IDs at runtime for various resources, such that they can be
24+
distinguished, sorted or otherwise processed in an efficient manner. It can in some cases replace hashes, handles and pointers.
25+
In cases where resource pointers are used as IDs, it offers a unique ID that requires far fewer bits, especially for 64bit apps.
26+
The design goal of this implementation was to return the most compact IDs as possible, limiting to a specific range if necessary.
27+
28+
The properties of this system are as follows:
29+
- Creating a new ID returns the smallest possible unused ID.
30+
- Creating a new range of IDs returns the smallest possible continuous range of the specified size.
31+
- Created IDs remain valid until destroyed.
32+
- Destroying an ID returns it to the pool and may be returned by subsequent allocations.
33+
- The system is NOT thread-safe.
34+
35+
Performance properties:
36+
- Creating an ID is O(1) and generally super-cheap.
37+
- Destroying an ID is also cheap, but O(log(n)), where n is the current number of distinct available ranges.
38+
- The system merges available ranges when IDs are destroyed, keeping said n generally very small in practice.
39+
- After warmup, no further memory allocations should be necessary, or be very rare.
40+
- The system uses very little memory.
41+
- It is possible to construct a pathological case where fragmentation would cause n to become large. This can be done by
42+
first allocating a very large range of IDs, then deleting every other ID, causing a new range to be allocated for every
43+
free ID, or as many ranges as there are free IDs. I believe nothing close to this situation happens in practical applications.
44+
In tests, millions of random scattered creations and deletions only resulted in a relatively short list in the worst case.
45+
This is because freed IDs are quickly reused and ranges eagerly merged.
46+
47+
Where would this system be useful? It was originally thought up as a replacement for resource pointers as part of sort-ids
48+
in rendering. Using for instance a 64-bit sort-id packing various flags and states, putting a pointer in there takes an
49+
awful lot of bits, especially considering the actual possible resources range in the thousands at most. This got far worse
50+
of course with the switch to 64bit as pointers are now twice as large and essentially eats all bits except bottom few for
51+
alignment.
52+
Another application would be for managing a shared pool of resources. IDs could be handed out as handles and used to access
53+
the actual resource from an array. By always returning the lowest possible ID or range of IDs we get very good cache behavior
54+
since all active resources will grouped together in the bottom part of the array. Using IDs instead of pointers for handles
55+
also allows easy resizing of the allocated memory since IDs can remain the same even if the underlying storage changed.
56+
57+
*/
58+
59+
60+
#include <cstdio> // For printf(). Remove if you don't need the PrintRanges() function (mostly for debugging anyway).
61+
#include <cstdint> // uint32_t
62+
#include <cstdlib>
63+
#include <cstring>
64+
65+
class MakeID
66+
{
67+
private:
68+
// Change to uint16_t here for a more compact implementation if 16bit or less IDs work for you.
69+
typedef uint32_t uint;
70+
71+
struct Range
72+
{
73+
uint m_First;
74+
uint m_Last;
75+
};
76+
77+
Range *m_Ranges; // Sorted array of ranges of free IDs
78+
uint m_Count; // Number of ranges in list
79+
uint m_Capacity; // Total capacity of range list
80+
81+
MakeID & operator=(const MakeID &);
82+
MakeID(const MakeID &);
83+
84+
public:
85+
explicit MakeID(const uint max_id)
86+
{
87+
// Start with a single range, from 0 to max allowed ID (specified)
88+
m_Ranges = static_cast<Range*>(::malloc(sizeof(Range)));
89+
m_Ranges[0].m_First = 0;
90+
m_Ranges[0].m_Last = max_id;
91+
m_Count = 1;
92+
m_Capacity = 1;
93+
}
94+
95+
~MakeID()
96+
{
97+
::free(m_Ranges);
98+
}
99+
100+
101+
bool CreateID(uint &id)
102+
{
103+
if (m_Ranges[0].m_First <= m_Ranges[0].m_Last)
104+
{
105+
id = m_Ranges[0].m_First;
106+
107+
// If current range is full and there is another one, that will become the new current range
108+
if (m_Ranges[0].m_First == m_Ranges[0].m_Last && m_Count > 1)
109+
{
110+
DestroyRange(0);
111+
}
112+
else
113+
{
114+
++m_Ranges[0].m_First;
115+
}
116+
return true;
117+
}
118+
119+
// No availble ID left
120+
return false;
121+
}
122+
123+
bool CreateRangeID(uint &id, const uint count)
124+
{
125+
uint i = 0;
126+
do
127+
{
128+
const uint range_count = 1 + m_Ranges[i].m_Last - m_Ranges[i].m_First;
129+
if (count <= range_count)
130+
{
131+
id = m_Ranges[i].m_First;
132+
133+
// If current range is full and there is another one, that will become the new current range
134+
if (count == range_count && i + 1 < m_Count)
135+
{
136+
DestroyRange(i);
137+
}
138+
else
139+
{
140+
m_Ranges[i].m_First += count;
141+
}
142+
return true;
143+
}
144+
++i;
145+
} while (i < m_Count);
146+
147+
// No range of free IDs was large enough to create the requested continuous ID sequence
148+
return false;
149+
}
150+
151+
bool DestroyID(const uint id)
152+
{
153+
return DestroyRangeID(id, 1);
154+
}
155+
156+
bool DestroyRangeID(const uint id, const uint count)
157+
{
158+
const uint end_id = id + count;
159+
160+
// Binary search of the range list
161+
uint i0 = 0;
162+
uint i1 = m_Count - 1;
163+
164+
for (;;)
165+
{
166+
const uint i = (i0 + i1) / 2;
167+
168+
if (id < m_Ranges[i].m_First)
169+
{
170+
// Before current range, check if neighboring
171+
if (end_id >= m_Ranges[i].m_First)
172+
{
173+
if (end_id != m_Ranges[i].m_First)
174+
return false; // Overlaps a range of free IDs, thus (at least partially) invalid IDs
175+
176+
// Neighbor id, check if neighboring previous range too
177+
if (i > i0 && id - 1 == m_Ranges[i - 1].m_Last)
178+
{
179+
// Merge with previous range
180+
m_Ranges[i - 1].m_Last = m_Ranges[i].m_Last;
181+
DestroyRange(i);
182+
}
183+
else
184+
{
185+
// Just grow range
186+
m_Ranges[i].m_First = id;
187+
}
188+
return true;
189+
}
190+
else
191+
{
192+
// Non-neighbor id
193+
if (i != i0)
194+
{
195+
// Cull upper half of list
196+
i1 = i - 1;
197+
}
198+
else
199+
{
200+
// Found our position in the list, insert the deleted range here
201+
InsertRange(i);
202+
m_Ranges[i].m_First = id;
203+
m_Ranges[i].m_Last = end_id - 1;
204+
return true;
205+
}
206+
}
207+
}
208+
else if (id > m_Ranges[i].m_Last)
209+
{
210+
// After current range, check if neighboring
211+
if (id - 1 == m_Ranges[i].m_Last)
212+
{
213+
// Neighbor id, check if neighboring next range too
214+
if (i < i1 && end_id == m_Ranges[i + 1].m_First)
215+
{
216+
// Merge with next range
217+
m_Ranges[i].m_Last = m_Ranges[i + 1].m_Last;
218+
DestroyRange(i + 1);
219+
}
220+
else
221+
{
222+
// Just grow range
223+
m_Ranges[i].m_Last += count;
224+
}
225+
return true;
226+
}
227+
else
228+
{
229+
// Non-neighbor id
230+
if (i != i1)
231+
{
232+
// Cull bottom half of list
233+
i0 = i + 1;
234+
}
235+
else
236+
{
237+
// Found our position in the list, insert the deleted range here
238+
InsertRange(i + 1);
239+
m_Ranges[i + 1].m_First = id;
240+
m_Ranges[i + 1].m_Last = end_id - 1;
241+
return true;
242+
}
243+
}
244+
}
245+
else
246+
{
247+
// Inside a free block, not a valid ID
248+
return false;
249+
}
250+
251+
}
252+
}
253+
254+
bool IsID(const uint id) const
255+
{
256+
// Binary search of the range list
257+
uint i0 = 0;
258+
uint i1 = m_Count - 1;
259+
260+
for (;;)
261+
{
262+
const uint i = (i0 + i1) / 2;
263+
264+
if (id < m_Ranges[i].m_First)
265+
{
266+
if (i == i0)
267+
return true;
268+
269+
// Cull upper half of list
270+
i1 = i - 1;
271+
}
272+
else if (id > m_Ranges[i].m_Last)
273+
{
274+
if (i == i1)
275+
return true;
276+
277+
// Cull bottom half of list
278+
i0 = i + 1;
279+
}
280+
else
281+
{
282+
// Inside a free block, not a valid ID
283+
return false;
284+
}
285+
286+
}
287+
}
288+
289+
uint GetAvailableIDs() const
290+
{
291+
uint count = m_Count;
292+
uint i = 0;
293+
294+
do
295+
{
296+
count += m_Ranges[i].m_Last - m_Ranges[i].m_First;
297+
++i;
298+
} while (i < m_Count);
299+
300+
return count;
301+
}
302+
303+
uint GetLargestContinuousRange() const
304+
{
305+
uint max_count = 0;
306+
uint i = 0;
307+
308+
do
309+
{
310+
uint count = m_Ranges[i].m_Last - m_Ranges[i].m_First + 1;
311+
if (count > max_count)
312+
max_count = count;
313+
314+
++i;
315+
} while (i < m_Count);
316+
317+
return max_count;
318+
}
319+
320+
void PrintRanges() const
321+
{
322+
uint i = 0;
323+
for (;;)
324+
{
325+
if (m_Ranges[i].m_First < m_Ranges[i].m_Last)
326+
printf("%u-%u", m_Ranges[i].m_First, m_Ranges[i].m_Last);
327+
else if (m_Ranges[i].m_First == m_Ranges[i].m_Last)
328+
printf("%u", m_Ranges[i].m_First);
329+
else
330+
printf("-");
331+
332+
++i;
333+
if (i >= m_Count)
334+
{
335+
printf("\n");
336+
return;
337+
}
338+
339+
printf(", ");
340+
}
341+
}
342+
343+
344+
private:
345+
346+
void InsertRange(const uint index)
347+
{
348+
if (m_Count >= m_Capacity)
349+
{
350+
m_Capacity += m_Capacity;
351+
m_Ranges = (Range *) realloc(m_Ranges, m_Capacity * sizeof(Range));
352+
}
353+
354+
::memmove(m_Ranges + index + 1, m_Ranges + index, (m_Count - index) * sizeof(Range));
355+
++m_Count;
356+
}
357+
358+
void DestroyRange(const uint index)
359+
{
360+
--m_Count;
361+
::memmove(m_Ranges + index, m_Ranges + index + 1, (m_Count - index) * sizeof(Range));
362+
}
363+
};
364+
365+
#endif // MAKEID_H

src/3rdparty/makeid/makeid.pri

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
INCLUDEPATH += $$PWD
2+
3+
HEADERS += $$PWD/makeid.h

src/DDCToolbox-lib.pri

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ INCLUDEPATH += $$PWD
9595
include ($$PWD/3rdparty/WebLoader/WebLoader.pri)
9696
include ($$PWD/3rdparty/QSimpleUpdater/QSimpleUpdater.pri)
9797
include ($$PWD/3rdparty/libgradfreeOpt.pri)
98+
include ($$PWD/3rdparty/makeid/makeid.pri)
9899

99100
RESOURCES += \
100101
$$PWD/../res/ddceditor_resources.qrc

src/VdcEditorWindow.cpp

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,9 @@ VdcEditorWindow::VdcEditorWindow(QWidget *parent) :
6868
createUpdater();
6969

7070
connect(&VdcProjectManager::instance(), &VdcProjectManager::projectClosed, undoStack, &QUndoStack::clear);
71+
connect(&VdcProjectManager::instance(), &VdcProjectManager::projectClosed, undoStack, []{
72+
Biquad::ResetIds();
73+
});
7174
connect(&VdcProjectManager::instance(), &VdcProjectManager::projectMetaChanged, [this]{
7275
QString title = qApp->applicationName();
7376

0 commit comments

Comments
 (0)