<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0">
<channel>
<title>Coder Legion - Recent posts tagged struct</title>
<link>https://coderlegion.com/tag/struct</link>
<description>Powered by Question2Answer</description>
<item>
<title>Is Your Game Choking on GC Allocations?</title>
<link>https://coderlegion.com/21847/is-your-game-choking-on-gc-allocations</link>
<description>
&lt;h2 id=&quot;is-your-game-choking-on-gc-allocations-a-guide-to-zero-allocation-data-structures&quot;&gt;Is Your Game Choking on GC Allocations? A Guide to Zero-Allocation Data Structures&lt;/h2&gt;

&lt;h3 id=&quot;introduction&quot;&gt;Introduction&lt;/h3&gt;

&lt;p&gt;You&amp;rsquo;ve optimized your shaders, painstakingly batched draw calls, and even dipped your toes into Unity&amp;rsquo;s Burst Compiler and C# Job System. Yet, your game still experiences inexplicable hitches and stutters, particularly during intense moments. The silent assassin in many Unity games isn&amp;rsquo;t the GPU struggling with pixels, but the &lt;strong&gt;Garbage Collector (GC)&lt;/strong&gt; reclaiming memory from incessant, hidden allocations. Specifically, insidious &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GC.Alloc&lt;/code&gt; calls, often for temporary &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;class&lt;/code&gt;-based data structures, can disrupt your carefully crafted frame budget, leading to frustrating performance spikes.&lt;/p&gt;

&lt;p&gt;This tutorial will illuminate this common pitfall and provide a robust architectural solution: embracing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt; types combined with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt; for transient data. By understanding and applying these principles, you can virtually eliminate many &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GC.Alloc&lt;/code&gt; calls from your high-frequency game logic, leading to buttery-smooth frame rates and a fundamentally cleaner, faster codebase.&lt;/p&gt;

&lt;h3 id=&quot;the-culprit-unnecessary-class-allocations&quot;&gt;The Culprit: Unnecessary Class Allocations&lt;/h3&gt;

&lt;p&gt;The core problem stems from the fundamental difference between &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;class&lt;/code&gt; and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt; in C#. A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;class&lt;/code&gt; is a reference type, always allocated on the &lt;strong&gt;managed heap&lt;/strong&gt;. When you create an instance of a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;class&lt;/code&gt; using &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;new&lt;/code&gt;, memory is allocated on the heap, and a reference to that memory is returned. When that instance is no longer reachable, it becomes garbage, waiting for the GC to collect it &amp;ndash; an operation that can cause noticeable stalls.&lt;/p&gt;

&lt;p&gt;Consider a common scenario: processing temporary query results or calculating scores for nearby entities every frame. If you use a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;class&lt;/code&gt; to represent these temporary data points and store them in a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;List&amp;lt;T&amp;gt;&lt;/code&gt;, you&amp;rsquo;re incurring a double hit:&lt;/p&gt;
&lt;ol&gt;
  &lt;li&gt;&lt;strong&gt;&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;List&amp;lt;T&amp;gt;&lt;/code&gt; itself is a class&lt;/strong&gt;, though often its internal array can be reused.&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;Each &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;T&lt;/code&gt; instance (if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;T&lt;/code&gt; is a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;class&lt;/code&gt;) added to the list is a separate heap allocation.&lt;/strong&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let&amp;rsquo;s illustrate with a typical (and problematic) pattern for calculating and processing scores for nearby interactable objects:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// PROBLEM: Class-based data structure&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;InteractableScore&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GameObject&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Score&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;

    &lt;span class=&quot;c1&quot;&gt;// Constructor or properties often implicitly lead to heap allocations&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;InteractableScore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GameObject&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;score&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Target&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Score&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;score&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ScoreCalculator&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MonoBehaviour&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;InteractableScore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_currentScores&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;InteractableScore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;();&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;_currentScores&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Clear&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;();&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Clears references, but allocated objects are now garbage&lt;/span&gt;
        
        &lt;span class=&quot;c1&quot;&gt;// Imagine GetNearbyInteractables() yields many results&lt;/span&gt;
        &lt;span class=&quot;k&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;interactable&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;GetNearbyInteractables&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt; 
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;score&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CalculateScore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;interactable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;_currentScores&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Add&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;InteractableScore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;interactable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;score&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;));&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// !!! EACH 'new' is a GC.Alloc !!!&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;nf&quot;&gt;ProcessScores&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;_currentScores&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; 
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IEnumerable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GameObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;GetNearbyInteractables&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* ... implementation ... */&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GameObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;();&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CalculateScore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GameObject&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* ... implementation ... */&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ProcessScores&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;InteractableScore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scores&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* ... implementation ... */&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In this &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Update&lt;/code&gt; loop, if &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GetNearbyInteractables()&lt;/code&gt; returns 10 objects, you&amp;rsquo;ve just made 10 &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;new InteractableScore()&lt;/code&gt; heap allocations every single frame. Over time, this rapid allocation and deallocation will trigger frequent GC cycles, causing noticeable stutter.&lt;/p&gt;

&lt;h3 id=&quot;the-solution-embrace-structs-and-span&quot;&gt;The Solution: Embrace Structs and Span&lt;/h3&gt;

&lt;p&gt;The solution lies in leveraging value types (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt;) and efficient memory slicing (&lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt;). A &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt; is a value type, meaning its instances are typically allocated directly on the &lt;strong&gt;stack&lt;/strong&gt; (for local variables) or inline within its containing type (if part of an array or another object). When a &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt; is copied, its entire data is copied, not just a reference. This means no heap allocation for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt; itself, and thus, no GC pressure from its creation.&lt;/p&gt;

&lt;p&gt;For temporary collections of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt; data, we combine &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt; with a pre-allocated array and &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt;. An array, even of &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt;s, is a reference type allocated on the heap &lt;em&gt;once&lt;/em&gt;. By pre-allocating an array large enough to hold your maximum expected temporary data, you perform a single heap allocation at startup. Then, you fill this array with &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt; instances. Since &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt;s are value types, assigning them to array elements does &lt;em&gt;not&lt;/em&gt; create new heap allocations for the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt; data itself. Finally, &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt; provides a lightweight, zero-allocation &amp;ldquo;view&amp;rdquo; into a portion of this array, allowing you to process only the relevant data without copying or allocating new collections.&lt;/p&gt;

&lt;p&gt;Here&amp;rsquo;s the refactored, performance-optimized approach:&lt;/p&gt;

&lt;div class=&quot;language-csharp highlighter-rouge&quot;&gt;&lt;div class=&quot;highlight&quot;&gt;&lt;pre class=&quot;highlight&quot;&gt;&lt;code&gt;&lt;span class=&quot;c1&quot;&gt;// SOLUTION: Struct-based data structure&lt;/span&gt;
&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;struct&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;InteractableScoreData&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// It's a struct!&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;GameObject&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;Score&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

&lt;span class=&quot;k&quot;&gt;public&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;class&lt;/span&gt; &lt;span class=&quot;nc&quot;&gt;ZeroAllocScoreCalculator&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;:&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;MonoBehaviour&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Pre-allocate the buffer ONCE at startup. This is a single heap allocation.&lt;/span&gt;
    &lt;span class=&quot;c1&quot;&gt;// Choose a size appropriate for your maximum expected interactables.&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;InteractableScoreData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[]&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_scoreBuffer&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;InteractableScoreData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;50&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;];&lt;/span&gt; 

    &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;Update&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
        &lt;span class=&quot;kt&quot;&gt;int&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;currentScoreCount&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Tracks how many valid scores we've added to the buffer&lt;/span&gt;
        
        &lt;span class=&quot;k&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;interactable&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;GetNearbyInteractables&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;())&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;k&quot;&gt;if&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;currentScoreCount&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_scoreBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Length&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Prevent out-of-bounds&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
                &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;score&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CalculateScore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;interactable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
                &lt;span class=&quot;c1&quot;&gt;// Assigning a struct to an array element does NOT allocate on the heap!&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;_scoreBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;[&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;currentScoreCount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;]&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;InteractableScoreData&lt;/span&gt; 
                &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; 
                    &lt;span class=&quot;n&quot;&gt;Target&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;interactable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; 
                    &lt;span class=&quot;n&quot;&gt;Score&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;score&lt;/span&gt; 
                &lt;span class=&quot;p&quot;&gt;};&lt;/span&gt;
                &lt;span class=&quot;n&quot;&gt;currentScoreCount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;++;&lt;/span&gt;
            &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

        &lt;span class=&quot;c1&quot;&gt;// Use Span&amp;lt;T&amp;gt; to create a zero-allocation view of the valid data in our buffer&lt;/span&gt;
        &lt;span class=&quot;n&quot;&gt;Span&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;InteractableScoreData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;currentScores&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;=&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;_scoreBuffer&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;AsSpan&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;m&quot;&gt;0&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;,&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;currentScoreCount&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        
        &lt;span class=&quot;c1&quot;&gt;// Process the scores using the Span&lt;/span&gt;
        &lt;span class=&quot;nf&quot;&gt;ProcessScores&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;currentScores&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt; 
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;

    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;IEnumerable&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GameObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;GetNearbyInteractables&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;()&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* ... implementation ... */&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;new&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;List&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GameObject&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;();&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;float&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;CalculateScore&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;GameObject&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;obj&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; &lt;span class=&quot;cm&quot;&gt;/* ... implementation ... */&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;return&lt;/span&gt; &lt;span class=&quot;m&quot;&gt;0f&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;;&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    
    &lt;span class=&quot;c1&quot;&gt;// Updated to accept Span&amp;lt;T&amp;gt;&lt;/span&gt;
    &lt;span class=&quot;k&quot;&gt;private&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;void&lt;/span&gt; &lt;span class=&quot;nf&quot;&gt;ProcessScores&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Span&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;lt;&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;InteractableScoreData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;&amp;gt;&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scores&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; 
    &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt; 
        &lt;span class=&quot;k&quot;&gt;foreach&lt;/span&gt; &lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;k&quot;&gt;ref&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;readonly&lt;/span&gt; &lt;span class=&quot;kt&quot;&gt;var&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scoreData&lt;/span&gt; &lt;span class=&quot;k&quot;&gt;in&lt;/span&gt; &lt;span class=&quot;n&quot;&gt;scores&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;)&lt;/span&gt; &lt;span class=&quot;c1&quot;&gt;// Using 'ref readonly' for even more efficiency&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;
            &lt;span class=&quot;c1&quot;&gt;// Process scoreData directly from the buffer without copying&lt;/span&gt;
            &lt;span class=&quot;n&quot;&gt;Debug&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;nf&quot;&gt;Log&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;(&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;$&quot;Target: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;scoreData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Target&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;name&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;, Score: &lt;/span&gt;&lt;span class=&quot;p&quot;&gt;{&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;scoreData&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;.&lt;/span&gt;&lt;span class=&quot;n&quot;&gt;Score&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;&lt;span class=&quot;s&quot;&gt;&quot;&lt;/span&gt;&lt;span class=&quot;p&quot;&gt;);&lt;/span&gt;
        &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
    &lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;span class=&quot;p&quot;&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;
&lt;p&gt;In this refined code, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;_scoreBuffer&lt;/code&gt; is allocated only once when the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;MonoBehaviour&lt;/code&gt; starts. In &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Update&lt;/code&gt;, no new heap memory is allocated for &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;InteractableScoreData&lt;/code&gt; instances. Instead, the &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt; values are directly assigned into pre-existing slots in the array. &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt; then provides an incredibly efficient way to iterate over the valid portion of this array without any additional memory allocations.&lt;/p&gt;

&lt;p&gt;This approach not only prevents GC spikes but also significantly improves &lt;strong&gt;cache locality&lt;/strong&gt;. Because &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt; instances in an array are laid out contiguously in memory, the CPU can process them much faster, leveraging its cache more effectively &amp;ndash; a critical win for data-oriented design.&lt;/p&gt;

&lt;h3 id=&quot;conclusion&quot;&gt;Conclusion&lt;/h3&gt;

&lt;p&gt;Eliminating insidious &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;GC.Alloc&lt;/code&gt; calls is paramount for achieving consistent, high frame rates in Unity. By consciously choosing &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;struct&lt;/code&gt; over &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;class&lt;/code&gt; for transient, high-frequency data and combining this with pre-allocated buffers viewed through &lt;code class=&quot;language-plaintext highlighter-rouge&quot;&gt;Span&amp;lt;T&amp;gt;&lt;/code&gt;, you can drastically reduce GC pressure. This isn&amp;rsquo;t just about micro-optimizations; it&amp;rsquo;s about adopting a fundamentally cleaner and faster architectural pattern for your game logic. Start treating memory as a finite resource, and your game&amp;rsquo;s performance will thank you.&lt;/p&gt;
</description>
<category>Other</category>
<guid isPermaLink="true">https://coderlegion.com/21847/is-your-game-choking-on-gc-allocations</guid>
<pubDate>Fri, 03 Jul 2026 21:43:02 +0000</pubDate>
</item>
<item>
<title>Optimizing Memory Layout in Go: A Deep Dive into Struct Design</title>
<link>https://coderlegion.com/14659/optimizing-memory-layout-in-go-a-deep-dive-into-struct-design</link>
<description>
&lt;p&gt;&lt;img src=&quot;https://blog.ratnesh-maurya.com/images/blog/Optimizing-Memory-Layout-in-Go-A-Deep-Dive-into-Struct-Design.jpg&quot; alt=&quot;Optimizing Memory Layout in Go: A Deep Dive into Struct Design&quot;&gt;&lt;/p&gt;&lt;p&gt;Reorder the fields in a Go struct and the size changes &amp;mdash; without changing the data it holds. A &lt;code&gt;bool&lt;/code&gt; next to an &lt;code&gt;int64&lt;/code&gt; wastes 7 bytes of padding. Across 10 million allocations, that's 67MB of memory you're paying for but never using.&lt;/p&gt;
&lt;p&gt;This matters in high-throughput services where struct slices dominate heap usage: event pipelines, in-memory caches, analytics collectors.&lt;/p&gt;
&lt;h2 id=&quot;how-alignment-and-padding-work&quot;&gt;How alignment and padding work&lt;/h2&gt;
&lt;p&gt;Go stores struct fields in a contiguous block of memory. Each field must be aligned to a memory address that's a multiple of its own size &amp;mdash; &lt;code&gt;int64&lt;/code&gt; aligns to 8 bytes, &lt;code&gt;int32&lt;/code&gt; to 4, &lt;code&gt;bool&lt;/code&gt; to 1. When a smaller field is followed by a larger one, the compiler inserts invisible padding bytes to satisfy the alignment requirement.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-go&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;type&lt;/span&gt; Bad &lt;span class=&quot;hljs-keyword&quot;&gt;struct&lt;/span&gt; {
    Active  &lt;span class=&quot;hljs-type&quot;&gt;bool&lt;/span&gt;    &lt;span class=&quot;hljs-comment&quot;&gt;// 1 byte&lt;/span&gt;
    &lt;span class=&quot;hljs-comment&quot;&gt;// 7 bytes padding&lt;/span&gt;
    Balance &lt;span class=&quot;hljs-type&quot;&gt;float64&lt;/span&gt; &lt;span class=&quot;hljs-comment&quot;&gt;// 8 bytes&lt;/span&gt;
    Age     &lt;span class=&quot;hljs-type&quot;&gt;uint8&lt;/span&gt;   &lt;span class=&quot;hljs-comment&quot;&gt;// 1 byte&lt;/span&gt;
    &lt;span class=&quot;hljs-comment&quot;&gt;// 7 bytes padding&lt;/span&gt;
}
&lt;span class=&quot;hljs-comment&quot;&gt;// Total: 24 bytes (only 10 bytes of actual data)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;Reorder from largest to smallest:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-go&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;type&lt;/span&gt; Good &lt;span class=&quot;hljs-keyword&quot;&gt;struct&lt;/span&gt; {
    Balance &lt;span class=&quot;hljs-type&quot;&gt;float64&lt;/span&gt; &lt;span class=&quot;hljs-comment&quot;&gt;// 8 bytes&lt;/span&gt;
    Active  &lt;span class=&quot;hljs-type&quot;&gt;bool&lt;/span&gt;    &lt;span class=&quot;hljs-comment&quot;&gt;// 1 byte&lt;/span&gt;
    Age     &lt;span class=&quot;hljs-type&quot;&gt;uint8&lt;/span&gt;   &lt;span class=&quot;hljs-comment&quot;&gt;// 1 byte&lt;/span&gt;
    &lt;span class=&quot;hljs-comment&quot;&gt;// 6 bytes padding (struct itself aligns to 8)&lt;/span&gt;
}
&lt;span class=&quot;hljs-comment&quot;&gt;// Total: 16 bytes (same 10 bytes of data, 8 bytes less waste)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;That's a 33% reduction per struct, just by reordering fields.&lt;/p&gt;
&lt;h2 id=&quot;measuring-the-difference&quot;&gt;Measuring the difference&lt;/h2&gt;
&lt;p&gt;Use &lt;code&gt;reflect.TypeOf&lt;/code&gt; and &lt;code&gt;unsafe.Sizeof&lt;/code&gt; to check struct sizes at runtime:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-go&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;package&lt;/span&gt; main

&lt;span class=&quot;hljs-keyword&quot;&gt;import&lt;/span&gt; (
    &lt;span class=&quot;hljs-string&quot;&gt;&quot;fmt&quot;&lt;/span&gt;
    &lt;span class=&quot;hljs-string&quot;&gt;&quot;reflect&quot;&lt;/span&gt;
    &lt;span class=&quot;hljs-string&quot;&gt;&quot;unsafe&quot;&lt;/span&gt;
)

&lt;span class=&quot;hljs-keyword&quot;&gt;type&lt;/span&gt; Bad &lt;span class=&quot;hljs-keyword&quot;&gt;struct&lt;/span&gt; {
    Active  &lt;span class=&quot;hljs-type&quot;&gt;bool&lt;/span&gt;
    Balance &lt;span class=&quot;hljs-type&quot;&gt;float64&lt;/span&gt;
    Age     &lt;span class=&quot;hljs-type&quot;&gt;uint8&lt;/span&gt;
}

&lt;span class=&quot;hljs-keyword&quot;&gt;type&lt;/span&gt; Good &lt;span class=&quot;hljs-keyword&quot;&gt;struct&lt;/span&gt; {
    Balance &lt;span class=&quot;hljs-type&quot;&gt;float64&lt;/span&gt;
    Active  &lt;span class=&quot;hljs-type&quot;&gt;bool&lt;/span&gt;
    Age     &lt;span class=&quot;hljs-type&quot;&gt;uint8&lt;/span&gt;
}

&lt;span class=&quot;hljs-function&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;func&lt;/span&gt; &lt;span class=&quot;hljs-title&quot;&gt;main&lt;/span&gt;&lt;span class=&quot;hljs-params&quot;&gt;()&lt;/span&gt;&lt;/span&gt; {
    fmt.Println(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Bad:&quot;&lt;/span&gt;, unsafe.Sizeof(Bad{}), &lt;span class=&quot;hljs-string&quot;&gt;&quot;bytes&quot;&lt;/span&gt;)   &lt;span class=&quot;hljs-comment&quot;&gt;// 24&lt;/span&gt;
    fmt.Println(&lt;span class=&quot;hljs-string&quot;&gt;&quot;Good:&quot;&lt;/span&gt;, unsafe.Sizeof(Good{}), &lt;span class=&quot;hljs-string&quot;&gt;&quot;bytes&quot;&lt;/span&gt;) &lt;span class=&quot;hljs-comment&quot;&gt;// 16&lt;/span&gt;

    &lt;span class=&quot;hljs-comment&quot;&gt;// Field-by-field inspection&lt;/span&gt;
    t := reflect.TypeOf(Bad{})
    &lt;span class=&quot;hljs-keyword&quot;&gt;for&lt;/span&gt; i := &lt;span class=&quot;hljs-number&quot;&gt;0&lt;/span&gt;; i &amp;lt; t.NumField(); i++ {
        f := t.Field(i)
        fmt.Printf(&lt;span class=&quot;hljs-string&quot;&gt;&quot;  %s: size=%d, offset=%d\n&quot;&lt;/span&gt;, f.Name, f.Type.Size(), f.Offset)
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;h2 id=&quot;how-much-memory-this-saves-at-scale&quot;&gt;How much memory this saves at scale&lt;/h2&gt;
&lt;p&gt;Here's the math for a real scenario &amp;mdash; an analytics service tracking page view events:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-go&quot;&gt;&lt;span class=&quot;hljs-keyword&quot;&gt;type&lt;/span&gt; PageView &lt;span class=&quot;hljs-keyword&quot;&gt;struct&lt;/span&gt; {
    &lt;span class=&quot;hljs-comment&quot;&gt;// Unoptimized layout&lt;/span&gt;
    IsBot      &lt;span class=&quot;hljs-type&quot;&gt;bool&lt;/span&gt;      &lt;span class=&quot;hljs-comment&quot;&gt;// 1 + 7 padding&lt;/span&gt;
    Timestamp  &lt;span class=&quot;hljs-type&quot;&gt;int64&lt;/span&gt;     &lt;span class=&quot;hljs-comment&quot;&gt;// 8&lt;/span&gt;
    StatusCode &lt;span class=&quot;hljs-type&quot;&gt;uint16&lt;/span&gt;    &lt;span class=&quot;hljs-comment&quot;&gt;// 2 + 6 padding&lt;/span&gt;
    Duration   &lt;span class=&quot;hljs-type&quot;&gt;int64&lt;/span&gt;     &lt;span class=&quot;hljs-comment&quot;&gt;// 8&lt;/span&gt;
    UserID     &lt;span class=&quot;hljs-type&quot;&gt;uint32&lt;/span&gt;    &lt;span class=&quot;hljs-comment&quot;&gt;// 4 + 4 padding&lt;/span&gt;
    PathHash   &lt;span class=&quot;hljs-type&quot;&gt;uint64&lt;/span&gt;    &lt;span class=&quot;hljs-comment&quot;&gt;// 8&lt;/span&gt;
}
&lt;span class=&quot;hljs-comment&quot;&gt;// Size: 48 bytes&lt;/span&gt;

&lt;span class=&quot;hljs-keyword&quot;&gt;type&lt;/span&gt; PageViewOptimized &lt;span class=&quot;hljs-keyword&quot;&gt;struct&lt;/span&gt; {
    &lt;span class=&quot;hljs-comment&quot;&gt;// Sorted by alignment: 8 &amp;rarr; 4 &amp;rarr; 2 &amp;rarr; 1&lt;/span&gt;
    Timestamp  &lt;span class=&quot;hljs-type&quot;&gt;int64&lt;/span&gt;
    Duration   &lt;span class=&quot;hljs-type&quot;&gt;int64&lt;/span&gt;
    PathHash   &lt;span class=&quot;hljs-type&quot;&gt;uint64&lt;/span&gt;
    UserID     &lt;span class=&quot;hljs-type&quot;&gt;uint32&lt;/span&gt;
    StatusCode &lt;span class=&quot;hljs-type&quot;&gt;uint16&lt;/span&gt;
    IsBot      &lt;span class=&quot;hljs-type&quot;&gt;bool&lt;/span&gt;
}
&lt;span class=&quot;hljs-comment&quot;&gt;// Size: 32 bytes&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Struct count&lt;/th&gt;
&lt;th&gt;Unoptimized&lt;/th&gt;
&lt;th&gt;Optimized&lt;/th&gt;
&lt;th&gt;Saved&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;100K&lt;/td&gt;
&lt;td&gt;4.6 MB&lt;/td&gt;
&lt;td&gt;3.1 MB&lt;/td&gt;
&lt;td&gt;1.5 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;1M&lt;/td&gt;
&lt;td&gt;45.8 MB&lt;/td&gt;
&lt;td&gt;30.5 MB&lt;/td&gt;
&lt;td&gt;15.3 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10M&lt;/td&gt;
&lt;td&gt;457 MB&lt;/td&gt;
&lt;td&gt;305 MB&lt;/td&gt;
&lt;td&gt;152 MB&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;At 10 million structs, the difference is 152MB &amp;mdash; enough to matter for your container memory limits and GC pressure.&lt;/p&gt;
&lt;h2 id=&quot;automated-detection-with-fieldalignment&quot;&gt;Automated detection with fieldalignment&lt;/h2&gt;
&lt;p&gt;You don't need to manually audit every struct. The &lt;code&gt;fieldalignment&lt;/code&gt; analyzer from &lt;code&gt;golang.org/x/tools&lt;/code&gt; catches suboptimal layouts automatically:&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;hljs language-bash&quot;&gt;go install golang.org/x/tools/go/analysis/passes/fieldalignment/cmd/fieldalignment@latest
fieldalignment ./...
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;It reports every struct that could be smaller and suggests the optimal field order. You can also run it as part of &lt;code&gt;golangci-lint&lt;/code&gt; by enabling the &lt;code&gt;govet&lt;/code&gt; linter with the &lt;code&gt;fieldalignment&lt;/code&gt; check.&lt;/p&gt;
&lt;h2 id=&quot;the-alignment-rules&quot;&gt;The alignment rules&lt;/h2&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Size&lt;/th&gt;
&lt;th&gt;Alignment&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;bool&lt;/code&gt;, &lt;code&gt;byte&lt;/code&gt;, &lt;code&gt;uint8&lt;/code&gt;, &lt;code&gt;int8&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;1 byte&lt;/td&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uint16&lt;/code&gt;, &lt;code&gt;int16&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;2 bytes&lt;/td&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uint32&lt;/code&gt;, &lt;code&gt;int32&lt;/code&gt;, &lt;code&gt;float32&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;4 bytes&lt;/td&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;uint64&lt;/code&gt;, &lt;code&gt;int64&lt;/code&gt;, &lt;code&gt;float64&lt;/code&gt;, pointer, &lt;code&gt;string&lt;/code&gt;, slice, map, interface&lt;/td&gt;
&lt;td&gt;8 bytes&lt;/td&gt;
&lt;td&gt;8&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The general rule: &lt;strong&gt;sort fields from largest alignment to smallest.&lt;/strong&gt; This minimizes padding because smaller fields can pack together in the leftover space after larger fields.&lt;/p&gt;
&lt;p&gt;Structs themselves are padded to a multiple of their largest field's alignment. That's why the &lt;code&gt;Good&lt;/code&gt; struct above is 16 bytes (multiple of 8) even though the data only needs 10 bytes.&lt;/p&gt;
&lt;h2 id=&quot;when-not-to-bother&quot;&gt;When not to bother&lt;/h2&gt;
&lt;p&gt;Field ordering optimization is worth the effort when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;You allocate millions of the same struct (event pipelines, time-series data, game state)&lt;/li&gt;
&lt;li&gt;The struct is stored in a large slice that stays in memory&lt;/li&gt;
&lt;li&gt;You're hitting container memory limits or seeing heavy GC pauses&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;It's not worth the effort when:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The struct is allocated once or a handful of times&lt;/li&gt;
&lt;li&gt;Readability would suffer from rearranging logically grouped fields&lt;/li&gt;
&lt;li&gt;The struct is mostly pointers and strings (already 8-byte aligned)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Run &lt;code&gt;fieldalignment&lt;/code&gt; on your codebase as a CI check. Fix the easy wins &amp;mdash; the structs that save 8+ bytes per instance &amp;mdash; and leave the rest alone. The tool does the thinking for you.&lt;/p&gt;
</description>
<category>Other</category>
<guid isPermaLink="true">https://coderlegion.com/14659/optimizing-memory-layout-in-go-a-deep-dive-into-struct-design</guid>
<pubDate>Thu, 16 Apr 2026 10:59:45 +0000</pubDate>
</item>
</channel>
</rss>