1 /**
2 	FreeList allocator proxy templates used to prevent memory segmentation
3 	on base allocator.
4 
5     Copyright: © 2012-2013 RejectedSoftware e.K.
6     		   © 2014-2015 Etienne Cimon
7     License: Subject to the terms of the MIT license.
8     Authors: Sönke Ludwig, Etienne Cimon
9 */
10 module memutils.freelist;
11 
12 import memutils.allocators;
13 import std.algorithm : min;
14 
15 final class AutoFreeListAllocator(Base : Allocator) : Allocator {
16 	import std.typetuple;
17 	
18 	private {
19 		enum minExponent = 3;
20 		enum freeListCount = 12;
21 		FreeListAlloc!Base[freeListCount] m_freeLists;
22 		Base m_baseAlloc;
23 	}
24 	
25 	this()
26 	{
27 		m_baseAlloc = getAllocator!Base();
28 		foreach (i; iotaTuple!freeListCount)
29 			m_freeLists[i] = new FreeListAlloc!Base(nthFreeListSize!(i));
30 	}
31 
32 	~this() {
33 		foreach(fl; m_freeLists)
34 			destroy(fl);
35 	}
36 	
37 	void[] alloc(size_t sz)
38 	{
39 		logTrace("AFL alloc ", sz);
40 		if (sz > nthFreeListSize!(freeListCount-1)) return m_baseAlloc.alloc(sz);
41 		foreach (i; iotaTuple!freeListCount)
42 			if (sz <= nthFreeListSize!(i))
43 				return m_freeLists[i].alloc().ptr[0 .. sz];
44 		assert(false);
45 	}
46 
47 	void[] realloc(void[] data, size_t sz)
48 	{
49 		foreach (fl; m_freeLists) {
50 			if (data.length <= fl.elementSize) {
51 				// just grow the slice if it still fits into the free list slot
52 				if (sz <= fl.elementSize)
53 					return data.ptr[0 .. sz];
54 				
55 				// otherwise re-allocate
56 				auto newd = alloc(sz);
57 				assert(newd.ptr+sz <= data.ptr || newd.ptr >= data.ptr+data.length, "New block overlaps old one!?");
58 				auto len = min(data.length, sz);
59 				newd[0 .. len] = data[0 .. len];
60 				free(data);
61 				return newd;
62 			}
63 		}
64 		// forward large blocks to the base allocator
65 		return m_baseAlloc.realloc(data, sz);
66 	}
67 
68 	void free(void[] data)
69 	{
70 		logTrace("AFL free ", data.length);
71 		if (data.length > nthFreeListSize!(freeListCount-1)) {
72 			m_baseAlloc.free(data);
73 			return;
74 		}
75 		foreach(i; iotaTuple!freeListCount) {
76 			if (data.length <= nthFreeListSize!i) {
77 				m_freeLists[i].free(data.ptr[0 .. nthFreeListSize!i]);
78 				return;
79 			}
80 		}
81 		assert(false);
82 	}
83 	
84 	private static pure size_t nthFreeListSize(size_t i)() { return 1 << (i + minExponent); }
85 	private template iotaTuple(size_t i) {
86 		static if (i > 1) alias iotaTuple = TypeTuple!(iotaTuple!(i-1), i-1);
87 		else alias iotaTuple = TypeTuple!(0);
88 	}
89 }
90 
91 final class FreeListAlloc(Base : Allocator) : Allocator
92 {
93 	import memutils.vector : Vector;
94 	import memutils.utils : Malloc;
95 	private static struct FreeListSlot { FreeListSlot* next; }
96 	private {
97 		immutable size_t m_elemSize;
98 		Base m_baseAlloc;
99 		FreeListSlot* m_firstFree = null;
100 		size_t[] m_owned;
101 		size_t m_nalloc = 0;
102 		size_t m_nfree = 0;
103 	}
104 
105 	~this() {
106 		import core.thread : thread_isMainThread;
107 		if (!thread_isMainThread)
108 		foreach(size_t slot; m_owned) {
109 			m_baseAlloc.free( (cast(void*)slot)[0 .. m_elemSize]);
110 		}
111 	}
112 	
113 	this(size_t elem_size)
114 	{
115 		assert(elem_size >= size_t.sizeof);
116 		m_elemSize = elem_size;
117 		m_baseAlloc = getAllocator!Base();
118 		//logTrace("Create FreeListAlloc %d", m_elemSize);
119 	}
120 	
121 	@property size_t elementSize() const { return m_elemSize; }
122 	
123 	void[] alloc(size_t sz)
124 	{
125 		assert(sz == m_elemSize, "Invalid allocation size.");
126 		return alloc();
127 	}
128 	
129 	void[] alloc()
130 	{
131 		void[] mem;
132 		if( m_firstFree ){
133 			auto slot = m_firstFree;
134 			m_firstFree = slot.next;
135 			slot.next = null;
136 			mem = (cast(void*)slot)[0 .. m_elemSize];
137 			m_nfree--;
138 		} else {
139 			mem = m_baseAlloc.alloc(m_elemSize);
140 			m_owned ~= cast(size_t)mem.ptr;
141 			//logInfo("Alloc %d bytes: alloc: %d, free: %d", SZ, s_nalloc, s_nfree);
142 		}
143 		m_nalloc++;
144 		//logInfo("Alloc %d bytes: alloc: %d, free: %d", SZ, s_nalloc, s_nfree);
145 		return mem;
146 	}
147 
148 	void[] realloc(void[] mem, size_t sz)
149 	{
150 		assert(mem.length == m_elemSize);
151 		assert(sz == m_elemSize);
152 		return mem;
153 	}
154 
155 	void free(void[] mem)
156 	{
157 		assert(mem.length == m_elemSize, "Memory block passed to free has wrong size.");
158 		auto s = cast(FreeListSlot*)mem.ptr;
159 		s.next = m_firstFree;
160 		m_firstFree = s;
161 		m_nalloc--;
162 		m_nfree++;
163 	}
164 }