├── .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 46 | #include 47 | #include 48 | #include 49 | 50 | namespace tgc { 51 | namespace details { 52 | 53 | using namespace std; 54 | 55 | #ifndef TGC_MULTI_THREADED 56 | 57 | constexpr int try_to_lock = 0; 58 | 59 | struct shared_mutex {}; 60 | struct unique_lock { 61 | unique_lock(...) {} 62 | }; 63 | struct shared_lock { 64 | shared_lock(...) {} 65 | }; 66 | template 67 | struct atomic { 68 | T value; 69 | atomic(T v) : value{v} {} 70 | void operator++(int) { value++; } 71 | void operator--(int) { value--; } 72 | operator const T&() const { return value; } 73 | bool operator==(const T& r) const { return value == r; } 74 | }; 75 | 76 | #endif 77 | 78 | class ObjMeta; 79 | class ClassMeta; 80 | class PtrBase; 81 | class IPtrEnumerator; 82 | 83 | ////////////////////////////////////////////////////////////////////////// 84 | 85 | class ObjMeta { 86 | public: 87 | enum class Color : unsigned char { White, Gray, Black }; 88 | using LengthType = unsigned short; 89 | struct Less { 90 | bool operator()(ObjMeta* x, ObjMeta* y) const { return *x < *y; } 91 | }; 92 | 93 | ClassMeta* klass = nullptr; 94 | atomic color = Color::White; 95 | LengthType arrayLength = 0; 96 | 97 | static char* dummyObjPtr; 98 | 99 | ObjMeta(ClassMeta* c, char* o, size_t n) 100 | : klass(c), arrayLength((LengthType)n) {} 101 | ~ObjMeta() { 102 | if (arrayLength) 103 | destroy(); 104 | } 105 | void operator delete(void* c); 106 | bool operator<(ObjMeta& r) const; 107 | bool containsPtr(char* p); 108 | char* objPtr() const; 109 | void destroy(); 110 | }; 111 | 112 | static_assert(sizeof(ObjMeta) <= sizeof(void*) * 2, 113 | "too large for small allocation"); 114 | 115 | ////////////////////////////////////////////////////////////////////////// 116 | 117 | class IPtrEnumerator { 118 | public: 119 | virtual ~IPtrEnumerator() {} 120 | virtual const PtrBase* getNext() = 0; 121 | 122 | void* operator new(size_t sz) { 123 | static char buf[255]; 124 | assert(sz <= sizeof(buf)); 125 | return buf; 126 | } 127 | void operator delete(void*) {} 128 | }; 129 | 130 | class ObjPtrEnumerator : public IPtrEnumerator { 131 | size_t subPtrIdx = 0, arrayElemIdx = 0; 132 | ObjMeta* meta = nullptr; 133 | 134 | public: 135 | ObjPtrEnumerator(ObjMeta* m) : meta(m) {} 136 | const PtrBase* getNext() override; 137 | }; 138 | 139 | template 140 | struct PtrEnumerator : ObjPtrEnumerator { 141 | using ObjPtrEnumerator::ObjPtrEnumerator; 142 | }; 143 | 144 | ////////////////////////////////////////////////////////////////////////// 145 | 146 | class ClassMeta { 147 | public: 148 | enum class State : unsigned char { Unregistered, Registered }; 149 | enum class MemRequest { Alloc, Dctor, Dealloc, NewPtrEnumerator }; 150 | using MemHandler = void* (*)(ClassMeta* cls, MemRequest r, void* param); 151 | using OffsetType = unsigned short; 152 | using SizeType = unsigned short; 153 | 154 | MemHandler memHandler = nullptr; 155 | vector* subPtrOffsets = nullptr; 156 | State state = State::Unregistered; 157 | SizeType size = 0; 158 | 159 | #ifdef TGC_MULTI_THREADED 160 | shared_mutex mutex; 161 | #else 162 | static shared_mutex mutex; 163 | #endif 164 | 165 | static atomic isCreatingObj; 166 | static ClassMeta dummy; 167 | 168 | ClassMeta() {} 169 | ClassMeta(MemHandler h, SizeType sz) : memHandler(h), size(sz) {} 170 | ~ClassMeta() { delete subPtrOffsets; } 171 | 172 | ObjMeta* newMeta(size_t objCnt); 173 | void registerSubPtr(ObjMeta* owner, PtrBase* p); 174 | void endNewMeta(ObjMeta* meta, bool failed); 175 | IPtrEnumerator* enumPtrs(ObjMeta* m) { 176 | return (IPtrEnumerator*)memHandler(this, MemRequest::NewPtrEnumerator, m); 177 | } 178 | 179 | template 180 | static ClassMeta* get() { 181 | return &Holder::inst; 182 | } 183 | 184 | private: 185 | template 186 | struct Holder { 187 | static void* MemHandler(ClassMeta* cls, MemRequest r, void* param) { 188 | switch (r) { 189 | case MemRequest::Alloc: { 190 | auto cnt = (size_t)param; 191 | auto* p = new char[cls->size * cnt + sizeof(ObjMeta)]; 192 | return new (p) ObjMeta(cls, p + sizeof(ObjMeta), cnt); 193 | } 194 | case MemRequest::Dealloc: { 195 | auto meta = (ObjMeta*)param; 196 | delete[](char*) meta; 197 | } break; 198 | case MemRequest::Dctor: { 199 | auto meta = (ObjMeta*)param; 200 | auto p = (T*)meta->objPtr(); 201 | for (size_t i = 0; i < meta->arrayLength; i++, p++) { 202 | p->~T(); 203 | } 204 | } break; 205 | case MemRequest::NewPtrEnumerator: { 206 | auto meta = (ObjMeta*)param; 207 | return new PtrEnumerator(meta); 208 | } break; 209 | } 210 | return nullptr; 211 | } 212 | 213 | static ClassMeta inst; 214 | }; 215 | }; 216 | 217 | template 218 | ClassMeta ClassMeta::Holder::inst{MemHandler, sizeof(T)}; 219 | 220 | #ifndef TGC_MULTI_THREADED 221 | static_assert(sizeof(ClassMeta) <= sizeof(void*) * 3, 222 | "too large for lambda heavy programs"); 223 | #endif 224 | 225 | ////////////////////////////////////////////////////////////////////////// 226 | 227 | class PtrBase { 228 | friend class Collector; 229 | friend class ClassMeta; 230 | 231 | public: 232 | ObjMeta* getMeta() { return meta; } 233 | 234 | protected: 235 | PtrBase(); 236 | PtrBase(void* obj); 237 | ~PtrBase(); 238 | void onPtrChanged(); 239 | 240 | protected: 241 | ObjMeta* meta = nullptr; 242 | mutable unsigned int isRoot : 1; 243 | unsigned int index : 31; 244 | }; 245 | 246 | template 247 | class GcPtr : public PtrBase { 248 | public: 249 | using pointee = T; 250 | using element_type = T; // compatible with std::shared_ptr 251 | 252 | template 253 | friend class GcPtr; 254 | 255 | public: 256 | // Constructors 257 | 258 | GcPtr() {} 259 | GcPtr(ObjMeta* meta) { reset((T*)meta->objPtr(), meta); } 260 | explicit GcPtr(T* obj) : PtrBase(obj), p(obj) {} 261 | template 262 | GcPtr(const GcPtr& r) { 263 | reset(static_cast(r.p), r.meta); 264 | } 265 | GcPtr(const GcPtr& r) { reset(r.p, r.meta); } 266 | GcPtr(GcPtr&& r) { 267 | reset(r.p, r.meta); 268 | r = nullptr; 269 | } 270 | 271 | // Operators 272 | 273 | template 274 | GcPtr& operator=(const GcPtr& r) { 275 | reset(r.p, r.meta); 276 | return *this; 277 | } 278 | GcPtr& operator=(const GcPtr& r) { 279 | reset(r.p, r.meta); 280 | return *this; 281 | } 282 | GcPtr& operator=(GcPtr&& r) { 283 | reset(r.p, r.meta); 284 | r.meta = 0; 285 | r.p = 0; 286 | return *this; 287 | } 288 | T* operator->() const { return p; } 289 | T& operator*() const { return *p; } 290 | explicit operator bool() const { return p && meta; } 291 | bool operator==(const GcPtr& r) const { return p == r.p; } 292 | bool operator!=(const GcPtr& r) const { return p != r.p; } 293 | GcPtr& operator=(T* ptr) = delete; 294 | GcPtr& operator=(nullptr_t) { 295 | meta = 0; 296 | p = 0; 297 | return *this; 298 | } 299 | bool operator<(const GcPtr& r) const { return *p < *r.p; } 300 | 301 | // Methods 302 | 303 | void reset(T* o, ObjMeta* n) { 304 | p = o; 305 | meta = n; 306 | onPtrChanged(); 307 | } 308 | 309 | protected: 310 | T* p = nullptr; 311 | }; 312 | 313 | static_assert(sizeof(GcPtr) <= sizeof(void*) * 3, 314 | "too large for small object"); 315 | 316 | template 317 | class gc : public GcPtr { 318 | using base = GcPtr; 319 | 320 | public: 321 | using GcPtr::GcPtr; 322 | gc() {} 323 | gc(nullptr_t) {} 324 | gc(ObjMeta* o) : base(o) {} 325 | explicit gc(T* o) : base(o) {} 326 | }; 327 | 328 | #define TGC_DECL_AUTO_BOX(T, GcAliasName) \ 329 | template <> \ 330 | class details::gc : public details::GcPtr { \ 331 | public: \ 332 | using GcPtr::GcPtr; \ 333 | gc(const T& i) : GcPtr(details::gc_new_meta(1, i)) {} \ 334 | gc() {} \ 335 | gc(nullptr_t) {} \ 336 | operator T&() { return operator*(); } \ 337 | operator T&() const { return operator*(); } \ 338 | }; \ 339 | using GcAliasName = gc; 340 | 341 | ////////////////////////////////////////////////////////////////////////// 342 | 343 | class Collector { 344 | friend class ClassMeta; 345 | friend class PtrBase; 346 | 347 | public: 348 | static Collector* get(); 349 | void onPointerChanged(PtrBase* p); 350 | void registerPtr(PtrBase* p); 351 | void unregisterPtr(PtrBase* p); 352 | ObjMeta* globalFindOwnerMeta(void* obj); 353 | void collect(int stepCnt); 354 | void dumpStats(); 355 | 356 | enum class State { RootMarking, LeafMarking, Sweeping, MaxCnt }; 357 | 358 | private: 359 | Collector(); 360 | ~Collector(); 361 | 362 | void tryMarkRoot(PtrBase* p); 363 | ObjMeta* findCreatingObj(PtrBase* p); 364 | void addMeta(ObjMeta* meta); 365 | 366 | private: 367 | using MetaSet = unordered_set; 368 | 369 | vector pointers; 370 | vector grayObjs; 371 | MetaSet metaSet; 372 | // stack is no feasible for multi-threaded version. 373 | list creatingObjs; 374 | MetaSet::iterator nextSweeping; 375 | size_t nextRootMarking = 0; 376 | State state = State::RootMarking; 377 | shared_mutex mutex; 378 | int freeObjCntOfPrevGc; 379 | 380 | static Collector* inst; 381 | }; 382 | 383 | inline void gc_collect(int steps = 256) { 384 | Collector::get()->collect(steps); 385 | } 386 | 387 | inline void gc_dumpStats() { 388 | Collector::get()->dumpStats(); 389 | } 390 | 391 | template 392 | ObjMeta* gc_new_meta(size_t len, Args&&... args) { 393 | auto* cls = ClassMeta::get(); 394 | auto* meta = cls->newMeta(len); 395 | 396 | size_t i = 0; 397 | auto* p = (T*)meta->objPtr(); 398 | try { 399 | for (; i < len; i++, p++) 400 | new (p) T(forward(args)...); 401 | } catch (...) { 402 | for (auto j = i; j > 0; j--, p--) { 403 | p->~T(); 404 | } 405 | cls->endNewMeta(meta, true); 406 | throw; 407 | } 408 | 409 | cls->endNewMeta(meta, false); 410 | return meta; 411 | } 412 | 413 | template 414 | void gc_delete(gc& c) { 415 | if (c) { 416 | c.getMeta()->destroy(); 417 | c = nullptr; 418 | } 419 | } 420 | 421 | // used as shared_from_this 422 | template 423 | gc gc_from(T* o) { 424 | return gc(o); 425 | } 426 | 427 | // used as std::shared_ptr 428 | template 429 | gc gc_static_pointer_cast(gc& from) { 430 | return from; 431 | } 432 | 433 | // used as std::shared_ptr 434 | template 435 | gc gc_dynamic_pointer_cast(gc& from) { 436 | gc r; 437 | r.reset(dynamic_cast(from.operator->()), from.getMeta()); 438 | return r; 439 | } 440 | 441 | template 442 | gc gc_new(Args&&... args) { 443 | return gc_new_meta(1, forward(args)...); 444 | } 445 | 446 | template 447 | gc gc_new_array(size_t len, Args&&... args) { 448 | return gc_new_meta(len, forward(args)...); 449 | } 450 | 451 | ////////////////////////////////////////////////////////////////////////// 452 | /// Function 453 | 454 | template 455 | class gc_function; 456 | 457 | template 458 | class gc_function { 459 | public: 460 | gc_function() {} 461 | 462 | template 463 | gc_function(F&& f) : callable(gc_new_meta>(1, forward(f))) {} 464 | 465 | template 466 | gc_function& operator=(F&& f) { 467 | callable = gc_new_meta>(1, forward(f)); 468 | return *this; 469 | } 470 | 471 | template 472 | R operator()(U&&... a) const { 473 | return callable->call(forward(a)...); 474 | } 475 | 476 | explicit operator bool() const { return (bool)callable; } 477 | bool operator==(const gc_function& r) const { return callable == r.callable; } 478 | bool operator!=(const gc_function& r) const { return callable != r.callable; } 479 | 480 | private: 481 | struct Callable { 482 | virtual ~Callable() {} 483 | virtual R call(A... a) = 0; 484 | }; 485 | 486 | template 487 | struct Imp : Callable { 488 | F f; 489 | Imp(F&& ff) : f(ff) {} 490 | R call(A... a) override { return f(a...); } 491 | }; 492 | 493 | private: 494 | gc callable; 495 | }; 496 | 497 | ////////////////////////////////////////////////////////////////////////// 498 | // Wrap STL Containers 499 | ////////////////////////////////////////////////////////////////////////// 500 | 501 | template 502 | struct ContainerPtrEnumerator : IPtrEnumerator { 503 | C* o; 504 | typename C::iterator it; 505 | ContainerPtrEnumerator(ObjMeta* m) : o((C*)m->objPtr()), it(o->begin()) {} 506 | bool hasNext() { return it != o->end(); } 507 | }; 508 | 509 | ////////////////////////////////////////////////////////////////////////// 510 | /// Vector 511 | /// vector elements are not stored contiguously due to implementation 512 | /// limitation. use gc_new_array for better performance. 513 | 514 | template 515 | class gc_vector : public gc>> { 516 | public: 517 | using gc>>::gc; 518 | gc& operator[](int idx) { return (*this->p)[idx]; } 519 | }; 520 | 521 | template 522 | struct PtrEnumerator>> : ContainerPtrEnumerator>> { 523 | using ContainerPtrEnumerator>>::ContainerPtrEnumerator; 524 | 525 | const PtrBase* getNext() override { 526 | if (!this->hasNext()) 527 | return nullptr; 528 | return &*this->it++; 529 | } 530 | }; 531 | 532 | template 533 | gc_vector gc_new_vector(Args&&... args) { 534 | return gc_new_meta>>(1, forward(args)...); 535 | } 536 | 537 | template 538 | void gc_delete(gc_vector& p) { 539 | for (auto& i : *p) { 540 | gc_delete(i); 541 | } 542 | p->clear(); 543 | } 544 | 545 | ////////////////////////////////////////////////////////////////////////// 546 | /// Deque 547 | 548 | template 549 | class gc_deque : public gc>> { 550 | public: 551 | using gc>>::gc; 552 | gc& operator[](int idx) { return (*this->p)[idx]; } 553 | }; 554 | 555 | template 556 | struct PtrEnumerator>> : ContainerPtrEnumerator>> { 557 | using ContainerPtrEnumerator>>::ContainerPtrEnumerator; 558 | 559 | const PtrBase* getNext() override { 560 | if (!this->hasNext()) 561 | return nullptr; 562 | return &*this->it++; 563 | } 564 | }; 565 | 566 | template 567 | gc_deque gc_new_deque(Args&&... args) { 568 | return gc_new_meta>>(1, forward(args)...); 569 | } 570 | 571 | template 572 | void gc_delete(gc_deque& p) { 573 | for (auto& i : *p) { 574 | gc_delete(i); 575 | } 576 | p->clear(); 577 | } 578 | 579 | ////////////////////////////////////////////////////////////////////////// 580 | /// List 581 | 582 | template 583 | using gc_list = gc>>; 584 | 585 | template 586 | struct PtrEnumerator>> : ContainerPtrEnumerator>> { 587 | using ContainerPtrEnumerator>>::ContainerPtrEnumerator; 588 | 589 | const PtrBase* getNext() override { 590 | if (!this->hasNext()) 591 | return nullptr; 592 | return &*this->it++; 593 | } 594 | }; 595 | 596 | template 597 | gc_list gc_new_list(Args&&... args) { 598 | return gc_new_meta>>(1, forward(args)...); 599 | } 600 | 601 | template 602 | void gc_delete(gc_list& p) { 603 | for (auto& i : *p) { 604 | gc_delete(i); 605 | } 606 | p->clear(); 607 | } 608 | 609 | ////////////////////////////////////////////////////////////////////////// 610 | /// Map 611 | /// TODO: NOT support using gc object as key... 612 | 613 | template 614 | class gc_map : public gc>> { 615 | public: 616 | using gc>>::gc; 617 | gc& operator[](const K& k) { return (*this->p)[k]; } 618 | }; 619 | 620 | template 621 | struct PtrEnumerator>> : ContainerPtrEnumerator>> { 622 | using ContainerPtrEnumerator>>::ContainerPtrEnumerator; 623 | 624 | const PtrBase* getNext() override { 625 | if (!this->hasNext()) 626 | return nullptr; 627 | auto* ret = &this->it->second; 628 | ++this->it; 629 | return ret; 630 | } 631 | }; 632 | 633 | template 634 | gc_map gc_new_map(Args&&... args) { 635 | return gc_new_meta>>(1, forward(args)...); 636 | } 637 | 638 | template 639 | void gc_delete(gc_map& p) { 640 | for (auto& i : *p) { 641 | gc_delete(i->value); 642 | } 643 | p->clear(); 644 | } 645 | 646 | ////////////////////////////////////////////////////////////////////////// 647 | /// HashMap 648 | /// TODO: NOT support using gc object as key... 649 | 650 | template 651 | class gc_unordered_map : public gc>> { 652 | public: 653 | using gc>>::gc; 654 | gc& operator[](const K& k) { return (*this->p)[k]; } 655 | }; 656 | 657 | template 658 | struct PtrEnumerator>> 659 | : ContainerPtrEnumerator>> { 660 | using ContainerPtrEnumerator>>::ContainerPtrEnumerator; 661 | 662 | const PtrBase* getNext() override { 663 | if (!this->hasNext()) 664 | return nullptr; 665 | auto* ret = &this->it->second; 666 | ++this->it; 667 | return ret; 668 | } 669 | }; 670 | 671 | template 672 | gc_unordered_map gc_new_unordered_map(Args&&... args) { 673 | return gc_new_meta>>(1, forward(args)...); 674 | } 675 | template 676 | void gc_delete(gc_unordered_map& p) { 677 | for (auto& i : *p) { 678 | gc_delete(i.second); 679 | } 680 | p->clear(); 681 | } 682 | 683 | ////////////////////////////////////////////////////////////////////////// 684 | /// Set 685 | 686 | template 687 | using gc_set = gc>>; 688 | 689 | template 690 | struct PtrEnumerator>> : ContainerPtrEnumerator>> { 691 | using ContainerPtrEnumerator>>::ContainerPtrEnumerator; 692 | 693 | const PtrBase* getNext() override { 694 | if (!this->hasNext()) 695 | return nullptr; 696 | return &*this->it++; 697 | } 698 | }; 699 | 700 | template 701 | gc_set gc_new_set(Args&&... args) { 702 | return gc_new_meta>>(1, forward(args)...); 703 | } 704 | 705 | template 706 | void gc_delete(gc_set& p) { 707 | for (auto i : *p) { 708 | gc_delete(i); 709 | } 710 | p->clear(); 711 | } 712 | 713 | } // namespace details 714 | 715 | ////////////////////////////////////////////////////////////////////////// 716 | // Public APIs 717 | 718 | using details::gc; 719 | using details::gc_collect; 720 | using details::gc_dumpStats; 721 | using details::gc_dynamic_pointer_cast; 722 | using details::gc_from; 723 | using details::gc_function; 724 | using details::gc_new; 725 | using details::gc_new_array; 726 | using details::gc_static_pointer_cast; 727 | 728 | using details::gc_new_vector; 729 | using details::gc_vector; 730 | 731 | using details::gc_deque; 732 | using details::gc_new_deque; 733 | 734 | using details::gc_list; 735 | using details::gc_new_list; 736 | 737 | using details::gc_map; 738 | using details::gc_new_map; 739 | 740 | using details::gc_new_set; 741 | using details::gc_set; 742 | 743 | using details::gc_new_unordered_map; 744 | using details::gc_unordered_map; 745 | 746 | TGC_DECL_AUTO_BOX(char, gc_char); 747 | TGC_DECL_AUTO_BOX(unsigned char, gc_uchar); 748 | TGC_DECL_AUTO_BOX(short, gc_short); 749 | TGC_DECL_AUTO_BOX(unsigned short, gc_ushort); 750 | TGC_DECL_AUTO_BOX(int, gc_int); 751 | TGC_DECL_AUTO_BOX(unsigned int, gc_uint); 752 | TGC_DECL_AUTO_BOX(float, gc_float); 753 | TGC_DECL_AUTO_BOX(double, gc_double); 754 | TGC_DECL_AUTO_BOX(long, gc_long); 755 | TGC_DECL_AUTO_BOX(unsigned long, gc_ulong); 756 | TGC_DECL_AUTO_BOX(std::string, gc_string); 757 | 758 | } // namespace tgc 759 | --------------------------------------------------------------------------------