Libraryless. Click here for Pure Java version (6432L/37K).
1 | // Previously used class "Q" (#1000934) used 328 bytes when idle. |
2 | // Now we're at 32 (factor 10+). |
3 | // And we can evaporate this even further by merging the queue |
4 | // with the object it belongs to. Probably we'll end up paying nothing |
5 | // for an idle object while still enjoying the experience of being |
6 | // able to start a thread instantly whenever needed. |
7 | |
8 | sclass CompactQ extends Meta is AutoCloseable { |
9 | |
10 | // Object size at this point: 12 bytes for Java + 4 bytes for Meta |
11 | // (assuming you have 32 GB or less of Java RAM) |
12 | // |
13 | // So let's keep saving space. |
14 | |
15 | // Step 1 - no name... well, no reserved name field, anyway. |
16 | // Most queues can probably be identified from context. |
17 | // if you want to give your event queue a name - put it in Meta |
18 | // with these convenient procedures: |
19 | |
20 | S name() { ret or2((S) metaGet("name"), toString()); } |
21 | void name(S name) { metaSet(+name); } |
22 | |
23 | // Object size now: Still 16 bytes unless you foolishly insist on a |
24 | // custom queue name which you could definitely achieve in a much |
25 | // more efficient way (hint _subclassing_). |
26 | |
27 | // Now we do need to expend 4 more bytes. The items in the queue |
28 | // have to go somewhere. But! |
29 | |
30 | // No syncLinkedList or something - just an AppendableChain which |
31 | // is a minimalist approach but above the honor level of elegance |
32 | // and flexibility and absolutely not in the way in these circumstances. |
33 | // It's all O(1) after all. |
34 | // |
35 | // All sync is done on the main object BTW. (CompactQ.this) |
36 | |
37 | AppendableChain<Runnable> q; |
38 | |
39 | // Object size now: 20b |
40 | |
41 | // retired also goes to meta. In the end you don't even notice a |
42 | // difference. It's just another setter and getter and they are |
43 | // clearly not hard to make either. |
44 | // |
45 | // Of course, you will notice that we store "unretired" as null |
46 | // (meaning no entry at all, saving all the space) instead of false; |
47 | // even in the case that someone retires their event queue and then |
48 | // _unretires_ it. |
49 | // |
50 | // In the end, one can say we are really really pedantic but we do |
51 | // have a style. |
52 | |
53 | synchronized bool retired() { ret metaGet("retired") != null; } |
54 | synchronized void retired(bool b) { metaPut("retired", trueOrNull(b)); } |
55 | |
56 | // Object size: still 20b. Miraculous! |
57 | // |
58 | // ...Here comes a good one. |
59 | |
60 | // The "enter" field is an optional thread ownership marker, e.g. |
61 | // for modules in the Java OS because otherwise thread control is |
62 | // a nightmare. |
63 | // |
64 | // Well... it still kind of is. But I see the way forward with one |
65 | // clean re-architecture that we should one day just do. We just need |
66 | // central instances of thread managing. This cannot be decentra- |
67 | // lized. |
68 | // |
69 | // Oh, and having slashed absolutely everything else, we keep this |
70 | // one as an actual field. |
71 | // |
72 | // The field type is so a customer's enter procedure can do |
73 | // whatever fancy things they fashion as long as they provide an |
74 | // AutoCloseable that will be called for cleanup. |
75 | |
76 | IF0<AutoCloseable> enter; |
77 | |
78 | // New size: 24b |
79 | |
80 | // We do want to know who is servicing us currently... |
81 | |
82 | Thread thread; // 28b |
83 | bool startingThread; |
84 | |
85 | // ...and finally the biggest boolean ever brings us to 32 bytes. |
86 | |
87 | // Optional monitoring |
88 | |
89 | ifdef CompactQ_Stats |
90 | new AtomicLong bouts; // how often we have stopped and started |
91 | int biggestSize; // how long the queue was at the worst moment |
92 | endifdef |
93 | |
94 | AutoCloseable enter() { ret callF(enter); } |
95 | |
96 | synchronized bool running() { ret thread != null; } |
97 | |
98 | bool done() { ret !running() && (isEmpty() || retired() || !licensed()); } |
99 | |
100 | // This is way smarter than the half-awake sleep(1) method |
101 | // Basically we take a place at the end of the line and when we're |
102 | // at the counter we see if anyone came in behind us. And repeat |
103 | // if necessary. |
104 | // |
105 | // Note that for this to work reliably we require that items in |
106 | // the queue are auto-closed in case they can't be dispatched |
107 | void waitUntilDone { |
108 | while (!done()) |
109 | putFlagInPipe(); |
110 | } |
111 | |
112 | void putFlagInPipe() { |
113 | flagSleep(flag -> add(raiseFlagOnRunAndClose(flag))); |
114 | } |
115 | |
116 | // Yes, in JavaX functions can have any number of names |
117 | public synchronized void close aka cancel() { |
118 | retired(true); |
119 | clear(); |
120 | if (thread == null) ret; |
121 | // threadBeingCancelled = new WeakReference(thread); // optional |
122 | cancelAndInterruptThread(thread); |
123 | thread = null; |
124 | } |
125 | |
126 | // Forget all the entries but don't cancel or interrupt currently |
127 | // executing task |
128 | public void clear { |
129 | L<Runnable> items; |
130 | synchronized { |
131 | items = cloneList(q); |
132 | q = null; |
133 | } |
134 | cleanUp(items); |
135 | } |
136 | |
137 | // append to q (do later) |
138 | void add(Runnable r) { |
139 | if (r == null) ret; |
140 | synchronized { |
141 | // cleanly reject if necessary |
142 | if (retired()) ret with cleanUp(r); |
143 | |
144 | q = chainPlus(q, r); |
145 | ifdef CompactQ_Stats biggestSize = max(size(), biggestSize); endifdef |
146 | } |
147 | checkForAction(); |
148 | } |
149 | |
150 | // prepend to q (do next) |
151 | void addInFront(Runnable r) { |
152 | if (r == null) ret; |
153 | synchronized { |
154 | // cleanly reject if necessary |
155 | if (retired()) ret with cleanUp(r); |
156 | |
157 | q = itemPlusChain(r, q); |
158 | ifdef CompactQ_Stats biggestSize = max(size(), biggestSize); endifdef |
159 | } |
160 | checkForAction(); |
161 | } |
162 | |
163 | protected void checkForAction() { |
164 | synchronized { |
165 | if (done() || running() || startingThread) |
166 | ret; |
167 | // There seems to be work, so we start a thread. |
168 | set startingThread; |
169 | } |
170 | |
171 | try { |
172 | // At this point, we are no longer synchronized, but |
173 | // we know we are uncontested. |
174 | // No one else will try to start a thread. |
175 | temp enter(); |
176 | startThread(name(), () -> { |
177 | try { |
178 | temp enter(); |
179 | |
180 | // Put us in the book. |
181 | synchronized { |
182 | startingThread = false; |
183 | thread = currentThread(); |
184 | } |
185 | |
186 | // And off we go! |
187 | _bout(); |
188 | } finally { |
189 | thread = null; |
190 | startingThread = false; |
191 | } |
192 | }); |
193 | } on fail { |
194 | close(); |
195 | } |
196 | } |
197 | |
198 | synchronized Runnable _grabWork() { |
199 | Runnable r = first(q); |
200 | q = popFirst(q); |
201 | ret r; |
202 | } |
203 | |
204 | // work as long there is work |
205 | void _bout { |
206 | ifdef CompactQ_Stats inc(bouts); endifdef |
207 | while (licensed() && !retired()) { |
208 | Runnable r = _grabWork(); |
209 | if (r != null) |
210 | pcall { r.run(); } |
211 | else { |
212 | onIdle(); |
213 | |
214 | // Try once more because onIdle may have strategically waited |
215 | // for 1 ms or something to see if more work came in. |
216 | |
217 | if ((r = _grabWork()) != null) |
218 | pcall { r.run(); } // get back in the game |
219 | else |
220 | break; |
221 | } |
222 | } |
223 | } |
224 | |
225 | synchronized bool isEmpty() { ret q == null; } |
226 | synchronized int size() { ret l(q); } |
227 | |
228 | O mutex() { this; } // clients can synchronize on this |
229 | |
230 | // export procedure following JavaX synchronization rules |
231 | // (only synchronize on one object at a time. exceptions granted |
232 | // when proof of harmlessness is given.) |
233 | synchronized L<Runnable> snapshot() { ret cloneList_unsynced(q); } |
234 | |
235 | // you can override this |
236 | void onIdle {} |
237 | } |
Began life as a copy of #1000934
download show line numbers debug dex old transpilations
Travelled to 3 computer(s): bhatertpkbcr, mowyntqkapby, mqqgnosmbjvj
No comments. add comment
Snippet ID: | #1032857 |
Snippet name: | CompactQ - Q (thread-based queue) but in compact - only 32 bytes when idle (including Java header!) |
Eternal ID of this version: | #1032857/76 |
Text MD5: | 32da1580b4fb6dbd8109b9d1b1e8aaa0 |
Transpilation MD5: | 8972134aa4e72bed0ed2fa022e28a3a7 |
Author: | stefan |
Category: | javax |
Type: | JavaX fragment (include) |
Public (visible to everyone): | Yes |
Archived (hidden from active list): | No |
Created/modified: | 2021-10-19 21:48:36 |
Source code size: | 7139 bytes / 237 lines |
Pitched / IR pitched: | No / No |
Views / Downloads: | 354 / 758 |
Version history: | 75 change(s) |
Referenced in: | [show references] |