Repository: mlange-42/arche
Pull request #137 (2023-03-21 14:04:11)

Merge storages into archetypes for faster access
Brings 5-10% speedup of archetype access. ~Not sure if this really justifies the added code.~

New and old version alternating for better comparability over fluctuating machine load.

```
cpu: Intel(R) Core(TM) i7-8550U CPU @ 1.80GHz
BenchmarkArchetypeAccess1_1000-8         1618312               717.6 ns/op             0 B/op          0 allocs/op
BenchmarkArchetypeAccessOld1_1000-8      1581909               753.6 ns/op             0 B/op          0 allocs/op
BenchmarkArchetypeAccess2_1000-8         1693066               709.0 ns/op             0 B/op          0 allocs/op
BenchmarkArchetypeAccessOld2_1000-8      1615272               843.7 ns/op             0 B/op          0 allocs/op
BenchmarkArchetypeAccess3_1000-8         1668674               706.7 ns/op             0 B/op          0 allocs/op
BenchmarkArchetypeAccessOld3_1000-8      1615597               749.5 ns/op             0 B/op          0 allocs/op
BenchmarkArchetypeAccess4_1000-8         1685044               713.2 ns/op             0 B/op          0 allocs/op
BenchmarkArchetypeAccessOld4_1000-8      1624300               738.7 ns/op             0 B/op          0 allocs/op
BenchmarkArchetypeAccess_1000-8          1682244               720.4 ns/op             0 B/op          0 allocs/op
BenchmarkArchetypeAccessOld_1000-8       1613794               743.7 ns/op             0 B/op          0 allocs/op
```


Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0031dc6..53be851 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -10,6 +10,7 @@
   * Move repetitive pointer copying code in `storage` into a private method.
   * Move repetitive entity creation code in  `World` into a private method.
 * Document all private functions, types and methods  (!136)
+* Speedup of archetype access by 5-10% by merging storages into archetypes (!137)
 
 ## [[v0.5.0]](https://github.com/mlange-42/arche/compare/v0.4.6...v0.5.0)
 
diff --git a/benchmark/arche/aos/results.csv b/benchmark/arche/aos/results.csv
index 0f60d7c..bc6f81e 100644
--- a/benchmark/arche/aos/results.csv
+++ b/benchmark/arche/aos/results.csv
@@ -1,46 +1,46 @@
 Model;Bytes;Entities;TotalTime;Time
-Arche;16;1000;2225;2.225
-Arche;16;10000;20994;2.0994
-Arche;16;100000;211542;2.11542
-Arche;32;1000;2172;2.172
-Arche;32;10000;20683;2.0683
-Arche;32;100000;210160;2.1016
-Arche;64;1000;2268;2.268
-Arche;64;10000;21285;2.1285
-Arche;64;100000;226078;2.26078
-Arche;128;1000;2239;2.239
-Arche;128;10000;22373;2.2373
-Arche;128;100000;229986;2.29986
-Arche;256;1000;2273;2.273
-Arche;256;10000;21312;2.1312
-Arche;256;100000;225957;2.25957
-AoP;16;1000;670.5;0.6705
-AoP;16;10000;8665;0.8665
-AoP;16;100000;94175;0.94175
-AoP;32;1000;882.3;0.8823
-AoP;32;10000;11031;1.1031
-AoP;32;100000;116077;1.16077
-AoP;64;1000;1014;1.014
-AoP;64;10000;17728;1.7728
-AoP;64;100000;261839;2.61839
-AoP;128;1000;1381;1.381
-AoP;128;10000;27756;2.7756
-AoP;128;100000;581235;5.81235
-AoP;256;1000;1755;1.755
-AoP;256;10000;32367;3.2367
-AoP;256;100000;925178;9.25178
-AoS;16;1000;266.3;0.2663
-AoS;16;10000;2587;0.2587
-AoS;16;100000;27404;0.27404
-AoS;32;1000;522.2;0.5222
-AoS;32;10000;5143;0.5143
-AoS;32;100000;51683;0.51683
-AoS;64;1000;2089;2.089
-AoS;64;10000;22305;2.2305
-AoS;64;100000;276172;2.76172
-AoS;128;1000;3976;3.976
-AoS;128;10000;42712;4.2712
-AoS;128;100000;794406;7.94406
-AoS;256;1000;6764;6.764
-AoS;256;10000;73719;7.3719
-AoS;256;100000;1538220;15.3822
+Arche;16;1000;2074;2.074
+Arche;16;10000;20066;2.0066
+Arche;16;100000;195594;1.95594
+Arche;32;1000;1992;1.992
+Arche;32;10000;19589;1.9589
+Arche;32;100000;198121;1.98121
+Arche;64;1000;2051;2.051
+Arche;64;10000;19387;1.9387
+Arche;64;100000;196917;1.96917
+Arche;128;1000;1996;1.996
+Arche;128;10000;19489;1.9489
+Arche;128;100000;196718;1.96718
+Arche;256;1000;2099;2.099
+Arche;256;10000;19489;1.9489
+Arche;256;100000;216944;2.16944
+AoP;16;1000;677.2;0.6772
+AoP;16;10000;8682;0.8682
+AoP;16;100000;97114;0.97114
+AoP;32;1000;871.3;0.8713
+AoP;32;10000;11087;1.1087
+AoP;32;100000;118526;1.18526
+AoP;64;1000;1028;1.028
+AoP;64;10000;17952;1.7952
+AoP;64;100000;226559;2.26559
+AoP;128;1000;1372;1.372
+AoP;128;10000;28117;2.8117
+AoP;128;100000;560284;5.60284
+AoP;256;1000;1680;1.68
+AoP;256;10000;33317;3.3317
+AoP;256;100000;911716;9.11716
+AoS;16;1000;263.6;0.2636
+AoS;16;10000;2549;0.2549
+AoS;16;100000;25364;0.25364
+AoS;32;1000;527;0.527
+AoS;32;10000;5197;0.5197
+AoS;32;100000;51514;0.51514
+AoS;64;1000;2105;2.105
+AoS;64;10000;23964;2.3964
+AoS;64;100000;266671;2.66671
+AoS;128;1000;4305;4.305
+AoS;128;10000;47167;4.7167
+AoS;128;100000;815311;8.15311
+AoS;256;1000;7293;7.293
+AoS;256;10000;80586;8.0586
+AoS;256;100000;1561781;15.61781
diff --git a/benchmark/arche/aos/results.svg b/benchmark/arche/aos/results.svg
index cec8691..9d29532 100644
--- a/benchmark/arche/aos/results.svg
+++ b/benchmark/arche/aos/results.svg
@@ -6,7 +6,7 @@
   <rdf:RDF xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:cc="http://creativecommons.org/ns#" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#">
    <cc:Work>
     <dc:type rdf:resource="http://purl.org/dc/dcmitype/StillImage"/>
-    <dc:date>2023-03-17T21:15:39.584415</dc:date>
+    <dc:date>2023-03-21T19:20:12.351396</dc:date>
     <dc:format>image/svg+xml</dc:format>
     <dc:creator>
      <cc:Agent>
@@ -41,12 +41,12 @@ z
     <g id="xtick_1">
      <g id="line2d_1">
       <defs>
-       <path id="ma231581b84" d="M 0 0 
+       <path id="m335eb5ce66" d="M 0 0 
 L 0 3.5 
 " style="stroke: #000000; stroke-width: 0.8"/>
       </defs>
       <g>
-       <use xlink:href="#ma231581b84" x="60.229091" y="246.04" style="stroke: #000000; stroke-width: 0.8"/>
+       <use xlink:href="#m335eb5ce66" x="60.229091" y="246.04" style="stroke: #000000; stroke-width: 0.8"/>
       </g>
      </g>
      <g id="text_1">
@@ -56,7 +56,7 @@ L 0 3.5
     <g id="xtick_2">
      <g id="line2d_2">
       <g>
-       <use xlink:href="#ma231581b84" x="146.174545" y="246.04" style="stroke: #000000; stroke-width: 0.8"/>
+       <use xlink:href="#m335eb5ce66" x="146.174545" y="246.04" style="stroke: #000000; stroke-width: 0.8"/>
       </g>
      </g>
      <g id="text_2">
@@ -66,7 +66,7 @@ L 0 3.5
     <g id="xtick_3">
      <g id="line2d_3">
       <g>
-       <use xlink:href="#ma231581b84" x="232.12" y="246.04" style="stroke: #000000; stroke-width: 0.8"/>
+       <use xlink:href="#m335eb5ce66" x="232.12" y="246.04" style="stroke: #000000; stroke-width: 0.8"/>
       </g>
      </g>
      <g id="text_3">
@@ -76,7 +76,7 @@ L 0 3.5
     <g id="xtick_4">
      <g id="line2d_4">
       <g>
-       <use xlink:href="#ma231581b84" x="318.065455" y="246.04" style="stroke: #000000; stroke-width: 0.8"/>
+       <use xlink:href="#m335eb5ce66" x="318.065455" y="246.04" style="stroke: #000000; stroke-width: 0.8"/>
       </g>
      </g>
      <g id="text_4">
@@ -86,7 +86,7 @@ L 0 3.5
     <g id="xtick_5">
      <g id="line2d_5">
       <g>
-       <use xlink:href="#ma231581b84" x="404.010909" y="246.04" style="stroke: #000000; stroke-width: 0.8"/>
+       <use xlink:href="#m335eb5ce66" x="404.010909" y="246.04" style="stroke: #000000; stroke-width: 0.8"/>
       </g>
      </g>
      <g id="text_5">
@@ -96,68 +96,68 @@ L 0 3.5
     <g id="xtick_6">
      <g id="line2d_6">
       <defs>
-       <path id="mfa7d49e973" d="M 0 0 
+       <path id="m08cedb3686" d="M 0 0 
 L 0 2 
 " style="stroke: #000000; stroke-width: 0.6"/>
       </defs>
       <g>
-       <use xlink:href="#mfa7d49e973" x="87.897347" y="246.04" style="stroke: #000000; stroke-width: 0.6"/>
+       <use xlink:href="#m08cedb3686" x="87.897347" y="246.04" style="stroke: #000000; stroke-width: 0.6"/>
       </g>
      </g>
     </g>
     <g id="xtick_7">
      <g id="line2d_7">
       <g>
-       <use xlink:href="#mfa7d49e973" x="138.172215" y="246.04" style="stroke: #000000; stroke-width: 0.6"/>
+       <use xlink:href="#m08cedb3686" x="138.172215" y="246.04" style="stroke: #000000; stroke-width: 0.6"/>
       </g>
      </g>
     </g>
     <g id="xtick_8">
      <g id="line2d_8">
       <g>
-       <use xlink:href="#mfa7d49e973" x="173.842802" y="246.04" style="stroke: #000000; stroke-width: 0.6"/>
+       <use xlink:href="#m08cedb3686" x="173.842802" y="246.04" style="stroke: #000000; stroke-width: 0.6"/>
       </g>
      </g>
     </g>
     <g id="xtick_9">
      <g id="line2d_9">
       <g>
-       <use xlink:href="#mfa7d49e973" x="201.511058" y="246.04" style="stroke: #000000; stroke-width: 0.6"/>
+       <use xlink:href="#m08cedb3686" x="201.511058" y="246.04" style="stroke: #000000; stroke-width: 0.6"/>
       </g>
      </g>
     </g>
     <g id="xtick_10">
      <g id="line2d_10">
       <g>
-       <use xlink:href="#mfa7d49e973" x="224.11767" y="246.04" style="stroke: #000000; stroke-width: 0.6"/>
+       <use xlink:href="#m08cedb3686" x="224.11767" y="246.04" style="stroke: #000000; stroke-width: 0.6"/>
       </g>
      </g>
     </g>
     <g id="xtick_11">
      <g id="line2d_11">
       <g>
-       <use xlink:href="#mfa7d49e973" x="243.231288" y="246.04" style="stroke: #000000; stroke-width: 0.6"/>
+       <use xlink:href="#m08cedb3686" x="243.231288" y="246.04" style="stroke: #000000; stroke-width: 0.6"/>
       </g>
      </g>
     </g>
     <g id="xtick_12">
      <g id="line2d_12">
       <g>
-       <use xlink:href="#mfa7d49e973" x="259.788256" y="246.04" style="stroke: #000000; stroke-width: 0.6"/>
+       <use xlink:href="#m08cedb3686" x="259.788256" y="246.04" style="stroke: #000000; stroke-width: 0.6"/>
       </g>
      </g>
     </g>
     <g id="xtick_13">
      <g id="line2d_13">
       <g>
-       <use xlink:href="#mfa7d49e973" x="274.392538" y="246.04" style="stroke: #000000; stroke-width: 0.6"/>
+       <use xlink:href="#m08cedb3686" x="274.392538" y="246.04" style="stroke: #000000; stroke-width: 0.6"/>
       </g>
      </g>
     </g>
     <g id="xtick_14">
      <g id="line2d_14">
       <g>
-       <use xlink:href="#mfa7d49e973" x="373.401967" y="246.04" style="stroke: #000000; stroke-width: 0.6"/>
+       <use xlink:href="#m08cedb3686" x="373.401967" y="246.04" style="stroke: #000000; stroke-width: 0.6"/>
       </g>
      </g>
     </g>
@@ -169,12 +169,12 @@ L 0 2
     <g id="ytick_1">
      <g id="line2d_15">
       <defs>
-       <path id="m48a676a885" d="M 0 0 
+       <path id="m0aa8cb60d0" d="M 0 0 
 L -3.5 0 
 " style="stroke: #000000; stroke-width: 0.8"/>
       </defs>
       <g>
-       <use xlink:href="#m48a676a885" x="43.04" y="246.04" style="stroke: #000000; stroke-width: 0.8"/>
+       <use xlink:href="#m0aa8cb60d0" x="43.04" y="246.04" style="stroke: #000000; stroke-width: 0.8"/>
       </g>
      </g>
      <g id="text_7">
@@ -184,81 +184,81 @@ L -3.5 0
     <g id="ytick_2">
      <g id="line2d_16">
       <g>
-       <use xlink:href="#m48a676a885" x="43.04" y="218.730296" style="stroke: #000000; stroke-width: 0.8"/>
+       <use xlink:href="#m0aa8cb60d0" x="43.04" y="219.142289" style="stroke: #000000; stroke-width: 0.8"/>
       </g>
      </g>
      <g id="text_8">
-      <text style="font: 10px 'Arial'; text-anchor: end" x="36.04" y="222.309202" transform="rotate(-0, 36.04, 222.309202)">2</text>
+      <text style="font: 10px 'Arial'; text-anchor: end" x="36.04" y="222.721196" transform="rotate(-0, 36.04, 222.721196)">2</text>
      </g>
     </g>
     <g id="ytick_3">
      <g id="line2d_17">
       <g>
-       <use xlink:href="#m48a676a885" x="43.04" y="191.420591" style="stroke: #000000; stroke-width: 0.8"/>
+       <use xlink:href="#m0aa8cb60d0" x="43.04" y="192.244579" style="stroke: #000000; stroke-width: 0.8"/>
       </g>
      </g>
      <g id="text_9">
-      <text style="font: 10px 'Arial'; text-anchor: end" x="36.04" y="194.999498" transform="rotate(-0, 36.04, 194.999498)">4</text>
+      <text style="font: 10px 'Arial'; text-anchor: end" x="36.04" y="195.823485" transform="rotate(-0, 36.04, 195.823485)">4</text>
      </g>
     </g>
     <g id="ytick_4">
      <g id="line2d_18">
       <g>
-       <use xlink:href="#m48a676a885" x="43.04" y="164.110887" style="stroke: #000000; stroke-width: 0.8"/>
+       <use xlink:href="#m0aa8cb60d0" x="43.04" y="165.346868" style="stroke: #000000; stroke-width: 0.8"/>
       </g>
      </g>
      <g id="text_10">
-      <text style="font: 10px 'Arial'; text-anchor: end" x="36.04" y="167.689793" transform="rotate(-0, 36.04, 167.689793)">6</text>
+      <text style="font: 10px 'Arial'; text-anchor: end" x="36.04" y="168.925774" transform="rotate(-0, 36.04, 168.925774)">6</text>
      </g>
     </g>
     <g id="ytick_5">
      <g id="line2d_19">
       <g>
-       <use xlink:href="#m48a676a885" x="43.04" y="136.801183" style="stroke: #000000; stroke-width: 0.8"/>
+       <use xlink:href="#m0aa8cb60d0" x="43.04" y="138.449158" style="stroke: #000000; stroke-width: 0.8"/>
       </g>
      </g>
      <g id="text_11">
-      <text style="font: 10px 'Arial'; text-anchor: end" x="36.04" y="140.380089" transform="rotate(-0, 36.04, 140.380089)">8</text>
+      <text style="font: 10px 'Arial'; text-anchor: end" x="36.04" y="142.028064" transform="rotate(-0, 36.04, 142.028064)">8</text>
      </g>
     </g>
     <g id="ytick_6">
      <g id="line2d_20">
       <g>
-       <use xlink:href="#m48a676a885" x="43.04" y="109.491479" style="stroke: #000000; stroke-width: 0.8"/>
+       <use xlink:href="#m0aa8cb60d0" x="43.04" y="111.551447" style="stroke: #000000; stroke-width: 0.8"/>
       </g>
      </g>
      <g id="text_12">
-      <text style="font: 10px 'Arial'; text-anchor: end" x="36.04" y="113.070385" transform="rotate(-0, 36.04, 113.070385)">10</text>
+      <text style="font: 10px 'Arial'; text-anchor: end" x="36.04" y="115.130353" transform="rotate(-0, 36.04, 115.130353)">10</text>
      </g>
     </g>
     <g id="ytick_7">
      <g id="line2d_21">
       <g>
-       <use xlink:href="#m48a676a885" x="43.04" y="82.181774" style="stroke: #000000; stroke-width: 0.8"/>
+       <use xlink:href="#m0aa8cb60d0" x="43.04" y="84.653736" style="stroke: #000000; stroke-width: 0.8"/>
       </g>
      </g>
      <g id="text_13">
-      <text style="font: 10px 'Arial'; text-anchor: end" x="36.04" y="85.760681" transform="rotate(-0, 36.04, 85.760681)">12</text>
+      <text style="font: 10px 'Arial'; text-anchor: end" x="36.04" y="88.232643" transform="rotate(-0, 36.04, 88.232643)">12</text>
      </g>
     </g>
     <g id="ytick_8">
      <g id="line2d_22">
       <g>
-       <use xlink:href="#m48a676a885" x="43.04" y="54.87207" style="stroke: #000000; stroke-width: 0.8"/>
+       <use xlink:href="#m0aa8cb60d0" x="43.04" y="57.756026" style="stroke: #000000; stroke-width: 0.8"/>
       </g>
      </g>
      <g id="text_14">
-      <text style="font: 10px 'Arial'; text-anchor: end" x="36.04" y="58.450976" transform="rotate(-0, 36.04, 58.450976)">14</text>
+      <text style="font: 10px 'Arial'; text-anchor: end" x="36.04" y="61.334932" transform="rotate(-0, 36.04, 61.334932)">14</text>
      </g>
     </g>
     <g id="ytick_9">
      <g id="line2d_23">
       <g>
-       <use xlink:href="#m48a676a885" x="43.04" y="27.562366" style="stroke: #000000; stroke-width: 0.8"/>
+       <use xlink:href="#m0aa8cb60d0" x="43.04" y="30.858315" style="stroke: #000000; stroke-width: 0.8"/>
       </g>
      </g>
      <g id="text_15">
-      <text style="font: 10px 'Arial'; text-anchor: end" x="36.04" y="31.141272" transform="rotate(-0, 36.04, 31.141272)">16</text>
+      <text style="font: 10px 'Arial'; text-anchor: end" x="36.04" y="34.437222" transform="rotate(-0, 36.04, 34.437222)">16</text>
      </g>
     </g>
     <g id="text_16">
@@ -266,14 +266,14 @@ L -3.5 0
     </g>
    </g>
    <g id="line2d_24">
-    <path d="M 60.229091 215.657954 
-L 146.174545 216.381661 
-L 232.12 215.070795 
-L 318.065455 215.466786 
-L 404.010909 215.002521 
-" clip-path="url(#pd867d2f3a9)" style="fill: none; stroke-dasharray: 1,1.65; stroke-dashoffset: 0; stroke: #000000"/>
+    <path d="M 60.229091 218.147074 
+L 146.174545 219.24988 
+L 232.12 218.456398 
+L 318.065455 219.196085 
+L 404.010909 217.810853 
+" clip-path="url(#pc18d567e11)" style="fill: none; stroke-dasharray: 1,1.65; stroke-dashoffset: 0; stroke: #000000"/>
     <defs>
-     <path id="m750e425129" d="M 0 1.5 
+     <path id="mef883ffe8d" d="M 0 1.5 
 C 0.397805 1.5 0.77937 1.341951 1.06066 1.06066 
 C 1.341951 0.77937 1.5 0.397805 1.5 0 
 C 1.5 -0.397805 1.341951 -0.77937 1.06066 -1.06066 
@@ -285,53 +285,53 @@ C -0.77937 1.341951 -0.397805 1.5 0 1.5
 z
 " style="stroke: #000000"/>
     </defs>
-    <g clip-path="url(#pd867d2f3a9)">
-     <use xlink:href="#m750e425129" x="60.229091" y="215.657954" style="stroke: #000000"/>
-     <use xlink:href="#m750e425129" x="146.174545" y="216.381661" style="stroke: #000000"/>
-     <use xlink:href="#m750e425129" x="232.12" y="215.070795" style="stroke: #000000"/>
-     <use xlink:href="#m750e425129" x="318.065455" y="215.466786" style="stroke: #000000"/>
-     <use xlink:href="#m750e425129" x="404.010909" y="215.002521" style="stroke: #000000"/>
+    <g clip-path="url(#pc18d567e11)">
+     <use xlink:href="#mef883ffe8d" x="60.229091" y="218.147074" style="stroke: #000000"/>
+     <use xlink:href="#mef883ffe8d" x="146.174545" y="219.24988" style="stroke: #000000"/>
+     <use xlink:href="#mef883ffe8d" x="232.12" y="218.456398" style="stroke: #000000"/>
+     <use xlink:href="#mef883ffe8d" x="318.065455" y="219.196085" style="stroke: #000000"/>
+     <use xlink:href="#mef883ffe8d" x="404.010909" y="217.810853" style="stroke: #000000"/>
     </g>
    </g>
    <g id="line2d_25">
-    <path d="M 60.229091 217.373003 
-L 146.174545 217.797669 
-L 232.12 216.975647 
-L 318.065455 215.489999 
-L 404.010909 216.938779 
-" clip-path="url(#pd867d2f3a9)" style="fill: none; stroke-dasharray: 4.44,1.92; stroke-dashoffset: 0; stroke: #000000; stroke-width: 1.2"/>
-    <g clip-path="url(#pd867d2f3a9)">
-     <use xlink:href="#m750e425129" x="60.229091" y="217.373003" style="stroke: #000000"/>
-     <use xlink:href="#m750e425129" x="146.174545" y="217.797669" style="stroke: #000000"/>
-     <use xlink:href="#m750e425129" x="232.12" y="216.975647" style="stroke: #000000"/>
-     <use xlink:href="#m750e425129" x="318.065455" y="215.489999" style="stroke: #000000"/>
-     <use xlink:href="#m750e425129" x="404.010909" y="216.938779" style="stroke: #000000"/>
+    <path d="M 60.229091 219.053527 
+L 146.174545 219.695037 
+L 232.12 219.966704 
+L 318.065455 219.829526 
+L 404.010909 219.829526 
+" clip-path="url(#pc18d567e11)" style="fill: none; stroke-dasharray: 4.44,1.92; stroke-dashoffset: 0; stroke: #000000; stroke-width: 1.2"/>
+    <g clip-path="url(#pc18d567e11)">
+     <use xlink:href="#mef883ffe8d" x="60.229091" y="219.053527" style="stroke: #000000"/>
+     <use xlink:href="#mef883ffe8d" x="146.174545" y="219.695037" style="stroke: #000000"/>
+     <use xlink:href="#mef883ffe8d" x="232.12" y="219.966704" style="stroke: #000000"/>
+     <use xlink:href="#mef883ffe8d" x="318.065455" y="219.829526" style="stroke: #000000"/>
+     <use xlink:href="#mef883ffe8d" x="404.010909" y="219.829526" style="stroke: #000000"/>
     </g>
    </g>
    <g id="line2d_26">
-    <path d="M 60.229091 217.154253 
-L 146.174545 217.342963 
-L 232.12 215.169383 
-L 318.065455 214.635752 
-L 404.010909 215.185906 
-" clip-path="url(#pd867d2f3a9)" style="fill: none; stroke: #000000; stroke-width: 1.5; stroke-linecap: square"/>
-    <g clip-path="url(#pd867d2f3a9)">
-     <use xlink:href="#m750e425129" x="60.229091" y="217.154253" style="stroke: #000000"/>
-     <use xlink:href="#m750e425129" x="146.174545" y="217.342963" style="stroke: #000000"/>
-     <use xlink:href="#m750e425129" x="232.12" y="215.169383" style="stroke: #000000"/>
-     <use xlink:href="#m750e425129" x="318.065455" y="214.635752" style="stroke: #000000"/>
-     <use xlink:href="#m750e425129" x="404.010909" y="215.185906" style="stroke: #000000"/>
+    <path d="M 60.229091 219.734846 
+L 146.174545 219.394993 
+L 232.12 219.556918 
+L 318.065455 219.583681 
+L 404.010909 216.863515 
+" clip-path="url(#pc18d567e11)" style="fill: none; stroke: #000000; stroke-width: 1.5; stroke-linecap: square"/>
+    <g clip-path="url(#pc18d567e11)">
+     <use xlink:href="#mef883ffe8d" x="60.229091" y="219.734846" style="stroke: #000000"/>
+     <use xlink:href="#mef883ffe8d" x="146.174545" y="219.394993" style="stroke: #000000"/>
+     <use xlink:href="#mef883ffe8d" x="232.12" y="219.556918" style="stroke: #000000"/>
+     <use xlink:href="#mef883ffe8d" x="318.065455" y="219.583681" style="stroke: #000000"/>
+     <use xlink:href="#mef883ffe8d" x="404.010909" y="216.863515" style="stroke: #000000"/>
     </g>
    </g>
    <g id="line2d_27">
-    <path d="M 60.229091 242.403713 
-L 146.174545 238.909436 
-L 232.12 217.515014 
-L 318.065455 191.748308 
-L 404.010909 153.67858 
-" clip-path="url(#pd867d2f3a9)" style="fill: none; stroke-dasharray: 1,1.65; stroke-dashoffset: 0; stroke: #ff0000"/>
+    <path d="M 60.229091 242.494882 
+L 146.174545 238.952453 
+L 232.12 217.73016 
+L 318.065455 188.142678 
+L 404.010909 147.957498 
+" clip-path="url(#pc18d567e11)" style="fill: none; stroke-dasharray: 1,1.65; stroke-dashoffset: 0; stroke: #ff0000"/>
     <defs>
-     <path id="m8ead3b69bd" d="M 0 1.5 
+     <path id="m42e0f5be14" d="M 0 1.5 
 C 0.397805 1.5 0.77937 1.341951 1.06066 1.06066 
 C 1.341951 0.77937 1.5 0.397805 1.5 0 
 C 1.5 -0.397805 1.341951 -0.77937 1.06066 -1.06066 
@@ -343,53 +343,53 @@ C -0.77937 1.341951 -0.397805 1.5 0 1.5
 z
 " style="stroke: #ff0000"/>
     </defs>
-    <g clip-path="url(#pd867d2f3a9)">
-     <use xlink:href="#m8ead3b69bd" x="60.229091" y="242.403713" style="fill: #ff0000; stroke: #ff0000"/>
-     <use xlink:href="#m8ead3b69bd" x="146.174545" y="238.909436" style="fill: #ff0000; stroke: #ff0000"/>
-     <use xlink:href="#m8ead3b69bd" x="232.12" y="217.515014" style="fill: #ff0000; stroke: #ff0000"/>
-     <use xlink:href="#m8ead3b69bd" x="318.065455" y="191.748308" style="fill: #ff0000; stroke: #ff0000"/>
-     <use xlink:href="#m8ead3b69bd" x="404.010909" y="153.67858" style="fill: #ff0000; stroke: #ff0000"/>
+    <g clip-path="url(#pc18d567e11)">
+     <use xlink:href="#m42e0f5be14" x="60.229091" y="242.494882" style="fill: #ff0000; stroke: #ff0000"/>
+     <use xlink:href="#m42e0f5be14" x="146.174545" y="238.952453" style="fill: #ff0000; stroke: #ff0000"/>
+     <use xlink:href="#m42e0f5be14" x="232.12" y="217.73016" style="fill: #ff0000; stroke: #ff0000"/>
+     <use xlink:href="#m42e0f5be14" x="318.065455" y="188.142678" style="fill: #ff0000; stroke: #ff0000"/>
+     <use xlink:href="#m42e0f5be14" x="404.010909" y="147.957498" style="fill: #ff0000; stroke: #ff0000"/>
     </g>
    </g>
    <g id="line2d_28">
-    <path d="M 60.229091 242.50749 
-L 146.174545 239.01731 
-L 232.12 215.582852 
-L 318.065455 187.717396 
-L 404.010909 145.377795 
-" clip-path="url(#pd867d2f3a9)" style="fill: none; stroke-dasharray: 4.44,1.92; stroke-dashoffset: 0; stroke: #ff0000; stroke-width: 1.2"/>
-    <g clip-path="url(#pd867d2f3a9)">
-     <use xlink:href="#m8ead3b69bd" x="60.229091" y="242.50749" style="fill: #ff0000; stroke: #ff0000"/>
-     <use xlink:href="#m8ead3b69bd" x="146.174545" y="239.01731" style="fill: #ff0000; stroke: #ff0000"/>
-     <use xlink:href="#m8ead3b69bd" x="232.12" y="215.582852" style="fill: #ff0000; stroke: #ff0000"/>
-     <use xlink:href="#m8ead3b69bd" x="318.065455" y="187.717396" style="fill: #ff0000; stroke: #ff0000"/>
-     <use xlink:href="#m8ead3b69bd" x="404.010909" y="145.377795" style="fill: #ff0000; stroke: #ff0000"/>
+    <path d="M 60.229091 242.611887 
+L 146.174545 239.05063 
+L 232.12 213.811163 
+L 318.065455 182.605784 
+L 404.010909 137.661055 
+" clip-path="url(#pc18d567e11)" style="fill: none; stroke-dasharray: 4.44,1.92; stroke-dashoffset: 0; stroke: #ff0000; stroke-width: 1.2"/>
+    <g clip-path="url(#pc18d567e11)">
+     <use xlink:href="#m42e0f5be14" x="60.229091" y="242.611887" style="fill: #ff0000; stroke: #ff0000"/>
+     <use xlink:href="#m42e0f5be14" x="146.174545" y="239.05063" style="fill: #ff0000; stroke: #ff0000"/>
+     <use xlink:href="#m42e0f5be14" x="232.12" y="213.811163" style="fill: #ff0000; stroke: #ff0000"/>
+     <use xlink:href="#m42e0f5be14" x="318.065455" y="182.605784" style="fill: #ff0000; stroke: #ff0000"/>
+     <use xlink:href="#m42e0f5be14" x="404.010909" y="137.661055" style="fill: #ff0000; stroke: #ff0000"/>
     </g>
    </g>
    <g id="line2d_29">
-    <path d="M 60.229091 242.298024 
-L 146.174545 238.982763 
-L 232.12 208.329122 
-L 318.065455 137.565035 
+    <path d="M 60.229091 242.628832 
+L 146.174545 239.111957 
+L 232.12 210.175803 
+L 318.065455 136.390003 
 L 404.010909 35.998333 
-" clip-path="url(#pd867d2f3a9)" style="fill: none; stroke: #ff0000; stroke-width: 1.5; stroke-linecap: square"/>
-    <g clip-path="url(#pd867d2f3a9)">
-     <use xlink:href="#m8ead3b69bd" x="60.229091" y="242.298024" style="fill: #ff0000; stroke: #ff0000"/>
-     <use xlink:href="#m8ead3b69bd" x="146.174545" y="238.982763" style="fill: #ff0000; stroke: #ff0000"/>
-     <use xlink:href="#m8ead3b69bd" x="232.12" y="208.329122" style="fill: #ff0000; stroke: #ff0000"/>
-     <use xlink:href="#m8ead3b69bd" x="318.065455" y="137.565035" style="fill: #ff0000; stroke: #ff0000"/>
-     <use xlink:href="#m8ead3b69bd" x="404.010909" y="35.998333" style="fill: #ff0000; stroke: #ff0000"/>
+" clip-path="url(#pc18d567e11)" style="fill: none; stroke: #ff0000; stroke-width: 1.5; stroke-linecap: square"/>
+    <g clip-path="url(#pc18d567e11)">
+     <use xlink:href="#m42e0f5be14" x="60.229091" y="242.628832" style="fill: #ff0000; stroke: #ff0000"/>
+     <use xlink:href="#m42e0f5be14" x="146.174545" y="239.111957" style="fill: #ff0000; stroke: #ff0000"/>
+     <use xlink:href="#m42e0f5be14" x="232.12" y="210.175803" style="fill: #ff0000; stroke: #ff0000"/>
+     <use xlink:href="#m42e0f5be14" x="318.065455" y="136.390003" style="fill: #ff0000; stroke: #ff0000"/>
+     <use xlink:href="#m42e0f5be14" x="404.010909" y="35.998333" style="fill: #ff0000; stroke: #ff0000"/>
     </g>
    </g>
    <g id="line2d_30">
-    <path d="M 60.229091 236.884422 
-L 146.174545 233.992324 
-L 232.12 232.19398 
-L 318.065455 227.182649 
-L 404.010909 222.075734 
-" clip-path="url(#pd867d2f3a9)" style="fill: none; stroke-dasharray: 1,1.65; stroke-dashoffset: 0; stroke: #0000ff"/>
+    <path d="M 60.229091 236.932435 
+L 146.174545 234.322012 
+L 232.12 232.214577 
+L 318.065455 227.588171 
+L 404.010909 223.445923 
+" clip-path="url(#pc18d567e11)" style="fill: none; stroke-dasharray: 1,1.65; stroke-dashoffset: 0; stroke: #0000ff"/>
     <defs>
-     <path id="m8011447a2c" d="M 0 1.5 
+     <path id="m691debf36c" d="M 0 1.5 
 C 0.397805 1.5 0.77937 1.341951 1.06066 1.06066 
 C 1.341951 0.77937 1.5 0.397805 1.5 0 
 C 1.5 -0.397805 1.341951 -0.77937 1.06066 -1.06066 
@@ -401,52 +401,52 @@ C -0.77937 1.341951 -0.397805 1.5 0 1.5
 z
 " style="stroke: #0000ff"/>
     </defs>
-    <g clip-path="url(#pd867d2f3a9)">
-     <use xlink:href="#m8011447a2c" x="60.229091" y="236.884422" style="fill: #0000ff; stroke: #0000ff"/>
-     <use xlink:href="#m8011447a2c" x="146.174545" y="233.992324" style="fill: #0000ff; stroke: #0000ff"/>
-     <use xlink:href="#m8011447a2c" x="232.12" y="232.19398" style="fill: #0000ff; stroke: #0000ff"/>
-     <use xlink:href="#m8011447a2c" x="318.065455" y="227.182649" style="fill: #0000ff; stroke: #0000ff"/>
-     <use xlink:href="#m8011447a2c" x="404.010909" y="222.075734" style="fill: #0000ff; stroke: #0000ff"/>
+    <g clip-path="url(#pc18d567e11)">
+     <use xlink:href="#m691debf36c" x="60.229091" y="236.932435" style="fill: #0000ff; stroke: #0000ff"/>
+     <use xlink:href="#m691debf36c" x="146.174545" y="234.322012" style="fill: #0000ff; stroke: #0000ff"/>
+     <use xlink:href="#m691debf36c" x="232.12" y="232.214577" style="fill: #0000ff; stroke: #0000ff"/>
+     <use xlink:href="#m691debf36c" x="318.065455" y="227.588171" style="fill: #0000ff; stroke: #0000ff"/>
+     <use xlink:href="#m691debf36c" x="404.010909" y="223.445923" style="fill: #0000ff; stroke: #0000ff"/>
     </g>
    </g>
    <g id="line2d_31">
-    <path d="M 60.229091 234.208071 
-L 146.174545 230.977333 
-L 232.12 221.832678 
-L 318.065455 208.139592 
-L 404.010909 201.84334 
-" clip-path="url(#pd867d2f3a9)" style="fill: none; stroke-dasharray: 4.44,1.92; stroke-dashoffset: 0; stroke: #0000ff; stroke-width: 1.2"/>
-    <g clip-path="url(#pd867d2f3a9)">
-     <use xlink:href="#m8011447a2c" x="60.229091" y="234.208071" style="fill: #0000ff; stroke: #0000ff"/>
-     <use xlink:href="#m8011447a2c" x="146.174545" y="230.977333" style="fill: #0000ff; stroke: #0000ff"/>
-     <use xlink:href="#m8011447a2c" x="232.12" y="221.832678" style="fill: #0000ff; stroke: #0000ff"/>
-     <use xlink:href="#m8011447a2c" x="318.065455" y="208.139592" style="fill: #0000ff; stroke: #0000ff"/>
-     <use xlink:href="#m8011447a2c" x="404.010909" y="201.84334" style="fill: #0000ff; stroke: #0000ff"/>
+    <path d="M 60.229091 234.363704 
+L 146.174545 231.129254 
+L 232.12 221.896615 
+L 318.065455 208.225854 
+L 404.010909 201.232449 
+" clip-path="url(#pc18d567e11)" style="fill: none; stroke-dasharray: 4.44,1.92; stroke-dashoffset: 0; stroke: #0000ff; stroke-width: 1.2"/>
+    <g clip-path="url(#pc18d567e11)">
+     <use xlink:href="#m691debf36c" x="60.229091" y="234.363704" style="fill: #0000ff; stroke: #0000ff"/>
+     <use xlink:href="#m691debf36c" x="146.174545" y="231.129254" style="fill: #0000ff; stroke: #0000ff"/>
+     <use xlink:href="#m691debf36c" x="232.12" y="221.896615" style="fill: #0000ff; stroke: #0000ff"/>
+     <use xlink:href="#m691debf36c" x="318.065455" y="208.225854" style="fill: #0000ff; stroke: #0000ff"/>
+     <use xlink:href="#m691debf36c" x="404.010909" y="201.232449" style="fill: #0000ff; stroke: #0000ff"/>
     </g>
    </g>
    <g id="line2d_32">
-    <path d="M 60.229091 233.180543 
-L 146.174545 230.189857 
-L 232.12 210.286272 
-L 318.065455 166.67322 
-L 404.010909 119.708312 
-" clip-path="url(#pd867d2f3a9)" style="fill: none; stroke: #0000ff; stroke-width: 1.5; stroke-linecap: square"/>
-    <g clip-path="url(#pd867d2f3a9)">
-     <use xlink:href="#m8011447a2c" x="60.229091" y="233.180543" style="fill: #0000ff; stroke: #0000ff"/>
-     <use xlink:href="#m8011447a2c" x="146.174545" y="230.189857" style="fill: #0000ff; stroke: #0000ff"/>
-     <use xlink:href="#m8011447a2c" x="232.12" y="210.286272" style="fill: #0000ff; stroke: #0000ff"/>
-     <use xlink:href="#m8011447a2c" x="318.065455" y="166.67322" style="fill: #0000ff; stroke: #0000ff"/>
-     <use xlink:href="#m8011447a2c" x="404.010909" y="119.708312" style="fill: #0000ff; stroke: #0000ff"/>
+    <path d="M 60.229091 232.979279 
+L 146.174545 230.09961 
+L 232.12 215.570408 
+L 318.065455 170.688216 
+L 404.010909 123.424634 
+" clip-path="url(#pc18d567e11)" style="fill: none; stroke: #0000ff; stroke-width: 1.5; stroke-linecap: square"/>
+    <g clip-path="url(#pc18d567e11)">
+     <use xlink:href="#m691debf36c" x="60.229091" y="232.979279" style="fill: #0000ff; stroke: #0000ff"/>
+     <use xlink:href="#m691debf36c" x="146.174545" y="230.09961" style="fill: #0000ff; stroke: #0000ff"/>
+     <use xlink:href="#m691debf36c" x="232.12" y="215.570408" style="fill: #0000ff; stroke: #0000ff"/>
+     <use xlink:href="#m691debf36c" x="318.065455" y="170.688216" style="fill: #0000ff; stroke: #0000ff"/>
+     <use xlink:href="#m691debf36c" x="404.010909" y="123.424634" style="fill: #0000ff; stroke: #0000ff"/>
     </g>
    </g>
    <g id="line2d_33">
-    <path clip-path="url(#pd867d2f3a9)" style="fill: none; stroke: #000000; stroke-width: 1.5; stroke-linecap: square"/>
+    <path clip-path="url(#pc18d567e11)" style="fill: none; stroke: #000000; stroke-width: 1.5; stroke-linecap: square"/>
    </g>
    <g id="line2d_34">
-    <path clip-path="url(#pd867d2f3a9)" style="fill: none; stroke-dasharray: 4.44,1.92; stroke-dashoffset: 0; stroke: #000000; stroke-width: 1.2"/>
+    <path clip-path="url(#pc18d567e11)" style="fill: none; stroke-dasharray: 4.44,1.92; stroke-dashoffset: 0; stroke: #000000; stroke-width: 1.2"/>
    </g>
    <g id="line2d_35">
-    <path clip-path="url(#pd867d2f3a9)" style="fill: none; stroke-dasharray: 1,1.65; stroke-dashoffset: 0; stroke: #000000"/>
+    <path clip-path="url(#pc18d567e11)" style="fill: none; stroke-dasharray: 1,1.65; stroke-dashoffset: 0; stroke: #000000"/>
    </g>
    <g id="patch_3">
     <path d="M 43.04 246.04 
@@ -491,7 +491,7 @@ L 62.04 38.154062
 L 72.04 38.154062 
 " style="fill: none; stroke: #000000; stroke-width: 1.5; stroke-linecap: square"/>
      <g>
-      <use xlink:href="#m750e425129" x="62.04" y="38.154062" style="stroke: #000000"/>
+      <use xlink:href="#mef883ffe8d" x="62.04" y="38.154062" style="stroke: #000000"/>
      </g>
     </g>
     <g id="text_18">
@@ -503,7 +503,7 @@ L 62.04 52.299375
 L 72.04 52.299375 
 " style="fill: none; stroke: #ff0000; stroke-width: 1.5; stroke-linecap: square"/>
      <g>
-      <use xlink:href="#m8ead3b69bd" x="62.04" y="52.299375" style="fill: #ff0000; stroke: #ff0000"/>
+      <use xlink:href="#m42e0f5be14" x="62.04" y="52.299375" style="fill: #ff0000; stroke: #ff0000"/>
      </g>
     </g>
     <g id="text_19">
@@ -515,7 +515,7 @@ L 62.04 66.444687
 L 72.04 66.444687 
 " style="fill: none; stroke: #0000ff; stroke-width: 1.5; stroke-linecap: square"/>
      <g>
-      <use xlink:href="#m8011447a2c" x="62.04" y="66.444687" style="fill: #0000ff; stroke: #0000ff"/>
+      <use xlink:href="#m691debf36c" x="62.04" y="66.444687" style="fill: #0000ff; stroke: #0000ff"/>
      </g>
     </g>
     <g id="text_20">
@@ -552,7 +552,7 @@ L 72.04 108.880625
   </g>
  </g>
  <defs>
-  <clipPath id="pd867d2f3a9">
+  <clipPath id="pc18d567e11">
    <rect x="43.04" y="25.49625" width="378.16" height="220.54375"/>
   </clipPath>
  </defs>
diff --git a/ecs/archetype.go b/ecs/archetype.go
index 3e53ae3..c7ddc99 100644
--- a/ecs/archetype.go
+++ b/ecs/archetype.go
@@ -1,12 +1,15 @@
 package ecs
 
 import (
+	"math"
 	"reflect"
 	"unsafe"
 
 	"github.com/mlange-42/arche/ecs/stats"
 )
 
+var layoutSize = unsafe.Sizeof(layout{})
+
 // archetypeNode is a node in the archetype graph
 type archetypeNode struct {
 	mask      Mask
@@ -48,16 +51,59 @@ func (a *archetypeNode) SetTransitionRemove(id ID, to *archetypeNode) {
 
 type archetypes = pagedArr32[archetype]
 
+// Helper for accessing data from an archetype
+type archetypeAccess struct {
+	basePointer   unsafe.Pointer
+	entityPointer unsafe.Pointer
+	Mask          Mask
+}
+
+// GetEntity returns the entity at the given index
+func (a *archetypeAccess) GetEntity(index uintptr) Entity {
+	return *(*Entity)(unsafe.Add(a.entityPointer, entitySize*index))
+}
+
+// Get returns the component with the given ID at the given index
+func (a *archetypeAccess) Get(index uintptr, id ID) unsafe.Pointer {
+	return a.getLayout(id).Get(index)
+}
+
+// HasComponent returns whether the archetype contains the given component ID
+func (a *archetypeAccess) HasComponent(id ID) bool {
+	return a.getLayout(id).pointer != nil
+}
+
+// GetLayout returns the column layout for a component.
+func (a *archetypeAccess) getLayout(id ID) *layout {
+	return (*layout)(unsafe.Add(a.basePointer, layoutSize*uintptr(id)))
+}
+
+// layout specification of a component column.
+type layout struct {
+	pointer  unsafe.Pointer
+	itemSize uintptr
+}
+
+// Get returns a pointer to the item at the given index.
+func (l *layout) Get(index uintptr) unsafe.Pointer {
+	if l.pointer == nil {
+		return nil
+	}
+	return unsafe.Add(l.pointer, l.itemSize*index)
+}
+
 // archetype represents an ECS archetype
 type archetype struct {
-	Mask        Mask
-	Ids         []ID
-	references  []*storage
-	entities    storage
-	components  []storage
-	graphNode   *archetypeNode
-	basePointer unsafe.Pointer
-	storageSize uintptr
+	archetypeAccess
+	Ids               []ID
+	layouts           []layout
+	indices           []uint32
+	buffers           []reflect.Value
+	entityBuffer      reflect.Value
+	len               uint32
+	cap               uint32
+	capacityIncrement uint32
+	graphNode         *archetypeNode
 }
 
 // Init initializes an archetype
@@ -66,8 +112,15 @@ func (a *archetype) Init(node *archetypeNode, capacityIncrement int, forStorage
 	if len(components) > 0 {
 		a.Ids = make([]ID, len(components))
 	}
-	a.components = make([]storage, len(components))
-	a.references = make([]*storage, MaskTotalBits)
+
+	a.buffers = make([]reflect.Value, len(components))
+	a.layouts = make([]layout, MaskTotalBits)
+	a.indices = make([]uint32, MaskTotalBits)
+
+	cap := 1
+	if forStorage {
+		cap = capacityIncrement
+	}
 
 	prev := -1
 	for i, c := range components {
@@ -75,107 +128,150 @@ func (a *archetype) Init(node *archetypeNode, capacityIncrement int, forStorage
 			panic("component arguments must be sorted by ID")
 		}
 		prev = int(c.ID)
-
 		mask.Set(c.ID, true)
+
+		size, align := c.Type.Size(), uintptr(c.Type.Align())
+		size = (size + (align - 1)) / align * align
+
 		a.Ids[i] = c.ID
-		a.components[i] = storage{}
-		a.components[i].Init(c.Type, capacityIncrement, forStorage)
-		a.references[c.ID] = &a.components[i]
+		a.buffers[i] = reflect.New(reflect.ArrayOf(cap, c.Type)).Elem()
+		a.layouts[c.ID] = layout{
+			a.buffers[i].Addr().UnsafePointer(),
+			size,
+		}
+		a.indices[c.ID] = uint32(i)
 	}
-	a.basePointer = unsafe.Pointer(&a.references[0])
-	a.storageSize = unsafe.Sizeof(a.references[0])
-
-	a.graphNode = node
-	a.Mask = mask
-	a.entities = storage{}
-	a.entities.Init(reflect.TypeOf(Entity{}), capacityIncrement, forStorage)
-}
+	a.entityBuffer = reflect.New(reflect.ArrayOf(cap, entityType)).Elem()
 
-// GetEntity returns the entity at the given index
-func (a *archetype) GetEntity(index uintptr) Entity {
-	return *(*Entity)(a.entities.Get(index))
-}
+	a.archetypeAccess = archetypeAccess{
+		basePointer:   unsafe.Pointer(&a.layouts[0]),
+		entityPointer: a.entityBuffer.Addr().UnsafePointer(),
+		Mask:          mask,
+	}
 
-// Get returns the component with the given ID at the given index
-func (a *archetype) Get(index uintptr, id ID) unsafe.Pointer {
-	return a.getStorage(id).Get(index)
-}
+	a.graphNode = node
 
-func (a *archetype) getStorage(id ID) *storage {
-	return *(**storage)(unsafe.Add(a.basePointer, a.storageSize*uintptr(id)))
+	a.capacityIncrement = uint32(capacityIncrement)
+	a.len = 0
+	a.cap = uint32(cap)
 }
 
 // Add adds an entity with zeroed components to the archetype
 func (a *archetype) Alloc(entity Entity, zero bool) uintptr {
-	idx := uintptr(a.entities.Add(&entity))
-	len := uintptr(len(a.components))
-
-	var i uintptr
-	for i = 0; i < len; i++ {
-		comp := &a.components[i]
-		idx := comp.Alloc()
-		if zero {
-			comp.Zero(idx)
-		}
+	idx := uintptr(a.len)
+	a.extend()
+	a.addEntity(&entity, idx)
+	if zero {
+		a.ZeroAll(idx)
 	}
+	a.len++
 	return idx
 }
 
 // Add adds an entity with components to the archetype
-func (a *archetype) Add(entity Entity, components ...Component) uint32 {
+func (a *archetype) Add(entity Entity, components ...Component) uintptr {
 	if len(components) != len(a.Ids) {
 		panic("Invalid number of components")
 	}
-	idx := a.entities.Add(&entity)
+	idx := uintptr(a.len)
+
+	a.extend()
+	a.addEntity(&entity, idx)
 	for _, c := range components {
-		a.getStorage(c.ID).Add(c.Comp)
+		lay := a.getLayout(c.ID)
+		dst := a.Get(uintptr(idx), c.ID)
+		if lay.itemSize == 0 {
+			continue
+		}
+		src := reflect.ValueOf(c.Comp).UnsafePointer()
+		a.copy(src, dst, lay.itemSize)
 	}
+	a.len++
 	return idx
 }
 
-// Remove removes an entity from the archetype
+// Remove removes an entity and its components from the archetype.
 func (a *archetype) Remove(index uintptr) bool {
-	swapped := a.entities.Remove(index)
-	len := len(a.components)
-	for i := 0; i < len; i++ {
-		a.components[i].Remove(index)
+	swapped := a.removeEntity(index)
+
+	old := uintptr(a.len - 1)
+
+	if index != old {
+		for _, id := range a.Ids {
+			lay := a.getLayout(id)
+			if lay.itemSize == 0 {
+				continue
+			}
+			src := unsafe.Add(lay.pointer, old*lay.itemSize)
+			dst := unsafe.Add(lay.pointer, index*lay.itemSize)
+			a.copy(src, dst, lay.itemSize)
+		}
 	}
-	return swapped
-}
+	a.len--
 
-// Components returns the component IDs for this archetype
-func (a *archetype) Components() []ID {
-	return a.Ids
+	return swapped
 }
 
-// HasComponent returns whether the archetype contains the given component ID
-func (a *archetype) HasComponent(id ID) bool {
-	return a.getStorage(id) != nil
+// ZeroAll resets a block of storage in all buffers.
+func (a *archetype) ZeroAll(index uintptr) {
+	for _, id := range a.Ids {
+		a.Zero(index, id)
+	}
 }
 
-// Len reports the number of entities in the archetype
-func (a *archetype) Len() uint32 {
-	return a.entities.Len()
-}
+// ZeroAll resets a block of storage in one buffer.
+func (a *archetype) Zero(index uintptr, id ID) {
+	lay := a.getLayout(id)
+	if lay.itemSize == 0 {
+		return
+	}
+	dst := unsafe.Add(lay.pointer, index*lay.itemSize)
 
-// Cap reports the current capacity of the archetype
-func (a *archetype) Cap() uint32 {
-	return a.entities.Cap()
+	for i := uintptr(0); i < lay.itemSize; i++ {
+		*(*byte)(dst) = 0
+		dst = unsafe.Add(dst, 1)
+	}
 }
 
 // Set overwrites a component with the data behind the given pointer
 func (a *archetype) Set(index uintptr, id ID, comp interface{}) unsafe.Pointer {
-	return a.getStorage(id).Set(index, comp)
+	lay := a.getLayout(id)
+	dst := a.Get(index, id)
+	if lay.itemSize == 0 {
+		return dst
+	}
+	rValue := reflect.ValueOf(comp)
+
+	src := rValue.UnsafePointer()
+	a.copy(src, dst, lay.itemSize)
+	return dst
 }
 
 // SetPointer overwrites a component with the data behind the given pointer
 func (a *archetype) SetPointer(index uintptr, id ID, comp unsafe.Pointer) unsafe.Pointer {
-	return a.getStorage(id).SetPointer(index, comp)
+	lay := a.getLayout(id)
+	dst := a.Get(index, id)
+	if lay.itemSize == 0 {
+		return dst
+	}
+
+	a.copy(comp, dst, lay.itemSize)
+	return dst
 }
 
-// Zero resets th memory at the given position
-func (a *archetype) Zero(index uintptr, id ID) {
-	a.getStorage(id).Zero(index)
+// Components returns the component IDs for this archetype
+func (a *archetype) Components() []ID {
+	return a.Ids
+}
+
+// Len reports the number of entities in the archetype
+func (a *archetype) Len() uint32 {
+	return a.len
+}
+
+// Cap reports the current capacity of the archetype
+func (a *archetype) Cap() uint32 {
+	return a.cap
 }
 
 // Stats generates statistics for an archetype
@@ -189,9 +285,9 @@ func (a *archetype) Stats(reg *componentRegistry[ID]) stats.ArchetypeStats {
 
 	cap := int(a.Cap())
 	memPerEntity := 0
-	for i := 0; i < len(a.components); i++ {
-		comp := &a.components[i]
-		memPerEntity += int(comp.itemSize)
+	for _, id := range a.Ids {
+		lay := a.getLayout(id)
+		memPerEntity += int(lay.itemSize)
 	}
 	memory := cap * (int(entitySize) + memPerEntity)
 
@@ -205,3 +301,58 @@ func (a *archetype) Stats(reg *componentRegistry[ID]) stats.ArchetypeStats {
 		MemoryPerEntity: memPerEntity,
 	}
 }
+
+// copy from one pointer to another.
+func (a *archetype) copy(src, dst unsafe.Pointer, itemSize uintptr) {
+	dstSlice := (*[math.MaxInt32]byte)(dst)[:itemSize:itemSize]
+	srcSlice := (*[math.MaxInt32]byte)(src)[:itemSize:itemSize]
+	copy(dstSlice, srcSlice)
+}
+
+// extend the memory buffers if necessary for adding an entity.
+func (a *archetype) extend() {
+	if a.cap > a.len {
+		return
+	}
+	a.cap = a.capacityIncrement * ((a.cap + a.capacityIncrement) / a.capacityIncrement)
+
+	old := a.entityBuffer
+	a.entityBuffer = reflect.New(reflect.ArrayOf(int(a.cap), entityType)).Elem()
+	a.entityPointer = a.entityBuffer.Addr().UnsafePointer()
+	reflect.Copy(a.entityBuffer, old)
+
+	for _, id := range a.Ids {
+		lay := a.getLayout(id)
+		if lay.itemSize == 0 {
+			continue
+		}
+		index := a.indices[id]
+		old := a.buffers[index]
+		a.buffers[index] = reflect.New(reflect.ArrayOf(int(a.cap), old.Type().Elem())).Elem()
+		lay.pointer = a.buffers[index].Addr().UnsafePointer()
+		reflect.Copy(a.buffers[index], old)
+	}
+}
+
+// Adds an entity at the given index. Does not extend the entity buffer.
+func (a *archetype) addEntity(entity *Entity, index uintptr) {
+	dst := unsafe.Add(a.entityPointer, entitySize*index)
+	src := reflect.ValueOf(entity).UnsafePointer()
+	a.copy(src, dst, entitySize)
+}
+
+// removeEntity removes an entity from tne archetype.
+// Components need to be removed separately.
+func (a *archetype) removeEntity(index uintptr) bool {
+	old := uintptr(a.len - 1)
+
+	if index == old {
+		return false
+	}
+
+	src := unsafe.Add(a.entityPointer, old*entitySize)
+	dst := unsafe.Add(a.entityPointer, index*entitySize)
+	a.copy(src, dst, entitySize)
+
+	return true
+}
diff --git a/ecs/entity.go b/ecs/entity.go
index 1049bea..e467d32 100644
--- a/ecs/entity.go
+++ b/ecs/entity.go
@@ -2,8 +2,12 @@ package ecs
 
 import "reflect"
 
+// Reflection type of an entity
+
+var entityType = reflect.TypeOf(Entity{})
+
 // Size of an Entity in memory.
-var entitySize = reflect.TypeOf(Entity{}).Size()
+var entitySize = entityType.Size()
 
 // Size of an entitySize in memory.
 var entityIndexSize = reflect.TypeOf(entityIndex{}).Size()
diff --git a/ecs/query.go b/ecs/query.go
index b526632..a5a53fb 100644
--- a/ecs/query.go
+++ b/ecs/query.go
@@ -35,10 +35,10 @@ func (f *MaskFilter) Matches(bits Mask) bool {
 // [github.com/mlange-42/arche/generic.Query2], etc.
 // For advanced filtering, see package [github.com/mlange-42/arche/filter]
 type Query struct {
+	archetypeIter
 	filter     Filter
 	world      *World
 	archetypes *archetypes
-	archetype  archetypeIter
 	index      int
 	lockBit    uint8
 	count      int
@@ -58,7 +58,7 @@ func newQuery(world *World, filter Filter, lockBit uint8, archetypes *archetypes
 
 // Next proceeds to the next [Entity] in the Query.
 func (q *Query) Next() bool {
-	if q.archetype.Next() {
+	if q.archetypeIter.Next() {
 		return true
 	}
 	// outline to allow inlining of the fast path
@@ -76,7 +76,7 @@ func (q *Query) Step(step int) bool {
 	}
 	var ok bool
 	for {
-		step, ok = q.archetype.Step(uint32(step))
+		step, ok = q.archetypeIter.Step(uint32(step))
 		if ok {
 			return true
 		}
@@ -101,26 +101,11 @@ func (q *Query) Count() int {
 	return q.count
 }
 
-// Has returns whether the current [Entity] has the given component.
-func (q *Query) Has(comp ID) bool {
-	return q.archetype.Has(comp)
-}
-
-// Get returns the pointer to the given component at the iterator's current [Entity].
-func (q *Query) Get(comp ID) unsafe.Pointer {
-	return q.archetype.Get(comp)
-}
-
-// Entity returns the [Entity] at the iterator's position
-func (q *Query) Entity() Entity {
-	return q.archetype.Entity()
-}
-
 // Mask returns the archetype [BitMask] for the [Entity] at the iterator's current position.
 //
 // Can be used for fast checks of the entity composition, e.g. using a [Filter].
 func (q *Query) Mask() Mask {
-	return q.archetype.Archetype.Mask
+	return q.Access.Mask
 }
 
 // Close closes the Query and unlocks the world.
@@ -138,7 +123,7 @@ func (q *Query) nextArchetype() bool {
 		a := q.archetypes.Get(i)
 		if a.Len() > 0 && q.filter.Matches(a.Mask) {
 			q.index = i
-			q.archetype = newArchetypeIter(a)
+			q.archetypeIter = newArchetypeIter(a)
 			return true
 		}
 	}
@@ -149,16 +134,16 @@ func (q *Query) nextArchetype() bool {
 
 // archetypeIter is an iterator ovr a single archetype.
 type archetypeIter struct {
-	Archetype *archetype
-	Length    uintptr
-	Index     uintptr
+	Access *archetypeAccess
+	Length uintptr
+	Index  uintptr
 }
 
 // newArchetypeIter creates a new archetypeIter.
 func newArchetypeIter(arch *archetype) archetypeIter {
 	return archetypeIter{
-		Archetype: arch,
-		Length:    uintptr(arch.Len()),
+		Access: &arch.archetypeAccess,
+		Length: uintptr(arch.Len()),
 	}
 }
 
@@ -180,17 +165,17 @@ func (it *archetypeIter) Step(count uint32) (int, bool) {
 	return int(it.Index) - int(it.Length), false
 }
 
-// Has returns whether the current entity has the given component
+// Has returns whether the current entity has the given component.
 func (it *archetypeIter) Has(comp ID) bool {
-	return it.Archetype.HasComponent(comp)
+	return it.Access.HasComponent(comp)
 }
 
-// Get returns the pointer to the given component at the iterator's position
+// Get returns the pointer to the given component at the iterator's position.
 func (it *archetypeIter) Get(comp ID) unsafe.Pointer {
-	return it.Archetype.Get(it.Index, comp)
+	return it.Access.Get(it.Index, comp)
 }
 
-// Entity returns the entity at the iterator's position
+// Entity returns the entity at the iterator's position.
 func (it *archetypeIter) Entity() Entity {
-	return it.Archetype.GetEntity(it.Index)
+	return it.Access.GetEntity(it.Index)
 }
diff --git a/ecs/registry.go b/ecs/registry.go
index 90d089d..6edc0a9 100644
--- a/ecs/registry.go
+++ b/ecs/registry.go
@@ -2,13 +2,13 @@ package ecs
 
 import "reflect"
 
-// componentRegistry keeps track of component IDs
+// componentRegistry keeps track of component IDs.
 type componentRegistry[T uint8] struct {
 	Components map[reflect.Type]T
 	Types      []reflect.Type
 }
 
-// newComponentRegistry creates a new ComponentRegistry
+// newComponentRegistry creates a new ComponentRegistry.
 func newComponentRegistry[T uint8]() componentRegistry[T] {
 	return componentRegistry[T]{
 		Components: map[reflect.Type]T{},
@@ -16,25 +16,25 @@ func newComponentRegistry[T uint8]() componentRegistry[T] {
 	}
 }
 
-// RegisterComponent registers a components and assigns an ID for it
-func (r *componentRegistry[T]) RegisterComponent(tp reflect.Type, totalBits int) T {
-	id := T(len(r.Components))
-	if int(id) >= totalBits {
-		panic("maximum of 128 component types exceeded")
-	}
-	r.Components[tp], r.Types[id] = id, tp
-	return id
-}
-
-// ComponentID returns the ID for a component type, and registers it if not already registered
+// ComponentID returns the ID for a component type, and registers it if not already registered.
 func (r *componentRegistry[T]) ComponentID(tp reflect.Type) T {
 	if id, ok := r.Components[tp]; ok {
 		return id
 	}
-	return r.RegisterComponent(tp, MaskTotalBits)
+	return r.registerComponent(tp, MaskTotalBits)
 }
 
-// ComponentType returns the type of a component by ID
+// ComponentType returns the type of a component by ID.
 func (r *componentRegistry[T]) ComponentType(id T) reflect.Type {
 	return r.Types[id]
 }
+
+// registerComponent registers a components and assigns an ID for it.
+func (r *componentRegistry[T]) registerComponent(tp reflect.Type, totalBits int) T {
+	id := T(len(r.Components))
+	if int(id) >= totalBits {
+		panic("maximum of 128 component types exceeded")
+	}
+	r.Components[tp], r.Types[id] = id, tp
+	return id
+}
diff --git a/ecs/storage.go b/ecs/storage.go
deleted file mode 100644
index 8a5287f..0000000
--- a/ecs/storage.go
+++ /dev/null
@@ -1,169 +0,0 @@
-package ecs
-
-import (
-	"math"
-	"reflect"
-	"unsafe"
-)
-
-// storage is a storage implementation that works with reflection
-type storage struct {
-	buffer            reflect.Value
-	bufferAddress     unsafe.Pointer
-	itemSize          uintptr
-	len               uint32
-	cap               uint32
-	capacityIncrement uint32
-}
-
-// Init initializes a storage
-func (s *storage) Init(tp reflect.Type, increment int, forStorage bool) {
-	size, align := tp.Size(), uintptr(tp.Align())
-	size = (size + (align - 1)) / align * align
-
-	cap := 1
-	if forStorage {
-		cap = increment
-	}
-	s.buffer = reflect.New(reflect.ArrayOf(cap, tp)).Elem()
-	s.bufferAddress = s.buffer.Addr().UnsafePointer()
-	s.itemSize = size
-	s.len = 0
-	s.cap = uint32(cap)
-	s.capacityIncrement = uint32(increment)
-}
-
-// Get retrieves an unsafe pointer to an element
-func (s *storage) Get(index uintptr) unsafe.Pointer {
-	if s == nil {
-		return nil
-	}
-	return unsafe.Add(s.bufferAddress, index*s.itemSize)
-}
-
-// Add adds an element to the end of the storage
-func (s *storage) Add(value interface{}) (index uint32) {
-	s.extend()
-	s.len++
-	s.Set(uintptr(s.len-1), value)
-	return s.len - 1
-}
-
-// AddPointer adds an element to the end of the storage, based on a pointer
-func (s *storage) AddPointer(value unsafe.Pointer) (index uint32) {
-	s.extend()
-	s.len++
-	s.SetPointer(uintptr(s.len-1), value)
-	return s.len - 1
-}
-
-// Alloc adds an empty element to the end of the storage.
-// It does not zero the storage!
-func (s *storage) Alloc() (index uintptr) {
-	s.extend()
-	s.len++
-	return uintptr(s.len - 1)
-}
-
-// extend the storage's capacity by capacityIncrement.
-//
-// Extends to a multiple of capacityIncrement.
-func (s *storage) extend() {
-	if s.cap > s.len || s.itemSize == 0 {
-		return
-	}
-
-	old := s.buffer
-	s.cap = s.capacityIncrement * ((s.cap + s.capacityIncrement) / s.capacityIncrement)
-	s.buffer = reflect.New(reflect.ArrayOf(int(s.cap), s.buffer.Type().Elem())).Elem()
-	s.bufferAddress = s.buffer.Addr().UnsafePointer()
-	reflect.Copy(s.buffer, old)
-}
-
-// Remove swap-removes an element
-func (s *storage) Remove(index uintptr) bool {
-	o := uintptr(s.len - 1)
-	n := uintptr(index)
-
-	if n == o || s.itemSize == 0 {
-		s.len--
-		return false
-	}
-
-	src := unsafe.Add(s.bufferAddress, o*s.itemSize)
-	dst := unsafe.Add(s.bufferAddress, n*s.itemSize)
-	s.copy(src, dst, s.itemSize)
-
-	s.len--
-	return true
-}
-
-// Set sets the storage at the given index.
-func (s *storage) Set(index uintptr, value interface{}) unsafe.Pointer {
-	dst := s.Get(index)
-
-	if s.itemSize == 0 {
-		return dst
-	}
-	rValue := reflect.ValueOf(value)
-
-	src := rValue.UnsafePointer()
-	s.copy(src, dst, s.itemSize)
-
-	return dst
-}
-
-// SetPointer sets the storage at the given index from the data behind an unsafe pointer.
-func (s *storage) SetPointer(index uintptr, value unsafe.Pointer) unsafe.Pointer {
-	dst := s.Get(index)
-	if s.itemSize == 0 {
-		return dst
-	}
-
-	s.copy(value, dst, s.itemSize)
-
-	return dst
-}
-
-// Zero resets a block of storage
-func (s *storage) Zero(index uintptr) {
-	if s.itemSize == 0 {
-		return
-	}
-
-	dst := s.Get(index)
-
-	for i := uintptr(0); i < s.itemSize; i++ {
-		*(*byte)(dst) = 0
-		dst = unsafe.Add(dst, 1)
-	}
-}
-
-// Len returns the number of items in the storage
-func (s *storage) Len() uint32 {
-	return s.len
-}
-
-// Cap returns the capacity of the storage
-func (s *storage) Cap() uint32 {
-	return s.cap
-}
-
-// copy from one pointer to another.
-func (s *storage) copy(src, dst unsafe.Pointer, itemSize uintptr) {
-	dstSlice := (*[math.MaxInt32]byte)(dst)[:itemSize:itemSize]
-	srcSlice := (*[math.MaxInt32]byte)(src)[:itemSize:itemSize]
-	copy(dstSlice, srcSlice)
-}
-
-// toSlice converts the content of a storage to a slice of structs
-func toSlice[T any](s storage) []T {
-	res := make([]T, s.Len())
-	var i uintptr
-	len := uintptr(s.Len())
-	for i = 0; i < len; i++ {
-		ptr := (*T)(s.Get(i))
-		res[i] = *ptr
-	}
-	return res
-}


Test patch:
diff --git a/ecs/archetype_test.go b/ecs/archetype_test.go
index 0ade5b9..47045e3 100644
--- a/ecs/archetype_test.go
+++ b/ecs/archetype_test.go
@@ -28,9 +28,7 @@ func TestArchetype(t *testing.T) {
 		Component{ID: 1, Comp: &rotation{6}},
 	)
 
-	assert.Equal(t, 2, int(arch.entities.Len()))
-	assert.Equal(t, 2, int(arch.components[0].Len()))
-	assert.Equal(t, 2, int(arch.components[1].Len()))
+	assert.Equal(t, 2, int(arch.Len()))
 
 	e0 := arch.GetEntity(0)
 	e1 := arch.GetEntity(1)
@@ -50,9 +48,7 @@ func TestArchetype(t *testing.T) {
 	assert.Equal(t, 6, rot1.Angle)
 
 	arch.Remove(0)
-	assert.Equal(t, 1, int(arch.entities.Len()))
-	assert.Equal(t, 1, int(arch.components[0].Len()))
-	assert.Equal(t, 1, int(arch.components[1].Len()))
+	assert.Equal(t, 1, int(arch.Len()))
 
 	pos0 = (*position)(arch.Get(0, ID(0)))
 	rot0 = (*rotation)(arch.Get(0, ID(1)))
@@ -75,6 +71,11 @@ func TestNewArchetype(t *testing.T) {
 	}
 	arch := archetype{}
 	arch.Init(nil, 32, true, comps...)
+	assert.Equal(t, 32, int(arch.Cap()))
+
+	arch = archetype{}
+	arch.Init(nil, 32, false, comps...)
+	assert.Equal(t, 1, int(arch.Cap()))
 
 	comps = []componentType{
 		{ID: 1, Type: reflect.TypeOf(rotation{})},
@@ -86,7 +87,37 @@ func TestNewArchetype(t *testing.T) {
 	})
 }
 
-func BenchmarkArchetypeAccess_1000(b *testing.B) {
+func TestArchetypeAddGetSet(t *testing.T) {
+	a := archetype{}
+
+	comps := []componentType{
+		{ID: 0, Type: reflect.TypeOf(testStruct0{})},
+		{ID: 1, Type: reflect.TypeOf(label{})},
+	}
+	a.Init(nil, 1, true, comps...)
+
+	assert.Equal(t, 1, int(a.Cap()))
+	assert.Equal(t, 0, int(a.Len()))
+
+	a.Add(Entity{1, 0}, Component{ID: 0, Comp: &testStruct0{100}}, Component{ID: 1, Comp: &label{}})
+	a.Add(Entity{2, 0}, Component{ID: 0, Comp: &testStruct0{200}}, Component{ID: 1, Comp: &label{}})
+
+	ts := (*testStruct0)(a.Get(0, 0))
+	assert.Equal(t, 100, int(ts.Val))
+
+	a.Set(1, 0, &testStruct0{200})
+	a.Set(1, 1, &label{})
+
+	_ = (*testStruct0)(a.Get(1, 0))
+	_ = (*label)(a.Get(1, 1))
+
+	a.Remove(0)
+	assert.Equal(t, 1, int(a.Len()))
+	a.Remove(0)
+	assert.Equal(t, 0, int(a.Len()))
+}
+
+func BenchmarkIterArchetype_1000(b *testing.B) {
 	b.StopTimer()
 	comps := []componentType{
 		{ID: 0, Type: reflect.TypeOf(testStruct0{})},
@@ -101,11 +132,46 @@ func BenchmarkArchetypeAccess_1000(b *testing.B) {
 	b.StartTimer()
 
 	for i := 0; i < b.N; i++ {
-		len := int(arch.Len())
+		len := uintptr(arch.Len())
 		id := ID(0)
-		for j := 0; j < len; j++ {
-			pos := (*testStruct0)(arch.Get(uintptr(j), id))
-			pos.Val = 1
+		var j uintptr
+		for j = 0; j < len; j++ {
+			pos := (*testStruct0)(arch.Get(j, id))
+			pos.Val++
+		}
+	}
+}
+
+func BenchmarkIterSlice_1000(b *testing.B) {
+	b.StopTimer()
+	s := []testStruct0{}
+	for i := 0; i < 1000; i++ {
+		s = append(s, testStruct0{})
+	}
+	assert.Equal(b, 1000, len(s))
+	b.StartTimer()
+
+	for i := 0; i < b.N; i++ {
+		for j := 0; j < len(s); j++ {
+			a := s[j]
+			a.Val++
+		}
+	}
+}
+
+func BenchmarkIterSliceInterface_1000(b *testing.B) {
+	b.StopTimer()
+	s := []interface{}{}
+	for i := 0; i < 1000; i++ {
+		s = append(s, testStruct0{})
+	}
+	assert.Equal(b, 1000, len(s))
+	b.StartTimer()
+
+	for i := 0; i < b.N; i++ {
+		for j := 0; j < len(s); j++ {
+			a := s[j].(testStruct0)
+			a.Val++
 		}
 	}
 }
diff --git a/ecs/registry_test.go b/ecs/registry_test.go
index 7d06fd0..eb9c242 100644
--- a/ecs/registry_test.go
+++ b/ecs/registry_test.go
@@ -13,7 +13,7 @@ func TestComponentRegistry(t *testing.T) {
 	posType := reflect.TypeOf((*position)(nil)).Elem()
 	rotType := reflect.TypeOf((*rotation)(nil)).Elem()
 
-	reg.RegisterComponent(posType, MaskTotalBits)
+	reg.registerComponent(posType, MaskTotalBits)
 
 	assert.Equal(t, ID(0), reg.ComponentID(posType))
 	assert.Equal(t, ID(1), reg.ComponentID(rotType))
@@ -25,9 +25,9 @@ func TestComponentRegistry(t *testing.T) {
 func TestComponentRegistryOverflow(t *testing.T) {
 	reg := newComponentRegistry()
 
-	reg.RegisterComponent(reflect.TypeOf((*position)(nil)).Elem(), 1)
+	reg.registerComponent(reflect.TypeOf((*position)(nil)).Elem(), 1)
 
 	assert.Panics(t, func() {
-		reg.RegisterComponent(reflect.TypeOf((*rotation)(nil)).Elem(), 1)
+		reg.registerComponent(reflect.TypeOf((*rotation)(nil)).Elem(), 1)
 	})
 }
diff --git a/ecs/resources_test.go b/ecs/resources_test.go
new file mode 100644
index 0000000..2a3c3bd
--- /dev/null
+++ b/ecs/resources_test.go
@@ -0,0 +1,37 @@
+package ecs
+
+import (
+	"reflect"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestResources(t *testing.T) {
+	res := newResources()
+
+	posID := res.registry.ComponentID(reflect.TypeOf(position{}))
+	rotID := res.registry.ComponentID(reflect.TypeOf(rotation{}))
+
+	assert.False(t, res.Has(posID))
+	assert.Nil(t, res.Get(posID))
+
+	res.Add(posID, &position{1, 2})
+
+	assert.True(t, res.Has(posID))
+	pos, ok := res.Get(posID).(*position)
+	assert.True(t, ok)
+	assert.Equal(t, position{1, 2}, *pos)
+
+	assert.Panics(t, func() { res.Add(posID, &position{1, 2}) })
+
+	pos, ok = res.Get(posID).(*position)
+	assert.True(t, ok)
+	assert.Equal(t, position{1, 2}, *pos)
+
+	res.Add(rotID, &rotation{5})
+	assert.True(t, res.Has(rotID))
+	res.Remove(rotID)
+	assert.False(t, res.Has(rotID))
+	assert.Panics(t, func() { res.Remove(rotID) })
+}
diff --git a/ecs/storage_test.go b/ecs/storage_test.go
deleted file mode 100644
index df8e586..0000000
--- a/ecs/storage_test.go
+++ /dev/null
@@ -1,327 +0,0 @@
-package ecs
-
-import (
-	"reflect"
-	"testing"
-
-	"github.com/stretchr/testify/assert"
-)
-
-type testStruct struct {
-	Int32 int32
-	Int64 int64
-	Bool1 bool
-	Bool2 bool
-}
-
-type simpleStruct struct {
-	Index int
-}
-
-func TestStorageAddGet(t *testing.T) {
-	obj1 := testStruct{}
-	s := storage{}
-	s.Init(reflect.TypeOf(obj1), 1, true)
-
-	assert.Equal(t, 1, int(s.Cap()))
-	assert.Equal(t, 0, int(s.Len()))
-
-	storageAddGet(t, s)
-}
-
-func storageAddGet(t *testing.T, s storage) {
-	obj1 := testStruct{}
-	obj2 := testStruct{1, 2, true, false}
-
-	idx := s.Add(&obj1)
-	assert.Equal(t, idx, uint32(0), "Index of first insertion should be 0")
-
-	idx = s.Add(&obj2)
-	assert.Equal(t, idx, uint32(1), "Index of second insertion should be 1")
-
-	ret1 := (*testStruct)(s.Get(0))
-	assert.Equal(t, obj1, *ret1, "First element not as passed in")
-
-	ret2 := (*testStruct)(s.Get(1))
-	assert.Equal(t, obj2, *ret2, "Second element not as passed in")
-
-	ret2.Int64 = 1001
-	ret2 = (*testStruct)(s.Get(1))
-	assert.Equal(t, testStruct{1, 1001, true, false}, *ret2, "Manipulating element does not change data")
-
-	assert.Equal(t, []testStruct{{}, {1, 1001, true, false}}, toSlice[testStruct](s), "Wrong extracted struct slice")
-
-	s.Alloc()
-	assert.Equal(t, []testStruct{{}, {1, 1001, true, false}, {}}, toSlice[testStruct](s), "Wrong extracted struct slice")
-}
-
-func TestStorageRemove(t *testing.T) {
-	ref := simpleStruct{}
-	s := storage{}
-	s.Init(reflect.TypeOf(ref), 32, false)
-
-	storageRemove(t, s)
-}
-
-func storageRemove(t *testing.T, s storage) {
-	for i := 0; i < 5; i++ {
-		obj := simpleStruct{i}
-		s.Add(&obj)
-	}
-
-	assert.Equal(t, uint32(5), s.Len(), "Wrong storage length")
-
-	s.Remove(4)
-	assert.Equal(t, uint32(4), s.Len(), "Wrong storage length")
-	assert.Equal(t, []simpleStruct{{0}, {1}, {2}, {3}}, toSlice[simpleStruct](s), "Wrong slice after remove")
-
-	s.Remove(1)
-	assert.Equal(t, uint32(3), s.Len(), "Wrong storage length")
-	assert.Equal(t, []simpleStruct{{0}, {3}, {2}}, toSlice[simpleStruct](s), "Wrong slice after remove")
-}
-
-func TestStorageDataSize(t *testing.T) {
-	ref := simpleStruct{}
-	s := storage{}
-	s.Init(reflect.TypeOf(ref), 1, true)
-
-	for i := 0; i < 5; i++ {
-		obj := simpleStruct{i}
-		s.Add(&obj)
-	}
-
-	assert.Equal(t, 5, int(s.cap))
-
-	s.Remove(0)
-	assert.Equal(t, 5, int(s.cap))
-	s.Remove(0)
-	assert.Equal(t, 5, int(s.cap))
-
-	s.Add(&simpleStruct{})
-	assert.Equal(t, 5, int(s.cap))
-	s.Add(&simpleStruct{})
-	assert.Equal(t, 5, int(s.cap))
-
-	s.Add(&simpleStruct{})
-	assert.Equal(t, 6, int(s.cap))
-}
-
-func TestNewStorage(t *testing.T) {
-	s := storage{}
-	s.Init(reflect.TypeOf(simpleStruct{}), 32, true)
-}
-
-func TestStoragePointer(t *testing.T) {
-	a := storage{}
-	a.Init(reflect.TypeOf(simpleStruct{}), 32, true)
-	a.Add(&simpleStruct{})
-	s := (*simpleStruct)(a.Get(0))
-	s.Index = 10
-
-	b := storage{}
-	b.Init(reflect.TypeOf(simpleStruct{}), 32, true)
-
-	ptr := a.Get(0)
-	b.AddPointer(ptr)
-	s = (*simpleStruct)(b.Get(0))
-	assert.Equal(t, 10, s.Index)
-
-	a.Set(0, &simpleStruct{20})
-	s = (*simpleStruct)(a.Get(0))
-	assert.Equal(t, 20, s.Index)
-}
-
-func TestStorageZeroSize(t *testing.T) {
-	a := storage{}
-	a.Init(reflect.TypeOf(label{}), 32, true)
-	a.Add(&label{})
-	a.Add(&label{})
-	s := (*label)(a.Get(0))
-	assert.NotNil(t, s)
-	s = (*label)(a.Get(1))
-	assert.NotNil(t, s)
-
-	b := storage{}
-	b.Init(reflect.TypeOf(label{}), 32, true)
-
-	ptr := a.Get(0)
-	b.AddPointer(ptr)
-	ptr = a.Get(1)
-	b.AddPointer(ptr)
-	s = (*label)(b.Get(0))
-	assert.NotNil(t, s)
-	s = (*label)(a.Get(1))
-	assert.NotNil(t, s)
-}
-
-func BenchmarkIterStorage_1000(b *testing.B) {
-	b.StopTimer()
-	ref := testStruct0{}
-	s := storage{}
-	s.Init(reflect.TypeOf(ref), 128, true)
-	for i := 0; i < 1000; i++ {
-		s.Add(&testStruct0{})
-	}
-	assert.Equal(b, 1000, int(s.Len()))
-	b.StartTimer()
-
-	for i := 0; i < b.N; i++ {
-		len := uintptr(s.Len())
-		var j uintptr
-		for j = 0; j < len; j++ {
-			a := (*testStruct0)(s.Get(j))
-			a.Val = 1
-		}
-	}
-}
-
-func BenchmarkIterSlice_1000(b *testing.B) {
-	b.StopTimer()
-	s := []testStruct0{}
-	for i := 0; i < 1000; i++ {
-		s = append(s, testStruct0{})
-	}
-	assert.Equal(b, 1000, len(s))
-	b.StartTimer()
-
-	for i := 0; i < b.N; i++ {
-		for j := 0; j < len(s); j++ {
-			a := s[j]
-			a.Val = 1
-		}
-	}
-}
-
-func BenchmarkIterSliceInterface_1000(b *testing.B) {
-	b.StopTimer()
-	s := []interface{}{}
-	for i := 0; i < 1000; i++ {
-		s = append(s, testStruct0{})
-	}
-	assert.Equal(b, 1000, len(s))
-	b.StartTimer()
-
-	for i := 0; i < b.N; i++ {
-		for j := 0; j < len(s); j++ {
-			a := s[j].(testStruct0)
-			a.Val = 1
-		}
-	}
-}
-
-func BenchmarkAddStorage_1000(b *testing.B) {
-	ref := testStruct0{}
-	for i := 0; i < b.N; i++ {
-		b.StopTimer()
-		s := storage{}
-		s.Init(reflect.TypeOf(ref), 1024, true)
-		b.StartTimer()
-
-		for i := 0; i < 1000; i++ {
-			s.Add(&ref)
-		}
-	}
-}
-
-func BenchmarkAddSlice_1000(b *testing.B) {
-	ref := testStruct0{}
-	for i := 0; i < b.N; i++ {
-		b.StopTimer()
-		s := make([]testStruct0, 0)
-		b.StartTimer()
-
-		for i := 0; i < 1000; i++ {
-			s = append(s, ref)
-		}
-
-		b.StopTimer()
-		_ = s
-		b.StartTimer()
-	}
-}
-
-func BenchmarkAddSliceInterface_1000(b *testing.B) {
-	ref := testStruct0{}
-	for i := 0; i < b.N; i++ {
-		b.StopTimer()
-		s := make([]interface{}, 0)
-		b.StartTimer()
-
-		for i := 0; i < 1000; i++ {
-			s = append(s, ref)
-		}
-
-		b.StopTimer()
-		_ = s
-		b.StartTimer()
-	}
-}
-
-func BenchmarkRemoveStorage_1000(b *testing.B) {
-	ref := testStruct0{}
-	for i := 0; i < b.N; i++ {
-		b.StopTimer()
-		s := storage{}
-		s.Init(reflect.TypeOf(ref), 1024, true)
-		for i := 0; i < 1000; i++ {
-			s.Add(&ref)
-		}
-		b.StartTimer()
-
-		for i := 0; i < 1000; i++ {
-			s.Remove(0)
-		}
-	}
-}
-
-func BenchmarkRemoveSlice_1000(b *testing.B) {
-	b.StopTimer()
-	ref := testStruct0{}
-	template := make([]testStruct0, 0)
-	for i := 0; i < 1000; i++ {
-		template = append(template, ref)
-	}
-	b.StartTimer()
-	for i := 0; i < b.N; i++ {
-		b.StopTimer()
-		s := append([]testStruct0{}, template...)
-		b.StartTimer()
-
-		l := len(s) - 1
-		for i := 0; i < 1000; i++ {
-			s[0], s[l] = s[l], s[0]
-			s = s[:l]
-			l--
-		}
-
-		b.StopTimer()
-		_ = s
-		b.StartTimer()
-	}
-}
-
-func BenchmarkRemoveSliceInterface_1000(b *testing.B) {
-	b.StopTimer()
-	ref := testStruct0{}
-	template := make([]interface{}, 0)
-	for i := 0; i < 1000; i++ {
-		template = append(template, ref)
-	}
-	b.StartTimer()
-	for i := 0; i < b.N; i++ {
-		b.StopTimer()
-		s := append([]interface{}{}, template...)
-		b.StartTimer()
-
-		l := len(s) - 1
-		for i := 0; i < 1000; i++ {
-			s[0], s[l] = s[l], s[0]
-			s = s[:l]
-			l--
-		}
-
-		b.StopTimer()
-		_ = s
-		b.StartTimer()
-	}
-}
diff --git a/ecs/world_test.go b/ecs/world_test.go
index ad2bc83..c801a67 100644
--- a/ecs/world_test.go
+++ b/ecs/world_test.go
@@ -414,8 +414,6 @@ func TestWorldResources(t *testing.T) {
 
 	AddResource(&w, &position{1, 2})
 
-	fmt.Println(w.resources.registry.Components)
-
 	assert.True(t, w.HasResource(posID))
 	pos, ok := w.GetResource(posID).(*position)
 
@@ -608,14 +606,15 @@ func TestTypeSizes(t *testing.T) {
 	printTypeSize[World]()
 	printTypeSizeName[pagedArr32[archetype]]("pagedArr32")
 	printTypeSize[archetype]()
+	printTypeSize[archetypeAccess]()
 	printTypeSize[archetypeNode]()
-	printTypeSize[storage]()
 	printTypeSize[entityPool]()
 	printTypeSizeName[componentRegistry[ID]]("componentRegistry")
 	printTypeSize[bitPool]()
 	printTypeSize[Query]()
 	printTypeSize[archetypeIter]()
 	printTypeSize[resources]()
+	printTypeSizeName[reflect.Value]("reflect.Value")
 }
 
 func printTypeSize[T any]() {