├── .gitignore
├── LICENSE
├── README.md
├── gctest.sln
├── gctest.vcxproj
├── gctest.vcxproj.filters
├── test.cpp
├── tgc.cpp
└── tgc.h
/.gitignore:
--------------------------------------------------------------------------------
1 | /Debug
2 | /Release
3 | /*.user
4 | /*.suo
5 | /*.sdf
6 | /*.opendb
7 | /.vs/gctest/v14/*.suo
8 | /.vs
9 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018 crazybie
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # TGC
2 |
3 | ## A Tiny, precise, incremental, mark & sweep, Garbage Collector for C++.
4 |
5 | 参考请注明出处,谢谢。
6 |
7 | ### Motivation
8 | - Scenarios that shared_ptr can't solve, e.g. object dependencies are dynamically constructed with no chance to recognize the usage of shared & weak pointers.
9 | - Try to make things simpler compared to shared_ptr and Oilpan, e.g. networking programs using callbacks for async io operations heavily.
10 | - A very good experiment to design a GC dedicated to the C++ language and see how the language features can help.
11 |
12 | ### Highlights
13 | - Non-intrusive
14 | - Use like shared_ptr.
15 | - Do not need to replace the global new operator.
16 | - Do not need to inherit from a common base.
17 | - Can even work with shared_ptr.
18 | - Incremental marking and sweeping
19 | - Won't stop the world.
20 | - Can specify the number of steps used for each collecting.
21 | - Can manually delete the object to control the destruction order.
22 | - Super lightweight
23 | - Only one header & CPP file, easier to integrate.
24 | - No extra threads to collect garbage.
25 | - Support most of the containers of STL.
26 | - Cross-platform, no other dependencies, only dependent on STL.
27 | - Support multi-threads.
28 | - Customization
29 | - It can work with other memory allocators and pool.
30 | - Provide hooks to redirect memory allocation.
31 | - It can be extended to use your custom containers.
32 | - Precise.
33 | - Ensure no memory leaks as long as objects are correctly traced.
34 |
35 | ### Comparison
36 | - Pros over shared_ptr:
37 | - no need for weak_ptr to break the circular references.
38 | - no shared_from_this is needed for GC pointer.
39 | - gc_from(this) works in the constructor where shared_ptr is not.
40 | - construct GC pointer from the raw pointer is safe to call many times where shared_ptr is not because it will reset the ref counter
41 | - can't predicate the number of objects destructed in complex scenarios when clear a shared_ptr, but not for GC pointers as you can control the collection steps to run.
42 |
43 | - Pros over Oilpan GC:
44 | - Easier to use, only one kind of GC pointer to be used.
45 | - More general, suitable for wider scenarios.
46 |
47 | ### Internals
48 | - This collector uses the triple color, mark & sweep algorithm internally.
49 | - Pointers are constructed as roots by default unless detected as children of other object.
50 | - A GC pointer is with the size of 3-pointers:
51 | - one flag determin whether it's root or not.
52 | - an index for fast unregistering from collector.
53 | - one raw pointer to the object and another one raw pointer to the correspoinding meta-object, this is to support:
54 | - multiple inheritance.
55 | - pointer to fields of other object, aka internal pointer.
56 | - Every class has a global meta-object keeping the necessary meta-information (e.g. class size and offsets of member pointers) used by GC, so programs using lambdas heavily may have some memory overhead. Besides, as the initialization order of global objects is not well defined, you should not use GC pointers as global variables too (there is an assert checking it).
57 | - Construct & copy & modify GC pointers are slower than shared_ptr, much slower than raw pointers(Boehm GC).
58 | - Every GC pointer must register itself to the collector and unregister on destruction as well.
59 | - Since C++ does not support ref-qualified constructors, the gc_new returns a temporary GC pointer bringing in some meaningless overhead. Instead, using gc_new_meta can bypass the construction of the temporary making things a bit faster.
60 | - Member pointers offsets of one class are calculated and recorded at the first time of creating the instance of that class.
61 | - Modifying a GC pointer will trigger a GC color adjustment which may not be cheap as well.
62 | - Each allocation has a few extra space overhead (size of two pointers at most), which is used for memory tracing.
63 | - Marking & swapping should be much faster than Boehm GC, due to the deterministic pointer management, no scanning inside the memories at all, just iterating pointers registered in the GC.
64 | - To make objects in proper tracing chain, you must use GC wrappers of STL containers instead, otherwise, memory leaks may occur.
65 | - gc_vector stores pointers of elements making its storage not continuous as a standard vector, this is necessary for the GC. All wrapped containers of STL stores GC pointers as elements.
66 | - You can manually call gc_delete to trigger the destructor of an object and let the GC claim the memory automatically. Besides, double free is also safe.
67 | - For the multi-threaded version, the collection function should be invoked from the main thread therefore the destructors can be triggered in the main thread as well.
68 |
69 |
70 | ### Performance Advice
71 | - Performance is not the first goal of this library.
72 | - Results from tests, a simple allocation of an integer is about 8~10 slower than standard new(see test), so benchmark your program if GC pointers are heavily used in the performance-critical parts(e.g. VM of another language).
73 | - Use the references to GC pointers as much as possible. (e.g. function parameters, see internals section)
74 | - Use gc_new_array to get a collectible continuous array for better performance in some special cases (see internals section).
75 | - Continuous efforts will be put to optimize the performance at a later time.
76 | - Languages with GC built-in prefer to create a huge number of heap objects which will give large pressure to the GC, some languages even use pointer escaping analyzing algorithm to increase the recycling efficiency, but it's not a serious problem to C++ as it has RAII and does not use heap objects everywhere. So the throughput of this triple-color GC should be efficient enough.
77 | - For real-time applications:
78 | - Static strategy: just call gc_collect with a suitable step count regularly in each frame of the event loop.
79 | - Dynamic strategy: you can specify a small step count(default is 255) for one collecting call and time it to see if still has time left to collect again, otherwise do collecting at the next time.
80 | - As memories are managed by GC, you can not release them immediately. If you want to get rid of the risk of OOM on some resource-limited system, memories guaranteed to have no pointers in it can be managed by shared_ptrs or raw pointers.
81 | - The single-threaded version(by default) should be much faster than the multi-threaded version because no locks are required at all. Please define TGC_MULTI_THREADED to enable the multi-threaded version.
82 |
83 |
84 | ### Usage
85 |
86 | Please see the tests in 'test.cpp'.
87 | Another small demo here: https://github.com/crazybie/AsioTest.git
88 |
89 | ### Refs
90 |
91 | - https://www.codeproject.com/Articles/938/A-garbage-collection-framework-for-C-Part-II.
92 | - Boehn GC: https://github.com/ivmai/bdwgc/
93 | - Oilpan GC: https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/platform/heap/BlinkGCDesign.md#Threading-model
94 |
95 | ### TODO
96 |
97 | - move & compact
98 | sine we manages raw pointers in pointer object, we can move the object and adjust the raw pointers internally.
99 | - use generatioinal algorithm
100 | no need to scan all objects every time when do garbage collecting.
101 |
102 | ### License
103 |
104 | The MIT License
105 |
106 | ```
107 | Copyright (C) 2018 soniced@sina.com
108 |
109 | 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:
110 |
111 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
112 |
113 | 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.
114 | ```
115 |
--------------------------------------------------------------------------------
/gctest.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio 14
4 | VisualStudioVersion = 14.0.24720.0
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "gctest", "gctest.vcxproj", "{9DB1F4B8-B6DC-4C16-85CC-15F002E9AD37}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Win32 = Debug|Win32
11 | Release|Win32 = Release|Win32
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {9DB1F4B8-B6DC-4C16-85CC-15F002E9AD37}.Debug|Win32.ActiveCfg = Debug|Win32
15 | {9DB1F4B8-B6DC-4C16-85CC-15F002E9AD37}.Debug|Win32.Build.0 = Debug|Win32
16 | {9DB1F4B8-B6DC-4C16-85CC-15F002E9AD37}.Release|Win32.ActiveCfg = Release|Win32
17 | {9DB1F4B8-B6DC-4C16-85CC-15F002E9AD37}.Release|Win32.Build.0 = Release|Win32
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | EndGlobal
23 |
--------------------------------------------------------------------------------
/gctest.vcxproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Debug
6 | Win32
7 |
8 |
9 | Release
10 | Win32
11 |
12 |
13 |
14 |
15 |
16 | {9DB1F4B8-B6DC-4C16-85CC-15F002E9AD37}
17 | 10.0
18 |
19 |
20 |
21 | Application
22 | v142
23 | false
24 | MultiByte
25 |
26 |
27 | Application
28 | v142
29 | false
30 | MultiByte
31 |
32 |
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 |
45 | .\Release\
46 | .\Release\
47 | false
48 |
49 |
50 | .\Debug\
51 | .\Debug\
52 | true
53 |
54 |
55 |
56 | MultiThreaded
57 | AnySuitable
58 | true
59 | true
60 | MaxSpeed
61 | true
62 | Level3
63 | true
64 | WIN32;NDEBUG;_CONSOLE;%(PreprocessorDefinitions)
65 | .\Release\
66 | .\Release\gctest.pch
67 | .\Release\
68 | .\Release\
69 | Speed
70 | true
71 |
72 |
73 | .\Release\gctest.tlb
74 |
75 |
76 | 0x0409
77 | NDEBUG;%(PreprocessorDefinitions)
78 |
79 |
80 | true
81 | .\Release\gctest.bsc
82 |
83 |
84 | true
85 | Console
86 | .\Release\gctest.exe
87 | odbc32.lib;odbccp32.lib;%(AdditionalDependencies)
88 | true
89 |
90 |
91 |
92 |
93 | MultiThreadedDebug
94 | Default
95 | true
96 | Disabled
97 | true
98 | Level3
99 | true
100 | true
101 | WIN32;_DEBUG;_CONSOLE;%(PreprocessorDefinitions)
102 | .\Debug\
103 | .\Debug\gctest.pch
104 | .\Debug\
105 | .\Debug\
106 | EnableFastChecks
107 |
108 |
109 | .\Debug\gctest.tlb
110 |
111 |
112 | 0x0409
113 | _DEBUG;%(PreprocessorDefinitions)
114 |
115 |
116 | true
117 | .\Debug\gctest.bsc
118 |
119 |
120 | true
121 | true
122 | Console
123 | .\Debug\gctest.exe
124 | %(AdditionalDependencies)
125 |
126 |
127 |
128 |
129 |
130 |
131 |
132 |
133 |
134 |
135 |
136 |
137 |
--------------------------------------------------------------------------------
/gctest.vcxproj.filters:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | {be4f26a3-0c8b-4e76-8ba6-1eda1c5cb560}
6 | cpp;c;cxx;rc;def;r;odl;idl;hpj;bat
7 |
8 |
9 | {5da56b99-f024-4973-bcb0-55623688498d}
10 | h;hpp;hxx;hm;inl
11 |
12 |
13 | {5b8293ef-9c58-4ad5-9239-42f1a5736954}
14 | ico;cur;bmp;dlg;rc2;rct;bin;rgs;gif;jpg;jpeg;jpe
15 |
16 |
17 |
18 |
19 | Source Files
20 |
21 |
22 | Source Files
23 |
24 |
25 |
26 |
27 | Header Files
28 |
29 |
30 |
--------------------------------------------------------------------------------
/test.cpp:
--------------------------------------------------------------------------------
1 | #include
2 |
3 | #include
4 | #include
5 | #include
6 |
7 | #include "tgc.h"
8 |
9 | using namespace tgc;
10 | using namespace std;
11 |
12 | struct b1 {
13 | b1(const string& s) : name(s) {
14 | cout << "Creating b1(" << name << ")." << endl;
15 | }
16 | virtual ~b1() { cout << "Destroying b1(" << name << ")." << endl; }
17 |
18 | string name;
19 | };
20 |
21 | struct b2 {
22 | b2(const string& s) : name(s) {
23 | cout << "Creating b2(" << name << ")." << endl;
24 | }
25 | virtual ~b2() { cout << "Destroying b2(" << name << ")." << endl; }
26 |
27 | string name;
28 | };
29 |
30 | struct d1 : public b1 {
31 | d1(const string& s) : b1(s) {
32 | cout << "Creating d1(" << name << ")." << endl;
33 | }
34 | virtual ~d1() { cout << "Destroying d1(" << name << ")." << endl; }
35 | };
36 |
37 | struct d2 : public b1, public b2 {
38 | d2(const string& s) : b1(s), b2(s) {
39 | cout << "Creating d2(" << b1::name << ")." << endl;
40 | }
41 | virtual ~d2() { cout << "Destroying d2(" << b1::name << ")." << endl; }
42 | };
43 |
44 | struct rc {
45 | int a = 11;
46 | rc() {}
47 | ~rc() { auto i = gc(this); }
48 | };
49 |
50 | void testPointerCast() {
51 | {
52 | gc prc = gc_new();
53 | {
54 | gc p2(gc_new("first"));
55 | gc p3(p2);
56 | gc p4(gc_new("second"));
57 | gc pz(dynamic_cast(&*p4));
58 | if ((void*)&*p4 == (void*)&*pz)
59 | throw std::runtime_error("unexpected");
60 |
61 | p3 = p2;
62 | gc_collect();
63 | }
64 | }
65 | gc_collect();
66 | }
67 |
68 | struct circ {
69 | circ(const string& s) : name(s) {
70 | cout << "Creating circ(" << name << ")." << endl;
71 | }
72 | ~circ() { cout << "Destroying circ(" << name << ")." << endl; }
73 |
74 | gc ptr;
75 | string name;
76 | };
77 |
78 | void testCirc() {
79 | {
80 | auto p5 = gc_new("root");
81 | {
82 | auto p6 = gc_new("first");
83 | auto p7 = gc_new("second");
84 |
85 | p5->ptr = p6;
86 |
87 | p6->ptr = p7;
88 | p7->ptr = p6;
89 |
90 | gc_collect();
91 | }
92 | }
93 | gc_collect();
94 | }
95 |
96 | void testMoveCtor() {
97 | {
98 | auto f = [] {
99 | auto t = gc_new("");
100 | return std::move(t);
101 | };
102 |
103 | auto p = f();
104 | gc p2 = p;
105 | p2 = f();
106 | }
107 | }
108 |
109 | void testMakeGcObj() {
110 | { auto a = gc_new("test"); }
111 | }
112 |
113 | void testEmpty() {
114 | {
115 | gc p(gc_new("a"));
116 | gc emptry;
117 | }
118 | }
119 |
120 | struct ArrayTest {
121 | gc_vector a;
122 | gc_map b;
123 | gc_map c;
124 |
125 | void f() {
126 | a = gc_new_vector();
127 | a->push_back(gc_new());
128 | b = gc_new_map();
129 | (*b)[0] = gc_new();
130 | b[1] = gc_new();
131 |
132 | b->find(1);
133 | bar(b);
134 | }
135 | void bar(gc_map cc) { cc->insert(std::make_pair(1, gc_new())); }
136 | };
137 |
138 | void testArray() {
139 | gc a;
140 | a = gc_new();
141 | a->f();
142 |
143 | a = gc_new();
144 | gc_delete(a);
145 | }
146 |
147 | void testCircledContainer() {
148 | static int delCnt = 0;
149 | struct Node {
150 | gc_map childs = gc_new_map();
151 | ~Node() { delCnt++; }
152 | };
153 | {
154 | auto node = gc_new();
155 | node->childs[0] = node;
156 | }
157 | gc_collect();
158 | assert(delCnt == 1);
159 | }
160 |
161 | bool operator<(rc& a, rc& b) {
162 | return a.a < b.a;
163 | }
164 |
165 | void testSet() {
166 | {
167 | gc_set t = gc_new_set();
168 | auto o = gc_new();
169 | t->insert(o);
170 | }
171 | gc_collect(1);
172 |
173 | auto t = gc_new_set();
174 | gc_delete(t);
175 | }
176 |
177 | void testList() {
178 | auto l = gc_new_list();
179 | l->push_back(gc_new(1));
180 | l->push_back(gc_new(2));
181 | l->pop_back();
182 | assert(*l->back() == 1);
183 |
184 | auto ll = gc_new_list();
185 | gc_delete(ll);
186 | }
187 |
188 | void testDeque() {
189 | auto l = gc_new_deque();
190 | l->push_back(gc_new(1));
191 | l->push_back(gc_new(2));
192 | l->pop_back();
193 | assert(*l->back() == 1);
194 |
195 | auto ll = gc_new_deque();
196 | gc_delete(ll);
197 | }
198 |
199 | void testHashMap() {
200 | auto l = gc_new_unordered_map();
201 | l[1] = gc_new(1);
202 | assert(l->size() == 1);
203 | assert(*l[1] == 1);
204 |
205 | auto ll = gc_new_unordered_map();
206 | gc_delete(ll);
207 | }
208 |
209 | void testLambda() {
210 | gc_function ff;
211 | {
212 | auto l = gc_new(1);
213 | auto f = [=] { return *l; };
214 |
215 | ff = f;
216 | }
217 |
218 | int i = ff();
219 | assert(i == 1);
220 | }
221 |
222 | void testPrimaryImplicitCtor() {
223 | gc a(1), b = gc_new(2);
224 | assert(a < b);
225 |
226 | auto v = gc_new_vector();
227 | v->push_back(1);
228 | assert(v[0] == 1);
229 |
230 | using namespace std::string_literals;
231 |
232 | gc_string s = "213"s;
233 | printf("%s", s->c_str());
234 | }
235 |
236 | void testGcFromThis() {
237 | struct Base {
238 | int i;
239 | Base() {
240 | auto p = gc_from(this);
241 | assert(p);
242 | }
243 | };
244 |
245 | struct Child : Base {
246 | int b;
247 | };
248 |
249 | auto makeLowerBoundHasElemToCompare = gc_new();
250 | auto p = gc_new();
251 | }
252 |
253 | void testDynamicCast() {
254 | struct BaseA {
255 | int a;
256 | virtual ~BaseA() {}
257 | };
258 | struct BaseB {
259 | float f;
260 | virtual ~BaseB() {}
261 | };
262 | struct Sub : BaseA, BaseB {
263 | int c;
264 | };
265 | auto sub = gc_new();
266 | gc baseB = sub;
267 | auto sub2 = gc_dynamic_pointer_cast(baseB);
268 | assert(sub == sub2);
269 | }
270 |
271 | void testException() {
272 | struct Ctx {
273 | int dctorCnt = 0, ctorCnt = 0;
274 | int len = 3;
275 | };
276 |
277 | struct Test {
278 | Ctx& c;
279 | Test(Ctx& cc) : c(cc) {
280 | c.ctorCnt++;
281 | if (c.ctorCnt == c.len)
282 | throw 1;
283 | }
284 | ~Test() { c.dctorCnt++; }
285 | };
286 |
287 | auto err = false;
288 | Ctx c;
289 | try {
290 | auto i = gc_new_array(c.len, c);
291 | } catch (int) {
292 | err = true;
293 | }
294 | assert(err);
295 | assert(c.dctorCnt == c.len - 1);
296 | assert(details::ClassMeta::get()->isCreatingObj == 0);
297 | }
298 |
299 | void testCollection() {
300 | struct Circled {
301 | gc child;
302 | };
303 |
304 | {
305 | int cnt = 1000;
306 | for (int i = 0; i < cnt; i++) {
307 | auto s = gc_new();
308 | s->child = s;
309 | }
310 | gc_dumpStats();
311 | gc_collect(cnt * 5);
312 | gc_dumpStats();
313 | }
314 | }
315 |
316 | const int profilingCounts = 10000 * 100;
317 |
318 | auto profiled = [](const char* tag, auto cb) {
319 | auto start = std::chrono::high_resolution_clock::now();
320 | for (int i = 0; i < profilingCounts; i++)
321 | cb();
322 | auto end = std::chrono::high_resolution_clock::now();
323 | std::chrono::duration elapsed_seconds = end - start;
324 | printf("[%10s] elapsed time: %fs\n", tag, elapsed_seconds.count());
325 | };
326 |
327 | void profileAlloc() {
328 | #ifndef _DEBUG
329 | vector rawPtrs;
330 | rawPtrs.reserve(profilingCounts);
331 | profiled("gc int", [] { gc p(111); });
332 | profiled("raw int", [&] { rawPtrs.push_back(new int(111)); });
333 | for (auto* i : rawPtrs)
334 | delete i;
335 | gc_collect(profilingCounts * 2);
336 | gc_dumpStats();
337 | #endif
338 | }
339 |
340 | int main() {
341 | profileAlloc();
342 | testCollection();
343 | testException();
344 | testDynamicCast();
345 | testGcFromThis();
346 | testCircledContainer();
347 | testPrimaryImplicitCtor();
348 | testSet();
349 | testEmpty();
350 | // testPointerCast();
351 | testMoveCtor();
352 | testCirc();
353 | testArray();
354 | testList();
355 | testDeque();
356 | testHashMap();
357 | testLambda();
358 |
359 | // there are some objects leaked from the upper tests, just dump them
360 | // out.
361 | gc_dumpStats();
362 | gc_collect();
363 | // there should be no objects exists after the collecting.
364 | gc_dumpStats();
365 |
366 | // leaking test, you should not see leaks in the output of VS.
367 | auto i = gc_new(100);
368 | return 0;
369 | }
370 |
--------------------------------------------------------------------------------
/tgc.cpp:
--------------------------------------------------------------------------------
1 | #include "tgc.h"
2 |
3 | #ifdef _WIN32
4 | #include
5 | #endif
6 |
7 | namespace tgc {
8 | namespace details {
9 |
10 | #ifndef TGC_MULTI_THREADED
11 | shared_mutex ClassMeta::mutex;
12 | #endif
13 | atomic ClassMeta::isCreatingObj = 0;
14 | ClassMeta ClassMeta::dummy;
15 | char* ObjMeta::dummyObjPtr = nullptr;
16 | Collector* Collector::inst = nullptr;
17 |
18 | static const char* StateStr[(int)Collector::State::MaxCnt] = {
19 | "RootMarking", "LeafMarking", "Sweeping"};
20 |
21 | //////////////////////////////////////////////////////////////////////////
22 |
23 | char* ObjMeta::objPtr() const {
24 | return klass == &ClassMeta::dummy ? dummyObjPtr
25 | : (char*)this + sizeof(ObjMeta);
26 | }
27 |
28 | void ObjMeta::destroy() {
29 | if (!arrayLength)
30 | return;
31 | klass->memHandler(klass, ClassMeta::MemRequest::Dctor, this);
32 | arrayLength = 0;
33 | }
34 |
35 | void ObjMeta::operator delete(void* p) {
36 | auto* m = (ObjMeta*)p;
37 | m->klass->memHandler(m->klass, ClassMeta::MemRequest::Dealloc, m);
38 | }
39 |
40 | bool ObjMeta::operator<(ObjMeta& r) const {
41 | return objPtr() + klass->size * arrayLength <
42 | r.objPtr() + r.klass->size * r.arrayLength;
43 | }
44 |
45 | bool ObjMeta::containsPtr(char* p) {
46 | auto* o = objPtr();
47 | return o <= p && p < o + klass->size * arrayLength;
48 | }
49 |
50 | //////////////////////////////////////////////////////////////////////////
51 |
52 | const PtrBase* ObjPtrEnumerator::getNext() {
53 | if (auto* subPtrs = meta->klass->subPtrOffsets) {
54 | if (arrayElemIdx < meta->arrayLength && subPtrIdx < subPtrs->size()) {
55 | auto* klass = meta->klass;
56 | auto* obj = meta->objPtr() + arrayElemIdx * klass->size;
57 | auto* subPtr = obj + (*klass->subPtrOffsets)[subPtrIdx];
58 | if (subPtrIdx++ >= klass->subPtrOffsets->size())
59 | arrayElemIdx++;
60 | return (PtrBase*)subPtr;
61 | }
62 | }
63 | return nullptr;
64 | }
65 |
66 | //////////////////////////////////////////////////////////////////////////
67 |
68 | PtrBase::PtrBase() : isRoot(1) {
69 | auto* c = Collector::inst ? Collector::inst : Collector::get();
70 | c->registerPtr(this);
71 | }
72 |
73 | PtrBase::PtrBase(void* obj) : isRoot(1) {
74 | auto* c = Collector::inst ? Collector::inst : Collector::get();
75 | meta = c->globalFindOwnerMeta(obj);
76 | c->registerPtr(this);
77 | }
78 |
79 | PtrBase::~PtrBase() {
80 | Collector::inst->unregisterPtr(this);
81 | }
82 |
83 | void PtrBase::onPtrChanged() {
84 | Collector::inst->onPointerChanged(this);
85 | }
86 |
87 | //////////////////////////////////////////////////////////////////////////
88 |
89 | ObjMeta* ClassMeta::newMeta(size_t objCnt) {
90 | assert(memHandler && "should not be called in global scope (before main)");
91 | auto* meta = (ObjMeta*)memHandler(this, MemRequest::Alloc,
92 | reinterpret_cast(objCnt));
93 |
94 | try {
95 | auto* c = Collector::inst ? Collector::inst : Collector::get();
96 | // Allow using gc_from(this) in the constructor of the creating object.
97 | c->addMeta(meta);
98 | } catch (std::bad_alloc&) {
99 | memHandler(this, MemRequest::Dealloc, meta);
100 | throw;
101 | }
102 |
103 | isCreatingObj++;
104 | return meta;
105 | }
106 |
107 | void ClassMeta::endNewMeta(ObjMeta* meta, bool failed) {
108 | isCreatingObj--;
109 | if (!failed) {
110 | unique_lock lk{mutex};
111 | state = ClassMeta::State::Registered;
112 | }
113 |
114 | {
115 | auto* c = Collector::inst;
116 | unique_lock lk{c->mutex, try_to_lock};
117 | c->creatingObjs.remove(meta);
118 | if (failed) {
119 | c->metaSet.erase(meta);
120 | memHandler(this, MemRequest::Dealloc, meta);
121 | }
122 | }
123 | }
124 |
125 | void ClassMeta::registerSubPtr(ObjMeta* owner, PtrBase* p) {
126 | auto offset = (OffsetType)((char*)p - owner->objPtr());
127 |
128 | {
129 | shared_lock lk{mutex};
130 |
131 | if (state == ClassMeta::State::Registered)
132 | return;
133 | // constructor recursed.
134 | if (subPtrOffsets && offset <= subPtrOffsets->back())
135 | return;
136 | }
137 |
138 | unique_lock lk{mutex};
139 | if (!subPtrOffsets)
140 | subPtrOffsets = new vector();
141 | subPtrOffsets->push_back(offset);
142 | }
143 |
144 | //////////////////////////////////////////////////////////////////////////
145 |
146 | Collector::Collector() {
147 | pointers.reserve(1024 * 5);
148 | grayObjs.reserve(1024 * 2);
149 | metaSet.reserve(1024 * 5);
150 | }
151 |
152 | Collector::~Collector() {
153 | for (auto i = metaSet.begin(); i != metaSet.end();) {
154 | delete *i;
155 | i = metaSet.erase(i);
156 | }
157 | }
158 |
159 | Collector* Collector::get() {
160 | if (!inst) {
161 | #ifdef _WIN32
162 | _CrtSetDbgFlag(_CRTDBG_ALLOC_MEM_DF | _CRTDBG_LEAK_CHECK_DF);
163 | #endif
164 |
165 | inst = new Collector();
166 | atexit([] { delete inst; });
167 | }
168 | return inst;
169 | }
170 |
171 | void Collector::addMeta(ObjMeta* meta) {
172 | unique_lock lk{mutex, try_to_lock};
173 | metaSet.insert(meta);
174 | creatingObjs.push_back(meta);
175 | }
176 |
177 | void Collector::registerPtr(PtrBase* p) {
178 | p->index = pointers.size();
179 | {
180 | unique_lock lk{mutex, try_to_lock};
181 | pointers.push_back(p);
182 | }
183 |
184 | if (ClassMeta::isCreatingObj > 0) {
185 | if (auto* owner = findCreatingObj(p)) {
186 | p->isRoot = 0;
187 | owner->klass->registerSubPtr(owner, p);
188 | }
189 | }
190 | }
191 |
192 | void Collector::unregisterPtr(PtrBase* p) {
193 | PtrBase* pointer;
194 | {
195 | unique_lock lk{mutex, try_to_lock};
196 |
197 | if (p == pointers.back()) {
198 | pointers.pop_back();
199 | return;
200 | } else {
201 | swap(pointers[p->index], pointers.back());
202 | pointer = pointers[p->index];
203 | pointers.pop_back();
204 | pointer->index = p->index;
205 | }
206 | }
207 | if (!pointer->meta)
208 | return;
209 | shared_lock lk{mutex, try_to_lock};
210 | if (state == State::RootMarking) {
211 | if (p->index < nextRootMarking) {
212 | tryMarkRoot(pointer);
213 | }
214 | }
215 | }
216 |
217 | void Collector::tryMarkRoot(PtrBase* p) {
218 | if (p->isRoot == 1) {
219 | if (p->meta->color == ObjMeta::Color::White) {
220 | p->meta->color = ObjMeta::Color::Gray;
221 |
222 | unique_lock lk{mutex, try_to_lock};
223 | grayObjs.push_back(p->meta);
224 | }
225 | }
226 | }
227 |
228 | void Collector::onPointerChanged(PtrBase* p) {
229 | if (!p->meta)
230 | return;
231 |
232 | shared_lock lk{mutex, try_to_lock};
233 | switch (state) {
234 | case State::RootMarking:
235 | if (p->index < nextRootMarking)
236 | tryMarkRoot(p);
237 | break;
238 | case State::LeafMarking:
239 | tryMarkRoot(p);
240 | break;
241 | case State::Sweeping:
242 | if (p->meta->color == ObjMeta::Color::White) {
243 | // if (*p->meta < **nextSweeping) {
244 | // already passed sweeping stage.
245 | //} else {
246 | // delay to the next collection.
247 | p->meta->color = ObjMeta::Color::Black;
248 | //}
249 | }
250 | break;
251 | }
252 | }
253 |
254 | ObjMeta* Collector::findCreatingObj(PtrBase* p) {
255 | shared_lock lk{mutex, try_to_lock};
256 | // owner may not be the current one(e.g. constructor recursed)
257 | for (auto i = creatingObjs.rbegin(); i != creatingObjs.rend(); ++i) {
258 | if ((*i)->containsPtr((char*)p))
259 | return *i;
260 | }
261 | return nullptr;
262 | }
263 |
264 | ObjMeta* Collector::globalFindOwnerMeta(void* obj) {
265 | shared_lock lk{mutex, try_to_lock};
266 | auto* meta = (ObjMeta*)((char*)obj - sizeof(ObjMeta));
267 | return meta;
268 | }
269 |
270 | void Collector::collect(int stepCnt) {
271 | unique_lock lk{mutex};
272 |
273 | freeObjCntOfPrevGc = 0;
274 |
275 | switch (state) {
276 | _RootMarking:
277 | case State::RootMarking:
278 | for (; nextRootMarking < pointers.size() && stepCnt-- > 0;
279 | nextRootMarking++) {
280 | auto p = pointers[nextRootMarking];
281 | auto meta = p->meta;
282 | if (!meta)
283 | continue;
284 | // for containers
285 | auto it = meta->klass->enumPtrs(meta);
286 | for (; auto* ptr = it->getNext(); stepCnt--) {
287 | ptr->isRoot = 0;
288 | }
289 | delete it;
290 | tryMarkRoot(p);
291 | }
292 | if (nextRootMarking >= pointers.size()) {
293 | state = State::LeafMarking;
294 | nextRootMarking = 0;
295 | goto _ChildMarking;
296 | }
297 | break;
298 |
299 | _ChildMarking:
300 | case State::LeafMarking:
301 | while (grayObjs.size() && stepCnt-- > 0) {
302 | ObjMeta* o = grayObjs.back();
303 | grayObjs.pop_back();
304 | o->color = ObjMeta::Color::Black;
305 |
306 | auto cls = o->klass;
307 | auto it = cls->enumPtrs(o);
308 | for (; auto* ptr = it->getNext(); stepCnt--) {
309 | auto* meta = ptr->meta;
310 | if (!meta)
311 | continue;
312 | if (meta->color == ObjMeta::Color::White) {
313 | meta->color = ObjMeta::Color::Gray;
314 | grayObjs.push_back(meta);
315 | }
316 | }
317 | delete it;
318 | }
319 | if (!grayObjs.size()) {
320 | state = State::Sweeping;
321 | nextSweeping = metaSet.begin();
322 | goto _Sweeping;
323 | }
324 | break;
325 |
326 | _Sweeping:
327 | case State::Sweeping:
328 | for (; nextSweeping != metaSet.end() && stepCnt-- > 0;) {
329 | ObjMeta* meta = *nextSweeping;
330 | if (meta->color == ObjMeta::Color::White) {
331 | nextSweeping = metaSet.erase(nextSweeping);
332 | delete meta;
333 | freeObjCntOfPrevGc++;
334 | continue;
335 | }
336 | meta->color = ObjMeta::Color::White;
337 | ++nextSweeping;
338 | }
339 | if (nextSweeping == metaSet.end()) {
340 | state = State::RootMarking;
341 | if (metaSet.size())
342 | goto _RootMarking;
343 | }
344 | break;
345 | }
346 | }
347 |
348 | void Collector::dumpStats() {
349 | shared_lock lk{mutex, try_to_lock};
350 |
351 | printf("========= [gc] ========\n");
352 | printf("[total pointers ] %3d\n", (unsigned)pointers.size());
353 | printf("[total meta ] %3d\n", (unsigned)metaSet.size());
354 | printf("[total gray meta] %3d\n", (unsigned)grayObjs.size());
355 | auto liveCnt = 0;
356 | for (auto i : metaSet)
357 | if (i->arrayLength)
358 | liveCnt++;
359 | printf("[live objects ] %3d\n", liveCnt);
360 | printf("[last freed objs] %3d\n", freeObjCntOfPrevGc);
361 | printf("[collector state] %s\n", StateStr[(int)state]);
362 | printf("=======================\n");
363 | }
364 |
365 | } // namespace details
366 | } // namespace tgc
367 |
--------------------------------------------------------------------------------
/tgc.h:
--------------------------------------------------------------------------------
1 | /*
2 |
3 | TGC: Tiny incremental mark & sweep Garbage Collector.
4 |
5 | //////////////////////////////////////////////////////////////////////////
6 |
7 | Copyright (C) 2018 soniced@sina.com
8 |
9 | Permission is hereby granted, free of charge, to any person obtaining a copy of
10 | this software and associated documentation files (the "Software"), to deal in
11 | the Software without restriction, including without limitation the rights to
12 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
13 | the Software, and to permit persons to whom the Software is furnished to do so,
14 | subject to the following conditions:
15 |
16 | The above copyright notice and this permission notice shall be included in all
17 | copies or substantial portions of the Software.
18 |
19 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
21 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
22 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
23 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
24 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
25 |
26 | */
27 |
28 | #pragma once
29 |
30 | //#define TGC_MULTI_THREADED
31 |
32 | #include
33 | #include
34 | #include
35 | #include
36 | #include
37 | #ifdef TGC_MULTI_THREADED
38 | #include
39 | #include
40 | #endif
41 |
42 | // for STL wrappers
43 | #include
44 | #include
45 | #include