Skip to content

Commit 25ef7b1

Browse files
Merge pull request #1231 from vsg-dev/InstrusiveAllocator_MemoryPools
Replaced original block vsg::Allocator with vsg::InstrusiveAllocator that is ~6 x faster at allocation/deallocation
2 parents d05479e + cc10773 commit 25ef7b1

18 files changed

+1350
-525
lines changed

CMakeLists.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
cmake_minimum_required(VERSION 3.7)
22

33
project(vsg
4-
VERSION 1.1.5
4+
VERSION 1.1.6
55
DESCRIPTION "VulkanSceneGraph library"
66
LANGUAGES CXX
77
)

cmake/cppcheck-suppression-list.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ useStlAlgorithm:*/src/vsg/vk/Instance.cpp
4848
useStlAlgorithm:*/src/vsg/vk/RenderPass.cpp
4949
useStlAlgorithm:*/src/vsg/core/Allocator.cpp
5050
useStlAlgorithm:*/src/vsg/core/MemorySlots.cpp
51+
useStlAlgorithm:*/src/vsg/core/IntrusiveAllocator.cpp
5152
useStlAlgorithm:*/src/vsg/state/ArrayState.cpp
5253
useStlAlgorithm:*/src/vsg/app/CompileTraversal.cpp
5354
useStlAlgorithm:*/src/vsg/utils/ShaderSet.cpp
@@ -74,6 +75,7 @@ syntaxError:*include/vsg/core/Data.h:51
7475
unusedStructMember:*include/vsg/core/Data.h
7576
unusedStructMember:*include/vsg/core/Exception.h
7677
unusedStructMember:*include/vsg/core/Version.h
78+
unusedStructMember:*include/vsg/core/IntrusiveAllocator.h
7779
unusedStructMember:*include/vsg/io/ObjectCache.h
7880
unusedStructMember:*include/vsg/nodes/Bin.h
7981
unusedStructMember:*include/vsg/nodes/LOD.h
@@ -145,6 +147,8 @@ returnTempReference:*/include/vsg/core/Inherit.h
145147
variableScope:*/include/vsg/utils/SharedObjects.h
146148
variableScope:*/src/vsg/utils/SharedObjects.cpp
147149
variableScope:*/src/vsg/app/CompileManager.cpp
150+
variableScope:*/src/vsg/core/IntrusiveAllocator.cpp
148151

149152
// suppress really stupid warning of pointerLessThanZero
150153
pointerLessThanZero:*/src/vsg/app/Viewer.cpp
154+

include/vsg/all.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
2424
#include <vsg/core/Export.h>
2525
#include <vsg/core/External.h>
2626
#include <vsg/core/Inherit.h>
27+
#include <vsg/core/IntrusiveAllocator.h>
2728
#include <vsg/core/Mask.h>
2829
#include <vsg/core/MemorySlots.h>
2930
#include <vsg/core/Object.h>

include/vsg/core/Allocator.h

Lines changed: 17 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,11 @@ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLI
1212
1313
</editor-fold> */
1414

15-
#include <vsg/core/MemorySlots.h>
15+
#include <vsg/core/Export.h>
1616

17+
#include <map>
1718
#include <memory>
1819
#include <mutex>
19-
#include <vector>
2020

2121
namespace vsg
2222
{
@@ -41,94 +41,46 @@ namespace vsg
4141
class VSG_DECLSPEC Allocator
4242
{
4343
public:
44-
explicit Allocator(size_t in_default_alignment = 4);
45-
explicit Allocator(std::unique_ptr<Allocator> in_nestedAllocator, size_t in_default_alignment = 4);
46-
47-
virtual ~Allocator();
44+
explicit Allocator(size_t in_defaultAlignment = 4) :
45+
defaultAlignment(in_defaultAlignment) {}
46+
explicit Allocator(std::unique_ptr<Allocator> in_nestedAllocator, size_t in_defaultAlignment = 4) :
47+
defaultAlignment(in_defaultAlignment), nestedAllocator(std::move(in_nestedAllocator)) {}
48+
virtual ~Allocator() {}
4849

4950
/// Allocator singleton
5051
static std::unique_ptr<Allocator>& instance();
5152

5253
/// allocate from the pool of memory blocks, or allocate from a new memory block
53-
virtual void* allocate(std::size_t size, AllocatorAffinity allocatorAffinity = ALLOCATOR_AFFINITY_OBJECTS);
54+
virtual void* allocate(std::size_t size, AllocatorAffinity allocatorAffinity = ALLOCATOR_AFFINITY_OBJECTS) = 0;
5455

5556
/// deallocate, returning data to pool.
56-
virtual bool deallocate(void* ptr, std::size_t size);
57+
virtual bool deallocate(void* ptr, std::size_t size) = 0;
5758

5859
/// delete any MemoryBlock that are empty
59-
virtual size_t deleteEmptyMemoryBlocks();
60+
virtual size_t deleteEmptyMemoryBlocks() = 0;
6061

6162
/// return the total available size of allocated MemoryBlocks
62-
virtual size_t totalAvailableSize() const;
63+
virtual size_t totalAvailableSize() const = 0;
6364

6465
/// return the total reserved size of allocated MemoryBlocks
65-
virtual size_t totalReservedSize() const;
66+
virtual size_t totalReservedSize() const = 0;
6667

6768
/// return the total memory size of allocated MemoryBlocks
68-
virtual size_t totalMemorySize() const;
69-
70-
/// report stats about blocks of memory allocated.
71-
virtual void report(std::ostream& out) const;
69+
virtual size_t totalMemorySize() const = 0;
7270

7371
AllocatorType allocatorType = ALLOCATOR_TYPE_VSG_ALLOCATOR; // use MemoryBlocks by default
74-
int memoryTracking = MEMORY_TRACKING_DEFAULT;
7572

76-
/// set the MemoryTracking member of the vsg::Allocator and all the MemoryBlocks that it manages.
77-
void setMemoryTracking(int mt);
78-
79-
struct MemoryBlock
80-
{
81-
MemoryBlock(size_t blockSize, int memoryTracking, size_t in_alignment);
82-
virtual ~MemoryBlock();
73+
virtual void setBlockSize(AllocatorAffinity allocatorAffinity, size_t blockSize) = 0;
8374

84-
void* allocate(std::size_t size);
85-
bool deallocate(void* ptr, std::size_t size);
86-
87-
vsg::MemorySlots memorySlots;
88-
size_t alignment = 4;
89-
size_t block_alignment = 16;
90-
uint8_t* memory = nullptr;
91-
};
92-
93-
struct MemoryBlocks
94-
{
95-
Allocator* parent = nullptr;
96-
std::string name;
97-
size_t blockSize = 0;
98-
size_t alignment = 4;
99-
std::map<void*, std::shared_ptr<MemoryBlock>> memoryBlocks;
100-
std::shared_ptr<MemoryBlock> latestMemoryBlock;
101-
102-
MemoryBlocks(Allocator* in_parent, const std::string& in_name, size_t in_blockSize, size_t in_alignment);
103-
virtual ~MemoryBlocks();
104-
105-
void* allocate(std::size_t size);
106-
bool deallocate(void* ptr, std::size_t size);
107-
108-
size_t deleteEmptyMemoryBlocks();
109-
size_t totalAvailableSize() const;
110-
size_t totalReservedSize() const;
111-
size_t totalMemorySize() const;
112-
};
113-
114-
MemoryBlocks* getMemoryBlocks(AllocatorAffinity allocatorAffinity);
115-
116-
MemoryBlocks* getOrCreateMemoryBlocks(AllocatorAffinity allocatorAffinity, const std::string& name, size_t blockSize, size_t in_alignment = 4);
117-
118-
void setBlockSize(AllocatorAffinity allocatorAffinity, size_t blockSize);
75+
/// report stats about blocks of memory allocated.
76+
virtual void report(std::ostream& out) const = 0;
11977

12078
mutable std::mutex mutex;
121-
122-
size_t default_alignment = 4;
123-
124-
double allocationTime = 0.0;
125-
double deallocationTime = 0.0;
79+
size_t defaultAlignment = 4;
12680

12781
protected:
12882
// if you are assigning a custom allocator you must retain the old allocator to manage the memory it allocated and needs to delete
12983
std::unique_ptr<Allocator> nestedAllocator;
130-
131-
std::vector<std::unique_ptr<MemoryBlocks>> allocatorMemoryBlocks;
13284
};
13385

13486
/// allocate memory using vsg::Allocator::instance() if available, otherwise use std::malloc(size)
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
#pragma once
2+
3+
/* <editor-fold desc="MIT License">
4+
5+
Copyright(c) 2024 Robert Osfield
6+
7+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
8+
9+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
10+
11+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
12+
13+
</editor-fold> */
14+
15+
#include <vsg/core/Allocator.h>
16+
17+
#include <list>
18+
#include <vector>
19+
20+
namespace vsg
21+
{
22+
////////////////////////////////////////////////////////////////////////////////////////////////////
23+
//
24+
// InstrusiveAllocator is the default Allocator implenentation
25+
//
26+
// Memory is allocated for fixed sized blocks, with indexing of allocated and available slots of memory
27+
// are stored within the same memory block that user memory allocation are made from. The memory block
28+
// is created a contiguous block of 4 bytes Elements, where the Element is a union of bitfield linked list
29+
// market the beginning of the previous slot or the begging of the next, the status of whether the slot is
30+
// allocated or available, or an index when used as part of doubling linked list of free slots.
31+
//
32+
// The block allocation is done based on the type of object so all nodes, data or general objects are
33+
// allocated within the blocks containing objects of similar type. This form of block allocation helps
34+
// scene graph traversal speeds by improving cache coherency/reducing cache missing as it ensures that
35+
// nodes etc. are packed in adjacent memory.
36+
//
37+
// The instrusive indexing means there is only a 4 byte panalty for each memory allocation, and a minimum
38+
// memory use per allocation of 12 bytes (3 Elements - 1 for the slot{previous, next, status} and 2 for the
39+
// previous and next free list indices.
40+
//
41+
// The maximum size of allocations within the block allocation is (2^15-2) * 4, allocations larger than this
42+
// are allocated using aligned versions of std::new and std::delete.
43+
//
44+
class VSG_DECLSPEC IntrusiveAllocator : public Allocator
45+
{
46+
public:
47+
explicit IntrusiveAllocator(size_t in_defaultAlignment = 4);
48+
explicit IntrusiveAllocator(std::unique_ptr<Allocator> in_nestedAllocator, size_t in_defaultAlignment = 4);
49+
50+
~IntrusiveAllocator();
51+
52+
void report(std::ostream& out) const override;
53+
54+
void* allocate(std::size_t size, AllocatorAffinity allocatorAffinity = ALLOCATOR_AFFINITY_OBJECTS) override;
55+
56+
bool deallocate(void* ptr, std::size_t size) override;
57+
58+
bool validate() const;
59+
60+
size_t deleteEmptyMemoryBlocks() override;
61+
size_t totalAvailableSize() const override;
62+
size_t totalReservedSize() const override;
63+
size_t totalMemorySize() const override;
64+
void setBlockSize(AllocatorAffinity allocatorAffinity, size_t blockSize) override;
65+
66+
protected:
67+
struct VSG_DECLSPEC MemoryBlock
68+
{
69+
MemoryBlock(const std::string& in_name, size_t in_blockSize, size_t in_alignment);
70+
virtual ~MemoryBlock();
71+
72+
std::string name;
73+
74+
void* allocate(std::size_t size);
75+
bool deallocate(void* ptr, std::size_t size);
76+
77+
void report(std::ostream& out) const;
78+
79+
// bitfield packing of doubly-linked with status field into a 4 byte word
80+
struct Element
81+
{
82+
union
83+
{
84+
uint32_t index;
85+
86+
struct
87+
{
88+
unsigned int previous : 15;
89+
unsigned int next : 15;
90+
unsigned int status : 2;
91+
};
92+
};
93+
94+
using Offset = decltype(previous);
95+
using Status = decltype(status);
96+
using Index = decltype(index);
97+
98+
explicit Element(Index in_index) :
99+
index(static_cast<Offset>(in_index)) {}
100+
101+
Element(Offset in_previous, Offset in_next, Status in_status) :
102+
previous(static_cast<Offset>(in_previous)),
103+
next(static_cast<Offset>(in_next)),
104+
status(in_status) {}
105+
106+
Element() = default;
107+
Element(const Element&) = default;
108+
};
109+
110+
struct FreeList
111+
{
112+
Element::Index count = 0;
113+
Element::Index head = 0;
114+
};
115+
116+
Element* memory = nullptr;
117+
Element* memoryEnd = nullptr;
118+
119+
size_t alignment = 4; // min aligment is 4 { sizeof(Element) }
120+
size_t blockAlignment = 16;
121+
size_t blockSize = 0;
122+
size_t maximumAllocationSize = 0;
123+
Element::Index elementAlignment = 1;
124+
Element::Index firstSlot = 1;
125+
Element::Index capacity = 0;
126+
127+
std::vector<FreeList> freeLists;
128+
129+
bool validate() const;
130+
131+
bool freeSlotsAvaible(size_t size) const;
132+
133+
inline bool within(const void* ptr) const { return memory <= ptr && ptr < memoryEnd; }
134+
135+
size_t totalAvailableSize() const;
136+
size_t totalReservedSize() const;
137+
size_t totalMemorySize() const;
138+
139+
// used for debugging only.
140+
struct VSG_DECLSPEC SlotTester
141+
{
142+
SlotTester(Element* in_mem, size_t in_head) :
143+
mem(in_mem), head(in_head){};
144+
145+
const Element* mem = nullptr;
146+
size_t head = 0;
147+
148+
struct Entry
149+
{
150+
std::string name;
151+
size_t position;
152+
Element slot;
153+
size_t previousFree;
154+
size_t nextFree;
155+
};
156+
157+
std::list<Entry> elements;
158+
159+
void slot(size_t position, const std::string& name);
160+
161+
void report(std::ostream& out);
162+
};
163+
164+
static inline size_t computeMaxiumAllocationSize(size_t blockSize, size_t alignment)
165+
{
166+
return std::min(blockSize - alignment, size_t((1 << 15) - 2) * sizeof(Element));
167+
}
168+
};
169+
170+
class VSG_DECLSPEC MemoryBlocks
171+
{
172+
public:
173+
MemoryBlocks(IntrusiveAllocator* in_parent, const std::string& in_name, size_t in_blockSize, size_t in_alignment);
174+
virtual ~MemoryBlocks();
175+
176+
IntrusiveAllocator* parent = nullptr;
177+
std::string name;
178+
size_t alignment = 4;
179+
size_t blockSize = 0;
180+
size_t maximumAllocationSize = 0;
181+
std::vector<std::shared_ptr<MemoryBlock>> memoryBlocks;
182+
std::shared_ptr<MemoryBlock> memoryBlockWithSpace;
183+
184+
void* allocate(std::size_t size);
185+
void report(std::ostream& out) const;
186+
bool validate() const;
187+
188+
size_t deleteEmptyMemoryBlocks();
189+
size_t totalAvailableSize() const;
190+
size_t totalReservedSize() const;
191+
size_t totalMemorySize() const;
192+
};
193+
194+
std::vector<std::unique_ptr<MemoryBlocks>> allocatorMemoryBlocks;
195+
std::map<void*, std::shared_ptr<MemoryBlock>> memoryBlocks;
196+
std::map<void*, std::pair<size_t, size_t>> largeAllocations;
197+
};
198+
199+
} // namespace vsg

include/vsg/utils/FindDynamicObjects.h

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,6 @@ namespace vsg
2424
class VSG_DECLSPEC FindDynamicObjects : public Inherit<ConstVisitor, FindDynamicObjects>
2525
{
2626
public:
27-
2827
std::mutex mutex;
2928
std::set<const Object*> dynamicObjects;
3029

include/vsg/vk/Device.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ namespace vsg
2323

2424
// forward declare
2525
class WindowTraits;
26+
class MemoryBufferPools;
2627

2728
struct QueueSetting
2829
{
@@ -82,6 +83,10 @@ namespace vsg
8283
/// return true if Device was created with specified extension
8384
bool supportsDeviceExtension(const char* extensionName) const;
8485

86+
// provide observer_ptr to memory buffer pools so that these can be accessed when required
87+
observer_ptr<MemoryBufferPools> deviceMemoryBufferPools;
88+
observer_ptr<MemoryBufferPools> stagingMemoryBufferPools;
89+
8590
protected:
8691
virtual ~Device();
8792

0 commit comments

Comments
 (0)