Broom 1.0.0
A thread-local C++ Garbage Collector
Loading...
Searching...
No Matches
main.cc
Go to the documentation of this file.
1#include <stdint.h>
2#include <string.h>
3
4#include <algorithm>
5#include <condition_variable>
6#include <cstdint>
7#include <limits>
8#include <mutex>
9#include <numeric>
10#include <print>
11#include <queue>
12#include <random>
13#include <stack>
14#include <string>
15#include <thread>
16
18#include "include/broom.h"
19
20constexpr const int KB = 1024;
21
22#if defined(__clang__) || defined(__GNUC__)
23#define NOINLINE __attribute__((noinline))
24#elif defined(_MSC_VER)
25#define NOINLINE __declspec(noinline)
26#endif
27
28class Job;
29
30// Global to make things simpler.
31std::mutex job_queue_mtx;
32std::mutex stdout_mtx;
33std::queue<broom::sharing_ptr<Job>> job_queue;
34std::condition_variable cv;
35bool done = false;
36std::atomic<bool> exit_producer = false;
37
38class BigThing {
39 public:
40 explicit BigThing() : bytes_(nullptr), size_(0) {}
41 // Simply allocates bytes_ as a managed region.
42 explicit BigThing(size_t size)
43 : bytes_(broom::allocate<uint8_t>(size, 0)), size_(size) {
44 assert(bytes_ != nullptr);
45 }
46 // Don't need to manage bytes_, it's GC'd.
47 ~BigThing() = default;
48
50 std::random_device rd;
51 std::mt19937 mt(rd());
52 std::uniform_int_distribution<uint16_t> dist(0, 255);
53 for (size_t i = 0; i < size_; ++i)
54 bytes_[i] = static_cast<uint8_t>(dist(mt));
55 }
56
57 BigThing(const BigThing& other) : bytes_(other.bytes_), size_(other.size_) {}
58 BigThing& operator=(const BigThing& other) = delete;
59 BigThing& operator=(BigThing&& other) = delete;
60
61 uint8_t At(size_t index) const { return bytes_[index]; }
62
63 private:
64 uint8_t* bytes_;
65 size_t size_;
66};
67
68enum class Kind {
69 kPrint,
72};
73
74class Job {
75 public:
76 // Pointers inside objects are fine.
77 explicit Job(BigThing big_thing)
78 : kind_(Kind::kProcessBigThing), big_thing_(big_thing) {}
79 // Will be ignored since it's not managed memory.
80 explicit Job(std::vector<int>&& numbers)
81 : kind_(Kind::kAddNumbers), numbers_(numbers) {}
82 // Regular pointers are fine too.
83 explicit Job(const std::string string_to_print) : kind_(Kind::kPrint) {
84 size_t s = string_to_print.size();
85 string_to_print_ = broom::allocate<char>(s + 1);
86 assert(string_to_print_ != nullptr);
87 memcpy(string_to_print_, &string_to_print[0], s);
88 string_to_print_[s] = '\0';
89 }
90 // We don't need to care about what happens to any of the inner pointers.
91 ~Job() = default;
92
94 assert(kind_ == Kind::kProcessBigThing);
95 return big_thing_;
96 }
97
98 const std::vector<int>& GetNumbers() const {
99 assert(kind_ == Kind::kAddNumbers);
100 return numbers_;
101 }
102
103 const char* GetStringToPrint() const {
104 assert(kind_ == Kind::kPrint);
105 return string_to_print_;
106 }
107
108 const Kind GetKind() const { return kind_; }
109
110 private:
111 Kind kind_;
112 // They all exist for simplicity.
113 BigThing big_thing_;
114 std::vector<int> numbers_;
115 char* string_to_print_;
116};
117
119 // We need to share our pointers, so use a broom::sharing_scope.
120 broom::sharing_scope broom_scope;
121 // Needs to be in a broom::vector because anything inside a regular
122 // std::vector is not visible to Broom.
123 broom::vector<BigThing> big_things;
124 // 10k iterations, 3 jobs per iteration.
125 // Any call to broom::allocate will potentially cause a garbage collection
126 // pass to trigger depending on the memory pressure.
127 for (size_t i = 0; i < 10000; ++i) {
128 {
129 Job* new_job = broom::allocate<Job>(1, BigThing(1 * KB));
130 // Simulate some being collected, while others are still live.
131 if (i % 1000 == 0) {
132 // Keep one every so often. All others should be GC'd.
133 big_things.push_back(new_job->GetBigThing());
134 }
135 assert(new_job != nullptr);
136 {
137 std::unique_lock lock(job_queue_mtx);
138 job_queue.push(broom::share<Job>(new_job));
139 }
140 }
141 cv.notify_all();
142
143 {
144 Job* new_job =
145 broom::allocate<Job>(1, "hello world from: " + std::to_string(i));
146 assert(new_job != nullptr);
147 {
148 std::unique_lock lock(job_queue_mtx);
149 job_queue.push(broom::share<Job>(new_job));
150 }
151 }
152 cv.notify_all();
153
154 {
155 std::vector<int> v(i / 2 > 10 ? i / 2 : 10);
156 std::iota(v.begin(), v.end(), 0);
157 Job* new_job = broom::allocate<Job>(1, std::move(v));
158 assert(new_job != nullptr);
159 {
160 std::unique_lock lock(job_queue_mtx);
161 job_queue.push(broom::share<Job>(new_job));
162 }
163 }
164 cv.notify_all();
165 // Simulate sometimes going to sleep because there are no jobs to enqueue.
166 if (i % 500 == 0) {
167#ifdef FORCE_COLLECT
168 // Since we will go to sleep, we can force a full sweep here.
170#endif
171 std::this_thread::sleep_for(std::chrono::milliseconds(200));
172 }
173 }
174
175 {
176 std::unique_lock lock(job_queue_mtx);
177 done = true;
178 cv.notify_all();
179 }
180
181 while (!exit_producer.load(std::memory_order_acquire));
182
183 // Should be alive throughout
184 for (const auto& big_thing : big_things) {
185 if (big_thing.At(1024) == 255) {
186 std::unique_lock stdout_lock(stdout_mtx);
187 std::println("Found 255 @ 1024!");
188 }
189 }
190 // Broom will run destructors here.
191}
192
193void ProcessJob(Job* job) {
194 switch (job->GetKind()) {
195 case Kind::kPrint: {
196 std::unique_lock stdout_lock(stdout_mtx);
197 std::println("{}", job->GetStringToPrint());
198 } break;
199 case Kind::kAddNumbers: {
200 const auto& numbers = job->GetNumbers();
201 int64_t result =
202 std::accumulate(std::begin(numbers), std::end(numbers), 0);
203 std::unique_lock stdout_lock(stdout_mtx);
204 std::println("result = {}", result);
205 } break;
208 std::unique_lock stdout_lock(stdout_mtx);
209 std::println("Filled with random bytes");
210 break;
211 }
212}
213
214void Worker() {
215 // No broom scope needed here. We aren't GCing anything on this thread.
216 std::unique_lock<std::mutex> lock(job_queue_mtx);
217 while (!(done && job_queue.empty())) {
218 cv.wait(lock, [&]() { return done || !job_queue.empty(); });
219 if (!job_queue.empty()) {
220 // Anchor the lifetime of the job in the current thread inside this scope.
221 // This won't cause the pointer to become garbage collected. Instead, it
222 // will simply let the garbage collector know that this thread is done
223 // using the pointer and it no longer needs to be pinned for this
224 // particular use.
226 job_queue.pop();
227 // We can safely use this pointer up until the end of the scope.
229 }
230 }
231}
232
233int main() {
234 std::thread job_producer(ProduceJobs);
235 const auto core_count = std::thread::hardware_concurrency();
236 std::vector<std::thread> workers(core_count / 2);
237 for (size_t i = 0; i < core_count / 2; ++i) {
238 workers[i] = std::thread(Worker);
239 }
240
241 for (size_t i = 0; i < core_count / 2; ++i) {
242 workers[i].join();
243 }
244 exit_producer.store(true, std::memory_order_release);
245 job_producer.join();
246 return 0;
247}
BigThing(const BigThing &other)
Definition main.cc:57
BigThing & operator=(const BigThing &other)=delete
void FillWithRandomBytes()
Definition main.cc:49
BigThing()
Definition main.cc:40
~BigThing()=default
BigThing(size_t size)
Definition main.cc:42
uint8_t At(size_t index) const
Definition main.cc:61
BigThing & operator=(BigThing &&other)=delete
Definition main.cc:74
const std::vector< int > & GetNumbers() const
Definition main.cc:98
Job(BigThing big_thing)
Definition main.cc:77
BigThing GetBigThing() const
Definition main.cc:93
~Job()=default
const Kind GetKind() const
Definition main.cc:108
Job(const std::string string_to_print)
Definition main.cc:83
const char * GetStringToPrint() const
Definition main.cc:103
Job(std::vector< int > &&numbers)
Definition main.cc:80
T * to_unsafe_ptr() const
Definition broom.h:651
std::queue< T, broom::deque< T > > queue
Definition broom-queue.h:12
void force_slow_collection()
Definition api.cc:40
std::condition_variable cv
Definition main.cc:34
bool done
Definition main.cc:35
std::atomic< bool > exit_producer
Definition main.cc:36
std::mutex stdout_mtx
Definition main.cc:32
void Worker()
Definition main.cc:214
std::queue< broom::sharing_ptr< Job > > job_queue
Definition main.cc:33
Kind
Definition main.cc:68
@ kProcessBigThing
@ kAddNumbers
@ kPrint
void ProcessJob(Job *job)
Definition main.cc:193
void ProduceJobs()
Definition main.cc:118
std::mutex job_queue_mtx
Definition main.cc:31
int main()
Definition main.cc:233
constexpr const int KB
Definition main.cc:20