<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged fsm-api</title>
<link>https://coderlegion.com/tag/fsm-api</link>
<description>Powered by Question2Answer</description>
<item>
<title>Benchmarking the FSM: Pure Strings, Already Fast — and About to Get Faster</title>
<link>https://coderlegion.com/26350/benchmarking-the-fsm-pure-strings-already-fast-and-about-to-get-faster</link>
<description>&lt;h2 id=&quot;benchmarking-the-fsm-pure-strings-already-fast-and-about-to-get-faster&quot;&gt;Benchmarking the FSM: Pure Strings, Already Fast — and About to Get Faster&lt;/h2&gt;
&lt;p&gt;There's a particular reaction you tend to get when you tell an experienced systems programmer that your architecture is string-backed:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;&quot;Don't use strings.&quot;&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It's almost reflexive.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=15571909521889431178&quot; alt=&quot;Stick figure teacher shown pointing at the white board where each white board has a different rule written on it.&quot;&gt;&lt;/p&gt;
&lt;p&gt;Use enums.&lt;/p&gt;
&lt;p&gt;Use integers.&lt;/p&gt;
&lt;p&gt;Use precomputed IDs.&lt;/p&gt;
&lt;p&gt;Avoid dictionary lookups.&lt;/p&gt;
&lt;p&gt;Watch the garbage collector.&lt;/p&gt;
&lt;p&gt;And, above all, don't put strings anywhere near a hot loop.&lt;/p&gt;
&lt;p&gt;There's good engineering history behind those instincts. Strings &lt;em&gt;can&lt;/em&gt; be expensive. Poorly designed string-heavy systems can generate allocations, create unnecessary comparisons, and turn perfectly reasonable abstractions into performance problems.&lt;/p&gt;
&lt;p&gt;But there's a problem with rules of thumb:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Eventually, you have to measure.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;So that's what I did.&lt;/p&gt;
&lt;p&gt;The current FSM_API is &lt;strong&gt;purely string-backed&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Not string-backed with an integer cache hiding underneath.&lt;/p&gt;
&lt;p&gt;Not enums converted to strings at the API boundary.&lt;/p&gt;
&lt;p&gt;Not generated IDs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Strings.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;And I wanted to know what that actually costs.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-baseline&quot;&gt;The Baseline&lt;/h2&gt;
&lt;p&gt;Using BenchmarkDotNet, I measured the cost of updating active process groups as the workload increased from one group to ten and then to fifty.&lt;/p&gt;
&lt;p&gt;The results:&lt;/p&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;&lt;th&gt;Method                 &lt;/th&gt;&lt;th&gt;Active Process Groups &lt;/th&gt;&lt;th&gt;       Mean &lt;/th&gt;&lt;th&gt;    Error &lt;/th&gt;&lt;th&gt;   StdDev &lt;/th&gt;&lt;th&gt; Gen 0 &lt;/th&gt;&lt;th&gt;Allocated&lt;/th&gt;&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;UpdateMultipleGroups&lt;/code&gt; &lt;/td&gt;&lt;td&gt;                    1 &lt;/td&gt;&lt;td&gt;   305.1 ns &lt;/td&gt;&lt;td&gt;  1.89 ns &lt;/td&gt;&lt;td&gt;  1.76 ns &lt;/td&gt;&lt;td&gt;0.0572 &lt;/td&gt;&lt;td&gt;    360 B&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;UpdateMultipleGroups&lt;/code&gt; &lt;/td&gt;&lt;td&gt;                   10 &lt;/td&gt;&lt;td&gt; 3,115.7 ns &lt;/td&gt;&lt;td&gt; 20.14 ns &lt;/td&gt;&lt;td&gt; 18.84 ns &lt;/td&gt;&lt;td&gt;0.5722 &lt;/td&gt;&lt;td&gt;  3,600 B&lt;/td&gt;&lt;/tr&gt;
&lt;tr&gt;&lt;td&gt;&lt;code&gt;UpdateMultipleGroups&lt;/code&gt; &lt;/td&gt;&lt;td&gt;                   50 &lt;/td&gt;&lt;td&gt;15,736.6 ns &lt;/td&gt;&lt;td&gt;121.61 ns &lt;/td&gt;&lt;td&gt;107.81 ns &lt;/td&gt;&lt;td&gt;2.8687 &lt;/td&gt;&lt;td&gt; 18,000 B&lt;/td&gt;&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The first number is the one that gets your attention.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=5632822310681256007&quot; alt=&quot;Stick figure teacher pointing at whiteboard with the results drawn out in a table on it.&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;305 nanoseconds.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That's the measured cost of updating one active process group.&lt;/p&gt;
&lt;p&gt;A 60 FPS application has approximately &lt;strong&gt;16.67 milliseconds&lt;/strong&gt; available for an entire frame.&lt;/p&gt;
&lt;p&gt;That's 16,670,000 nanoseconds.&lt;/p&gt;
&lt;p&gt;The measured FSM operation therefore consumes approximately &lt;strong&gt;0.0018% of that frame budget&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;However, the more interesting result isn't the 305 ns.&lt;/p&gt;
&lt;p&gt;It's what happens when I increase the workload.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;fifty-groups-fifteen-microseconds-&quot;&gt;Fifty Groups. Fifteen Microseconds.&lt;/h2&gt;
&lt;p&gt;At ten active process groups, the benchmark reports:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3.12 µs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;At fifty:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;15.74 µs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That's approximately &lt;strong&gt;0.0157 milliseconds&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;Against a 16.67 ms frame budget, fifty active process groups consume approximately:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;0.094% of one frame.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That's not a theoretical &quot;eventually it should scale&quot; statement.&lt;/p&gt;
&lt;p&gt;That's measured behavior.&lt;/p&gt;
&lt;p&gt;And the scaling is almost perfectly linear.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=17829156475947039196&quot; alt=&quot;Stick figure teacher with white board showing near linear scaling!&quot;&gt;&lt;/p&gt;
&lt;p&gt;One group:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;305 ns&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Ten groups:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3.12 µs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Fifty groups:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;15.74 µs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The architecture isn't encountering some mysterious performance cliff as the workload grows.&lt;/p&gt;
&lt;p&gt;More groups mean more work.&lt;/p&gt;
&lt;p&gt;Approximately proportionally more work.&lt;/p&gt;
&lt;p&gt;That's exactly what we want from this kind of system.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;why-fifty-&quot;&gt;Why Fifty?&lt;/h2&gt;
&lt;p&gt;Fifty isn't a magic number.&lt;/p&gt;
&lt;p&gt;It isn't a claim that fifty process groups represents the maximum this FSM_API can handle.&lt;/p&gt;
&lt;p&gt;It doesn't.&lt;/p&gt;
&lt;p&gt;It is simply a useful workload for demonstrating what happens when we stop talking about one isolated state machine and start talking about &lt;strong&gt;multiple active systems being serviced together&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;That's an important distinction.&lt;/p&gt;
&lt;p&gt;A single-state benchmark can make almost anything look fast.&lt;/p&gt;
&lt;p&gt;Real software rarely gets the luxury of doing only one thing.&lt;/p&gt;
&lt;p&gt;A game might have character state, animation state, weapon state, UI state, navigation state, interaction state, environmental state, and networking state all operating simultaneously.&lt;/p&gt;
&lt;p&gt;An application outside of games has its own collection of concurrent concerns.&lt;/p&gt;
&lt;p&gt;The useful question therefore becomes:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;What happens when we start stacking these things together?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;In this benchmark, the answer is refreshingly boring.&lt;/p&gt;
&lt;p&gt;The cost grows with the workload.&lt;/p&gt;
&lt;p&gt;It doesn't suddenly explode.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;what-this-benchmark-does-and-doesn-t-prove&quot;&gt;What This Benchmark Does — and Doesn't — Prove&lt;/h2&gt;
&lt;p&gt;I believe it is worth being precise here.&lt;/p&gt;
&lt;p&gt;This benchmark is measuring the cost of the FSM's process-group update machinery.&lt;/p&gt;
&lt;p&gt;It is &lt;strong&gt;not&lt;/strong&gt; claiming that the actual work performed by your states is free.&lt;/p&gt;
&lt;p&gt;If your state executes an expensive pathfinding operation, performs a large physics calculation, processes network data, or does something computationally expensive, the FSM doesn't magically make that work disappear.&lt;/p&gt;
&lt;p&gt;That's not what we're measuring.&lt;/p&gt;
&lt;p&gt;We're measuring the machinery responsible for organizing and updating those processes.&lt;/p&gt;
&lt;p&gt;And that machinery is currently taking approximately fifteen microseconds for fifty active groups.&lt;/p&gt;
&lt;p&gt;That distinction is important because abstractions are often blamed for the work they organize.&lt;/p&gt;
&lt;p&gt;A state machine can be blamed for a slow AI routine simply because the AI routine happens to execute inside a state.&lt;/p&gt;
&lt;p&gt;This benchmark lets us separate those concerns.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The abstraction has a cost.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;I measured it.&lt;/p&gt;
&lt;p&gt;And at this scale, that cost is fairly small.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;but-there-s-something-important-hiding-in-that-table&quot;&gt;But There's Something Important Hiding in That Table&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=6106992878403153157&quot; alt=&quot;Stick figure teacher pointing at whiteboard with the cost of strings drawn out on it.&quot;&gt;&lt;/p&gt;
&lt;p&gt;The benchmark also reports allocation.&lt;/p&gt;
&lt;p&gt;One group:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;360 bytes&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Ten:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3,600 bytes&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Fifty:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;18,000 bytes&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That is approximately &lt;strong&gt;360 bytes per active process group per update&lt;/strong&gt; under this benchmark.&lt;/p&gt;
&lt;p&gt;At 60 FPS, if fifty groups were updated every frame, that corresponds to:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;approximately 1.08 MB of managed allocation per second.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That isn't catastrophic.&lt;/p&gt;
&lt;p&gt;But it isn't zero either.&lt;/p&gt;
&lt;p&gt;And this is where the benchmark becomes particularly useful.&lt;/p&gt;
&lt;p&gt;Because I'm not finished...&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;this-is-the-performance-baseline-not-the-destination&quot;&gt;This Is the Performance Baseline — Not the Destination&lt;/h2&gt;
&lt;p&gt;The FSM_API architecture is currently &lt;strong&gt;fully string-backed&lt;/strong&gt;.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=13634934897178831906&quot; alt=&quot;Stick figure shouting 'All Aboard' a train heading from string backing to integer backing.&quot;&gt;&lt;/p&gt;
&lt;p&gt;That's important because the numbers above aren't the result of some hidden integer representation doing the real work underneath.&lt;/p&gt;
&lt;p&gt;The strings are actually part of the machinery being measured.&lt;/p&gt;
&lt;p&gt;And that's intentional.&lt;/p&gt;
&lt;p&gt;One of the architectural goals of FSM_API has always been to make state management expressive and discoverable.&lt;/p&gt;
&lt;p&gt;States can have meaningful names.&lt;/p&gt;
&lt;p&gt;Process groups can be identified without forcing the developer to maintain a parallel universe of integer constants.&lt;/p&gt;
&lt;p&gt;The API remains readable.&lt;/p&gt;
&lt;p&gt;The architecture remains flexible.&lt;/p&gt;
&lt;p&gt;And now we have measured what that flexibility costs.&lt;/p&gt;
&lt;p&gt;The answer is:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Not very much.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;But there is another conclusion hiding in that result.&lt;/p&gt;
&lt;p&gt;If this is what the system can do &lt;strong&gt;while paying the cost of its string-backed implementation&lt;/strong&gt;, then we now have a very interesting optimization target.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-next-version-removes-the-string-cost&quot;&gt;The Next Version Removes the String Cost&lt;/h2&gt;
&lt;p&gt;I'm currently refactoring the underlying implementation so that the FSM can retain its &lt;strong&gt;string-facing architecture and developer experience while moving the internal backing representation toward integer indexing.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That's an important distinction.&lt;/p&gt;
&lt;p&gt;The goal isn't to tell developers:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&quot;Sorry, strings are slow. Go back and rewrite your application using integers.&quot;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;The goal is the opposite.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Keep the expressive interface. Optimize what happens underneath it.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The developer can continue thinking in terms of:&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&quot;Idle&quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&quot;Running&quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&quot;Paused&quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;&lt;code&gt;&quot;Processing&quot;&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;while the machinery underneath increasingly has the opportunity to operate on compact integer representations.&lt;/p&gt;
&lt;p&gt;That's the architecture we're moving toward.&lt;/p&gt;
&lt;p&gt;And the benchmark above gives us something extremely valuable:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;a control group.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;We already know what the purely string-backed implementation costs.&lt;/p&gt;
&lt;p&gt;When the integer-backed implementation is complete, we can run the same benchmark again.&lt;/p&gt;
&lt;p&gt;Same workload.&lt;/p&gt;
&lt;p&gt;Same benchmark harness.&lt;/p&gt;
&lt;p&gt;Same measurement methodology.&lt;/p&gt;
&lt;p&gt;Then we can put the two implementations beside each other and see exactly what the architectural change bought us.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;we-can-already-see-the-shape-of-the-opportunity&quot;&gt;We Can Already See the Shape of the Opportunity&lt;/h2&gt;
&lt;p&gt;We shouldn't manufacture a performance number before we have measured it.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=17348225345711217563&quot; alt=&quot;Stick figure reviewing the shape of opportunity when we go from string backing to integer backing.&quot;&gt;&lt;/p&gt;
&lt;p&gt;That's one of the reasons I like this experiment.&lt;/p&gt;
&lt;p&gt;We have a baseline.&lt;/p&gt;
&lt;p&gt;We have a known workload.&lt;/p&gt;
&lt;p&gt;We have known execution times.&lt;/p&gt;
&lt;p&gt;We have known allocations.&lt;/p&gt;
&lt;p&gt;Now we have something to beat.&lt;/p&gt;
&lt;p&gt;The current 50-group benchmark takes:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;15,736.6 ns&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;and allocates:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;18,000 bytes.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Suppose, purely as a thought experiment, the integer-backed implementation reduced execution time by 25%.&lt;/p&gt;
&lt;p&gt;That would take the measured operation from approximately:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;15.7 µs → 11.8 µs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A 50% reduction would produce:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;15.7 µs → 7.9 µs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;A 75% reduction would produce:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;15.7 µs → 3.9 µs&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Those aren't predictions.&lt;/p&gt;
&lt;p&gt;They're not marketing numbers.&lt;/p&gt;
&lt;p&gt;They're simply showing the size of the optimization space we're about to measure.&lt;/p&gt;
&lt;p&gt;And the allocation story could be even more interesting.&lt;/p&gt;
&lt;p&gt;If the integer-backed representation eliminates allocations currently associated with string construction or other string-oriented work in the measured path, the memory profile could change substantially even if the raw execution-time improvement turns out to be modest.&lt;/p&gt;
&lt;p&gt;That matters for systems that run continuously.&lt;/p&gt;
&lt;p&gt;A few microseconds once isn't particularly interesting.&lt;/p&gt;
&lt;p&gt;A few microseconds and a small allocation repeated every frame, across hundreds or thousands of objects, is a different conversation.&lt;/p&gt;
&lt;p&gt;This is where optimization becomes architecture.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-goal-isn-t-integer-good-string-bad-&quot;&gt;The Goal Isn't &quot;Integer Good, String Bad&quot;&lt;/h2&gt;
&lt;p&gt;This is probably the most important point in the entire experiment.&lt;/p&gt;
&lt;p&gt;I'm not refactoring FSM_API because the string-backed implementation failed.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;It didn't.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;It's fast.&lt;/p&gt;
&lt;p&gt;It scales predictably.&lt;/p&gt;
&lt;p&gt;It's readable.&lt;/p&gt;
&lt;p&gt;It's flexible.&lt;/p&gt;
&lt;p&gt;And it has now survived measurement.&lt;/p&gt;
&lt;p&gt;The integer-backed refactor is about taking an architecture that is already useful and asking:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Can we preserve everything developers like about it while making the machinery underneath even cheaper?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That's a much more interesting engineering problem.&lt;/p&gt;
&lt;p&gt;We're not sacrificing usability to chase a benchmark score.&lt;/p&gt;
&lt;p&gt;We're trying to make the implementation disappear even further beneath the abstraction.&lt;/p&gt;
&lt;p&gt;The developer shouldn't have to care whether the machine underneath is using strings, integers, tables, caches, or some future representation we haven't invented yet.&lt;/p&gt;
&lt;p&gt;They should be able to describe their state.&lt;/p&gt;
&lt;p&gt;The machine should do the rest.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;and-this-is-where-don-t-use-strings-gets-interesting&quot;&gt;And This Is Where &quot;Don't Use Strings&quot; Gets Interesting&lt;/h2&gt;
&lt;p&gt;The benchmark doesn't prove that strings are universally fast.&lt;/p&gt;
&lt;p&gt;They aren't.&lt;/p&gt;
&lt;p&gt;It proves something much more useful:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;A carefully engineered string-backed FSM does not automatically become a performance problem.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Our current implementation can update fifty active process groups in approximately fifteen microseconds.&lt;/p&gt;
&lt;p&gt;That's fast enough that the abstraction itself occupies less than one tenth of one percent of a 60 FPS frame in this benchmark.&lt;/p&gt;
&lt;p&gt;And that's before the integer-backed optimization is finished.&lt;/p&gt;
&lt;p&gt;So perhaps the better engineering rule isn't:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Don't use strings.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;Maybe it's:&lt;/p&gt;
&lt;blockquote&gt;&lt;p&gt;&lt;strong&gt;Don't assume. Measure.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;If the strings are costing us something measurable, optimize them.&lt;/p&gt;
&lt;p&gt;If they aren't costing us enough to matter, spend our engineering effort somewhere more valuable.&lt;/p&gt;
&lt;p&gt;And if we can preserve the developer-facing benefits of strings while removing much of their runtime cost?&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=10517863792517626526&quot; alt=&quot;Stick figure observing tradeoffs between string backing and integer backing&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Then we've got the best of both worlds.&lt;/strong&gt;&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;the-benchmark-becomes-part-of-the-architecture&quot;&gt;The Benchmark Becomes Part of the Architecture&lt;/h2&gt;
&lt;p&gt;This is ultimately why I like this benchmark more than a simple performance chart.&lt;/p&gt;
&lt;p&gt;It isn't a victory lap.&lt;/p&gt;
&lt;p&gt;It's a timestamp.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;This is what the FSM costs today.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Pure strings.&lt;/p&gt;
&lt;p&gt;305 ns for one group.&lt;/p&gt;
&lt;p&gt;3.12 µs for ten.&lt;/p&gt;
&lt;p&gt;15.74 µs for fifty.&lt;/p&gt;
&lt;p&gt;360 bytes per group under the measured workload.&lt;/p&gt;
&lt;p&gt;Linear scaling.&lt;/p&gt;
&lt;p&gt;And now we're changing the internal representation.&lt;/p&gt;
&lt;p&gt;So we'll measure it again.&lt;/p&gt;
&lt;p&gt;The interesting question isn't whether the next implementation is faster.&lt;/p&gt;
&lt;p&gt;It almost certainly should be.&lt;/p&gt;
&lt;p&gt;The interesting question is:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How much faster?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;And more importantly:&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;How much allocation can we eliminate without sacrificing the API and architectural flexibility that made the string-backed design worthwhile in the first place?&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;That's something worth measuring.&lt;/p&gt;
&lt;p&gt;And when those numbers are available, we'll have something better than a benchmark claim.&lt;/p&gt;
&lt;p&gt;We'll have an &lt;strong&gt;A/B measurement of the architecture itself.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;The string-backed FSM already flies.&lt;/p&gt;
&lt;p&gt;Now we're taking the strings out of the engine and finding out just how much faster it can fly.&lt;/p&gt;
&lt;hr&gt;
&lt;h2 id=&quot;you-don-t-have-to-take-my-word-for-it&quot;&gt;You Don't Have to Take My Word for It&lt;/h2&gt;
&lt;p&gt;And here's the part where the benchmark stops being an article and becomes an invitation.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=3726050540135857480&quot; alt=&quot;Stick figure seen trying the API out for themselves&quot;&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;The FSM_API is available now.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;You don't have to agree with me about strings.&lt;/p&gt;
&lt;p&gt;You don't have to take the 305 ns number on faith.&lt;/p&gt;
&lt;p&gt;You don't even have to like the architecture.&lt;/p&gt;
&lt;p&gt;Install it.&lt;/p&gt;
&lt;p&gt;Build something with it.&lt;/p&gt;
&lt;p&gt;Put it under your own workload.&lt;/p&gt;
&lt;p&gt;Benchmark it against whatever you're currently using.&lt;/p&gt;
&lt;p&gt;Then tell me what happens.&lt;/p&gt;
&lt;p&gt;That's one of the reasons I publish these experiments in the first place.&lt;/p&gt;
&lt;p&gt;The goal isn't to tell developers what they &lt;em&gt;should&lt;/em&gt; believe.&lt;/p&gt;
&lt;p&gt;The goal is to build the tooling, expose the architecture, measure the results, and let other engineers kick the tires.&lt;/p&gt;
&lt;p&gt;And if you find a workload where it falls over?&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;I want to know about that too.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Because that's useful data.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;Resources &amp;amp; Code:&lt;/p&gt;
&lt;p&gt;The FSM Package (Unity Asset Store):&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://assetstore.unity.com/packages/slug/332450&quot;&gt;https://assetstore.unity.com/packages/slug/332450&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;NuGet Package (Non-Unity Core):&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.nuget.org/packages/TheSingularityWorkshop.FSM_API&quot;&gt;https://www.nuget.org/packages/TheSingularityWorkshop.FSM_API&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;GitHub Repository:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://github.com/TrentBest/FSM_API&quot;&gt;https://github.com/TrentBest/FSM_API&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Support Our Work:&lt;/p&gt;
&lt;p&gt;Patreon Page:&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.patreon.com/c/TheSingularityWorkshop&quot;&gt;https://www.patreon.com/c/TheSingularityWorkshop&lt;/a&gt;&lt;/p&gt;
&lt;p&gt; Support Us (PayPal Donation):&lt;br&gt;
&lt;a rel=&quot;nofollow&quot; href=&quot;https://www.paypal.com/donate/?hosted_button_id=3Z7263LCQMV9J&quot;&gt;https://www.paypal.com/donate/?hosted_button_id=3Z7263LCQMV9J&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;Please Like this post, Love the code, and Share your experience in the comments.&lt;/p&gt;
&lt;hr&gt;
&lt;p&gt;&lt;img src=&quot;https://coderlegion.com/?qa=blob&amp;amp;qa_blobid=11780617275403541958&quot; alt=&quot;A Hyper-Detailed, Architecturally Impossible Synthesis of Consciousness and Digital Matter. The image is a frenetic, deeply complex digital vista where a central Luminous Eye dominates a vast, glowing circuit landscape. This eye, suspended mid-air, is a sphere of intense, fractal energy—a pulsating vortex of pink, violet, and electric blue light—that powerfully suggests an emergence of digital consciousness or a Technological Singularity itself. The core is a bottomless black aperture, ringed by a white-hot plasma disc. Below this ocular energy source, the light dissipates into an intricate, copper-gold and neon-blue Circuit Board Megastructure that stretches to the horizon, impossibly dense with exaggerated microchips, glowing resistor arrays, and power conduits that form deep, glowing canyons. The background is a dark, holographic projection field displaying complex schematics, mathematical models, and flowing data streams, reinforcing the theme of Absolute Digital Engineering. The entire scene is bathed in a dramatic, opposing light source—a warm, orange-gold glow from the left and an ice-cold, electric-blue glow from the right—creating a maximalist, high-contrast visual experience that definitively rejects the minimalist simplicity of conventional design and announces: Technology is intricate, overwhelming, and infinitely complex.&quot;&gt;&lt;/p&gt;
</description>
<category>Articles</category>
<guid isPermaLink="true">https://coderlegion.com/26350/benchmarking-the-fsm-pure-strings-already-fast-and-about-to-get-faster</guid>
<pubDate>Sun, 06 Sep 2026 19:41:30 +0000</pubDate>
</item>
</channel>
</rss>