Repository: mlange-42/arche
Pull request #92 (2023-02-16 11:03:15)

Benchmark archetype moves


Patch:
diff --git a/benchmark/add/components.go b/benchmark/add/components.go
deleted file mode 100644
index b9712d3..0000000
--- a/benchmark/add/components.go
+++ /dev/null
@@ -1,10 +0,0 @@
-package add
-
-type position struct {
-	X int
-	Y int
-}
-
-type rotation struct {
-	Angle int
-}
diff --git a/benchmark/common/components.go b/benchmark/common/components.go
new file mode 100644
index 0000000..6d8a546
--- /dev/null
+++ b/benchmark/common/components.go
@@ -0,0 +1,50 @@
+package common
+
+import "github.com/mlange-42/arche/ecs"
+
+type Position struct {
+	X int
+	Y int
+}
+
+type Rotation struct {
+	Angle int
+}
+
+type TestStruct0 struct{ val int32 }
+type TestStruct1 struct{ val int32 }
+type TestStruct2 struct{ val int32 }
+type TestStruct3 struct{ val int32 }
+type TestStruct4 struct{ val int32 }
+type TestStruct5 struct{ val int32 }
+type TestStruct6 struct{ val int32 }
+type TestStruct7 struct{ val int32 }
+type TestStruct8 struct{ val int32 }
+type TestStruct9 struct{ val int32 }
+
+func RegisterAll(w *ecs.World) []ecs.ID {
+	_ = TestStruct0{1}
+	_ = TestStruct1{1}
+	_ = TestStruct2{1}
+	_ = TestStruct3{1}
+	_ = TestStruct4{1}
+	_ = TestStruct5{1}
+	_ = TestStruct6{1}
+	_ = TestStruct7{1}
+	_ = TestStruct8{1}
+	_ = TestStruct9{1}
+
+	ids := make([]ecs.ID, 10)
+	ids[0] = ecs.ComponentID[TestStruct0](w)
+	ids[1] = ecs.ComponentID[TestStruct1](w)
+	ids[2] = ecs.ComponentID[TestStruct2](w)
+	ids[3] = ecs.ComponentID[TestStruct3](w)
+	ids[4] = ecs.ComponentID[TestStruct4](w)
+	ids[5] = ecs.ComponentID[TestStruct5](w)
+	ids[6] = ecs.ComponentID[TestStruct6](w)
+	ids[7] = ecs.ComponentID[TestStruct7](w)
+	ids[8] = ecs.ComponentID[TestStruct8](w)
+	ids[9] = ecs.ComponentID[TestStruct9](w)
+
+	return ids
+}
diff --git a/benchmark/iterate/components.go b/benchmark/iterate/components.go
deleted file mode 100644
index ce954b7..0000000
--- a/benchmark/iterate/components.go
+++ /dev/null
@@ -1,50 +0,0 @@
-package iterate
-
-import "github.com/mlange-42/arche/ecs"
-
-type position struct {
-	X int
-	Y int
-}
-
-type rotation struct {
-	Angle int
-}
-
-type testStruct0 struct{ val int32 }
-type testStruct1 struct{ val int32 }
-type testStruct2 struct{ val int32 }
-type testStruct3 struct{ val int32 }
-type testStruct4 struct{ val int32 }
-type testStruct5 struct{ val int32 }
-type testStruct6 struct{ val int32 }
-type testStruct7 struct{ val int32 }
-type testStruct8 struct{ val int32 }
-type testStruct9 struct{ val int32 }
-
-func registerAll(w *ecs.World) []ecs.ID {
-	_ = testStruct0{1}
-	_ = testStruct1{1}
-	_ = testStruct2{1}
-	_ = testStruct3{1}
-	_ = testStruct4{1}
-	_ = testStruct5{1}
-	_ = testStruct6{1}
-	_ = testStruct7{1}
-	_ = testStruct8{1}
-	_ = testStruct9{1}
-
-	ids := make([]ecs.ID, 10)
-	ids[0] = ecs.ComponentID[testStruct0](w)
-	ids[1] = ecs.ComponentID[testStruct1](w)
-	ids[2] = ecs.ComponentID[testStruct2](w)
-	ids[3] = ecs.ComponentID[testStruct3](w)
-	ids[4] = ecs.ComponentID[testStruct4](w)
-	ids[5] = ecs.ComponentID[testStruct5](w)
-	ids[6] = ecs.ComponentID[testStruct6](w)
-	ids[7] = ecs.ComponentID[testStruct7](w)
-	ids[8] = ecs.ComponentID[testStruct8](w)
-	ids[9] = ecs.ComponentID[testStruct9](w)
-
-	return ids
-}
diff --git a/benchmark/profile/exchange/main.go b/benchmark/profile/exchange/main.go
new file mode 100644
index 0000000..d53b9c9
--- /dev/null
+++ b/benchmark/profile/exchange/main.go
@@ -0,0 +1,70 @@
+package main
+
+// Profiling:
+// go build ./benchmark/profile/exchange
+// exchange
+// go tool pprof -http=":8000" -nodefraction=0.001 main cpu.pprof
+// go tool pprof -http=":8000" -nodefraction=0.001 main mem.pprof
+
+import (
+	"github.com/mlange-42/arche/ecs"
+	"github.com/pkg/profile"
+)
+
+type position struct {
+	X int
+	Y int
+}
+
+type velocity struct {
+	X int
+	Y int
+}
+
+type rotation struct {
+	Angle int
+}
+
+func main() {
+
+	count := 100
+	iters := 250
+	entities := 1000
+
+	stop := profile.Start(profile.CPUProfile, profile.ProfilePath("."))
+	run(count, iters, entities)
+	stop.Stop()
+
+	stop = profile.Start(profile.MemProfileAllocs, profile.ProfilePath("."))
+	run(count, iters, entities)
+	stop.Stop()
+}
+
+func run(rounds, iters, numEntities int) {
+	for i := 0; i < rounds; i++ {
+		world := ecs.NewConfig().
+			WithCapacityIncrement(1024).
+			Build()
+
+		posID := ecs.ComponentID[position](&world)
+		velID := ecs.ComponentID[velocity](&world)
+		rotID := ecs.ComponentID[rotation](&world)
+
+		entities := make([]ecs.Entity, numEntities)
+		for j := 0; j < numEntities; j++ {
+			entity := world.NewEntity(posID, velID)
+			entities[j] = entity
+		}
+
+		for j := 0; j < iters; j++ {
+			add := []ecs.ID{rotID}
+			remove := []ecs.ID{velID}
+			if j%2 != 0 {
+				add, remove = remove, add
+			}
+			for _, entity := range entities {
+				world.Exchange(entity, add, remove)
+			}
+		}
+	}
+}
diff --git a/benchmark/profile/main/profile.go b/benchmark/profile/iter/main.go
similarity index 96%
rename from benchmark/profile/main/profile.go
rename to benchmark/profile/iter/main.go
index 4c24c2b..1f03915 100644
--- a/benchmark/profile/main/profile.go
+++ b/benchmark/profile/iter/main.go
@@ -1,8 +1,8 @@
 package main
 
 // Profiling:
-// go build ./benchmark/profile/main
-// main
+// go build ./benchmark/profile/iter
+// iter
 // go tool pprof -http=":8000" -nodefraction=0.001 main cpu.pprof
 // go tool pprof -http=":8000" -nodefraction=0.001 main mem.pprof
 
diff --git a/generic/mask.go b/generic/mask.go
index e5bb3f8..3ddc68b 100644
--- a/generic/mask.go
+++ b/generic/mask.go
@@ -10,6 +10,11 @@ func T[A any]() Comp {
 	return Comp(typeOf[A]())
 }
 
+// T1 creates a component type list for one component types.
+func T1[A any]() []Comp {
+	return []Comp{typeOf[A]()}
+}
+
 // T2 creates a component type list for two component types.
 func T2[A any, B any]() []Comp {
 	return []Comp{typeOf[A](), typeOf[B]()}


Test patch:
diff --git a/benchmark/add/arche_test.go b/benchmark/add/arche_test.go
index 72f6b61..dcf7e6a 100644
--- a/benchmark/add/arche_test.go
+++ b/benchmark/add/arche_test.go
@@ -3,6 +3,7 @@ package add
 import (
 	"testing"
 
+	c "github.com/mlange-42/arche/benchmark/common"
 	"github.com/mlange-42/arche/ecs"
 	"github.com/mlange-42/arche/generic"
 )
@@ -12,8 +13,8 @@ func addArcheWorld(b *testing.B, count int) {
 		b.StopTimer()
 		world := ecs.NewWorld()
 
-		posID := ecs.ComponentID[position](&world)
-		rotID := ecs.ComponentID[rotation](&world)
+		posID := ecs.ComponentID[c.Position](&world)
+		rotID := ecs.ComponentID[c.Rotation](&world)
 		b.StartTimer()
 
 		for i := 0; i < count; i++ {
@@ -26,7 +27,7 @@ func addArcheGeneric(b *testing.B, count int) {
 	for i := 0; i < b.N; i++ {
 		b.StopTimer()
 		world := ecs.NewWorld()
-		mut := generic.NewMap2[position, rotation](&world)
+		mut := generic.NewMap2[c.Position, c.Rotation](&world)
 		b.StartTimer()
 
 		for i := 0; i < count; i++ {
diff --git a/benchmark/add/ggecs_test.go b/benchmark/add/ggecs_test.go
index cabf087..ec9a2b2 100644
--- a/benchmark/add/ggecs_test.go
+++ b/benchmark/add/ggecs_test.go
@@ -4,13 +4,14 @@ import (
 	"testing"
 
 	ecs "github.com/marioolofo/go-gameengine-ecs"
+	c "github.com/mlange-42/arche/benchmark/common"
 )
 
 func addGameEngineEcs(b *testing.B, count int) {
 	b.StopTimer()
 	comps := []ecs.ComponentConfig{
-		{ID: 0, Component: position{}},
-		{ID: 1, Component: rotation{}},
+		{ID: 0, Component: c.Position{}},
+		{ID: 1, Component: c.Rotation{}},
 	}
 	b.StartTimer()
 
diff --git a/benchmark/iterate/components_test.go b/benchmark/common/components_test.go
similarity index 78%
rename from benchmark/iterate/components_test.go
rename to benchmark/common/components_test.go
index 7c59357..492cb3b 100644
--- a/benchmark/iterate/components_test.go
+++ b/benchmark/common/components_test.go
@@ -1,4 +1,4 @@
-package iterate
+package common
 
 import (
 	"testing"
@@ -8,5 +8,5 @@ import (
 
 func TestRegisterAll(t *testing.T) {
 	w := ecs.NewWorld()
-	registerAll(&w)
+	RegisterAll(&w)
 }
diff --git a/benchmark/iterate/arche_test.go b/benchmark/iterate/arche_test.go
index 26cb0dd..767c476 100644
--- a/benchmark/iterate/arche_test.go
+++ b/benchmark/iterate/arche_test.go
@@ -3,6 +3,7 @@ package iterate
 import (
 	"testing"
 
+	c "github.com/mlange-42/arche/benchmark/common"
 	"github.com/mlange-42/arche/ecs"
 	"github.com/mlange-42/arche/filter"
 	"github.com/mlange-42/arche/generic"
@@ -12,8 +13,8 @@ func runArcheQuery(b *testing.B, count int) {
 	b.StopTimer()
 	world := ecs.NewWorld()
 
-	posID := ecs.ComponentID[position](&world)
-	rotID := ecs.ComponentID[rotation](&world)
+	posID := ecs.ComponentID[c.Position](&world)
+	rotID := ecs.ComponentID[c.Rotation](&world)
 
 	for i := 0; i < count; i++ {
 		_ = world.NewEntity(posID, rotID)
@@ -25,7 +26,7 @@ func runArcheQuery(b *testing.B, count int) {
 		query := world.Query(ecs.All(posID, rotID))
 		b.StartTimer()
 		for query.Next() {
-			pos := (*position)(query.Get(posID))
+			pos := (*c.Position)(query.Get(posID))
 			_ = pos
 		}
 	}
@@ -35,8 +36,8 @@ func runArcheFilter(b *testing.B, count int) {
 	b.StopTimer()
 	world := ecs.NewWorld()
 
-	posID := ecs.ComponentID[position](&world)
-	rotID := ecs.ComponentID[rotation](&world)
+	posID := ecs.ComponentID[c.Position](&world)
+	rotID := ecs.ComponentID[c.Rotation](&world)
 
 	for i := 0; i < count; i++ {
 		_ = world.NewEntity(posID, rotID)
@@ -48,7 +49,7 @@ func runArcheFilter(b *testing.B, count int) {
 		query := world.Query(filter.All(posID, rotID))
 		b.StartTimer()
 		for query.Next() {
-			pos := (*position)(query.Get(posID))
+			pos := (*c.Position)(query.Get(posID))
 			_ = pos
 		}
 	}
@@ -58,13 +59,13 @@ func runArcheQueryGeneric(b *testing.B, count int) {
 	b.StopTimer()
 	world := ecs.NewWorld()
 
-	posID := ecs.ComponentID[position](&world)
-	rotID := ecs.ComponentID[rotation](&world)
+	posID := ecs.ComponentID[c.Position](&world)
+	rotID := ecs.ComponentID[c.Rotation](&world)
 
 	for i := 0; i < count; i++ {
 		_ = world.NewEntity(posID, rotID)
 	}
-	query := generic.NewFilter1[position]()
+	query := generic.NewFilter1[c.Position]()
 	b.StartTimer()
 
 	for i := 0; i < b.N; i++ {
@@ -82,11 +83,11 @@ func runArcheQuery5C(b *testing.B, count int) {
 	b.StopTimer()
 	world := ecs.NewWorld()
 
-	id0 := ecs.ComponentID[testStruct0](&world)
-	id1 := ecs.ComponentID[testStruct1](&world)
-	id2 := ecs.ComponentID[testStruct2](&world)
-	id3 := ecs.ComponentID[testStruct3](&world)
-	id4 := ecs.ComponentID[testStruct4](&world)
+	id0 := ecs.ComponentID[c.TestStruct0](&world)
+	id1 := ecs.ComponentID[c.TestStruct1](&world)
+	id2 := ecs.ComponentID[c.TestStruct2](&world)
+	id3 := ecs.ComponentID[c.TestStruct3](&world)
+	id4 := ecs.ComponentID[c.TestStruct4](&world)
 
 	for i := 0; i < count; i++ {
 		_ = world.NewEntity(id0, id1, id2, id3, id4)
@@ -98,11 +99,11 @@ func runArcheQuery5C(b *testing.B, count int) {
 		query := world.Query(ecs.All(id0, id1, id2, id3, id4))
 		b.StartTimer()
 		for query.Next() {
-			t1 := (*testStruct0)(query.Get(id0))
-			t2 := (*testStruct1)(query.Get(id1))
-			t3 := (*testStruct2)(query.Get(id2))
-			t4 := (*testStruct3)(query.Get(id3))
-			t5 := (*testStruct4)(query.Get(id4))
+			t1 := (*c.TestStruct0)(query.Get(id0))
+			t2 := (*c.TestStruct1)(query.Get(id1))
+			t3 := (*c.TestStruct2)(query.Get(id2))
+			t4 := (*c.TestStruct3)(query.Get(id3))
+			t5 := (*c.TestStruct4)(query.Get(id4))
 			_, _, _, _, _ = t1, t2, t3, t4, t5
 		}
 	}
@@ -112,17 +113,17 @@ func runArcheQueryGeneric5C(b *testing.B, count int) {
 	b.StopTimer()
 	world := ecs.NewWorld()
 
-	id0 := ecs.ComponentID[testStruct0](&world)
-	id1 := ecs.ComponentID[testStruct1](&world)
-	id2 := ecs.ComponentID[testStruct2](&world)
-	id3 := ecs.ComponentID[testStruct3](&world)
-	id4 := ecs.ComponentID[testStruct4](&world)
+	id0 := ecs.ComponentID[c.TestStruct0](&world)
+	id1 := ecs.ComponentID[c.TestStruct1](&world)
+	id2 := ecs.ComponentID[c.TestStruct2](&world)
+	id3 := ecs.ComponentID[c.TestStruct3](&world)
+	id4 := ecs.ComponentID[c.TestStruct4](&world)
 
 	for i := 0; i < count; i++ {
 		_ = world.NewEntity(id0, id1, id2, id3, id4)
 	}
 
-	query := generic.NewFilter5[testStruct0, testStruct1, testStruct2, testStruct3, testStruct4]()
+	query := generic.NewFilter5[c.TestStruct0, c.TestStruct1, c.TestStruct2, c.TestStruct3, c.TestStruct4]()
 	b.StartTimer()
 
 	for i := 0; i < b.N; i++ {
@@ -138,7 +139,7 @@ func runArcheQueryGeneric5C(b *testing.B, count int) {
 func runArcheQuery1kArch(b *testing.B, count int) {
 	b.StopTimer()
 	world := ecs.NewWorld()
-	registerAll(&world)
+	c.RegisterAll(&world)
 
 	perArch := 2 * count / 1000
 
@@ -165,7 +166,7 @@ func runArcheQuery1kArch(b *testing.B, count int) {
 		query := world.Query(ecs.All(6))
 		b.StartTimer()
 		for query.Next() {
-			pos := (*position)(query.Get(6))
+			pos := (*c.Position)(query.Get(6))
 			_ = pos
 		}
 	}
@@ -174,7 +175,7 @@ func runArcheQuery1kArch(b *testing.B, count int) {
 func runArcheFilter1kArch(b *testing.B, count int) {
 	b.StopTimer()
 	world := ecs.NewWorld()
-	registerAll(&world)
+	c.RegisterAll(&world)
 
 	perArch := 2 * count / 1000
 
@@ -201,7 +202,7 @@ func runArcheFilter1kArch(b *testing.B, count int) {
 		query := world.Query(filter.All(6))
 		b.StartTimer()
 		for query.Next() {
-			pos := (*position)(query.Get(6))
+			pos := (*c.Position)(query.Get(6))
 			_ = pos
 		}
 	}
@@ -211,8 +212,8 @@ func runArcheWorld(b *testing.B, count int) {
 	b.StopTimer()
 	world := ecs.NewWorld()
 
-	posID := ecs.ComponentID[position](&world)
-	rotID := ecs.ComponentID[rotation](&world)
+	posID := ecs.ComponentID[c.Position](&world)
+	rotID := ecs.ComponentID[c.Rotation](&world)
 
 	for i := 0; i < count; i++ {
 		_ = world.NewEntity(posID, rotID)
@@ -225,7 +226,7 @@ func runArcheWorld(b *testing.B, count int) {
 		b.StartTimer()
 		for query.Next() {
 			entity := query.Entity()
-			pos := (*position)(world.Get(entity, posID))
+			pos := (*c.Position)(world.Get(entity, posID))
 			_ = pos
 		}
 	}
@@ -235,10 +236,10 @@ func runArcheWorldGeneric(b *testing.B, count int) {
 	b.StopTimer()
 	world := ecs.NewWorld()
 
-	posID := ecs.ComponentID[position](&world)
-	rotID := ecs.ComponentID[rotation](&world)
+	posID := ecs.ComponentID[c.Position](&world)
+	rotID := ecs.ComponentID[c.Rotation](&world)
 
-	get := generic.NewMap[position](&world)
+	get := generic.NewMap[c.Position](&world)
 
 	for i := 0; i < count; i++ {
 		_ = world.NewEntity(posID, rotID)
diff --git a/benchmark/iterate/ggecs_test.go b/benchmark/iterate/ggecs_test.go
index 4f60c7c..f26c5e0 100644
--- a/benchmark/iterate/ggecs_test.go
+++ b/benchmark/iterate/ggecs_test.go
@@ -4,13 +4,14 @@ import (
 	"testing"
 
 	ecs "github.com/marioolofo/go-gameengine-ecs"
+	c "github.com/mlange-42/arche/benchmark/common"
 )
 
 func runGameEngineEcs(b *testing.B, count int) {
 	b.StopTimer()
 	comps := []ecs.ComponentConfig{
-		{ID: 0, Component: position{}},
-		{ID: 1, Component: rotation{}},
+		{ID: 0, Component: c.Position{}},
+		{ID: 1, Component: c.Rotation{}},
 	}
 	world := ecs.NewWorld(comps...)
 
@@ -24,7 +25,7 @@ func runGameEngineEcs(b *testing.B, count int) {
 
 	for i := 0; i < b.N; i++ {
 		for _, e := range filter.Entities() {
-			pos := (*position)(world.Component(e, 0))
+			pos := (*c.Position)(world.Component(e, 0))
 			_ = pos
 		}
 	}
@@ -33,11 +34,11 @@ func runGameEngineEcs(b *testing.B, count int) {
 func runGameEngineEcs5C(b *testing.B, count int) {
 	b.StopTimer()
 	comps := []ecs.ComponentConfig{
-		{ID: 0, Component: testStruct0{}},
-		{ID: 1, Component: testStruct1{}},
-		{ID: 2, Component: testStruct2{}},
-		{ID: 3, Component: testStruct3{}},
-		{ID: 4, Component: testStruct4{}},
+		{ID: 0, Component: c.TestStruct0{}},
+		{ID: 1, Component: c.TestStruct1{}},
+		{ID: 2, Component: c.TestStruct2{}},
+		{ID: 3, Component: c.TestStruct3{}},
+		{ID: 4, Component: c.TestStruct4{}},
 	}
 	world := ecs.NewWorld(comps...)
 
@@ -51,11 +52,11 @@ func runGameEngineEcs5C(b *testing.B, count int) {
 
 	for i := 0; i < b.N; i++ {
 		for _, e := range filter.Entities() {
-			t1 := (*testStruct0)(world.Component(e, 0))
-			t2 := (*testStruct1)(world.Component(e, 1))
-			t3 := (*testStruct2)(world.Component(e, 2))
-			t4 := (*testStruct3)(world.Component(e, 3))
-			t5 := (*testStruct4)(world.Component(e, 4))
+			t1 := (*c.TestStruct0)(world.Component(e, 0))
+			t2 := (*c.TestStruct1)(world.Component(e, 1))
+			t3 := (*c.TestStruct2)(world.Component(e, 2))
+			t4 := (*c.TestStruct3)(world.Component(e, 3))
+			t5 := (*c.TestStruct4)(world.Component(e, 4))
 			_, _, _, _, _ = t1, t2, t3, t4, t5
 		}
 	}
@@ -64,16 +65,16 @@ func runGameEngineEcs5C(b *testing.B, count int) {
 func runGameEngineEcs1kArch(b *testing.B, count int) {
 	b.StopTimer()
 	comps := []ecs.ComponentConfig{
-		{ID: 0, Component: testStruct0{}},
-		{ID: 1, Component: testStruct1{}},
-		{ID: 2, Component: testStruct2{}},
-		{ID: 3, Component: testStruct3{}},
-		{ID: 4, Component: testStruct4{}},
-		{ID: 5, Component: testStruct5{}},
-		{ID: 6, Component: testStruct6{}},
-		{ID: 7, Component: testStruct7{}},
-		{ID: 8, Component: testStruct8{}},
-		{ID: 9, Component: testStruct9{}},
+		{ID: 0, Component: c.TestStruct0{}},
+		{ID: 1, Component: c.TestStruct1{}},
+		{ID: 2, Component: c.TestStruct2{}},
+		{ID: 3, Component: c.TestStruct3{}},
+		{ID: 4, Component: c.TestStruct4{}},
+		{ID: 5, Component: c.TestStruct5{}},
+		{ID: 6, Component: c.TestStruct6{}},
+		{ID: 7, Component: c.TestStruct7{}},
+		{ID: 8, Component: c.TestStruct8{}},
+		{ID: 9, Component: c.TestStruct9{}},
 	}
 	world := ecs.NewWorld(comps...)
 
@@ -101,7 +102,7 @@ func runGameEngineEcs1kArch(b *testing.B, count int) {
 
 	for i := 0; i < b.N; i++ {
 		for _, e := range filter.Entities() {
-			pos := (*position)(world.Component(e, 6))
+			pos := (*c.Position)(world.Component(e, 6))
 			_ = pos
 		}
 	}
diff --git a/benchmark/move/arche_test.go b/benchmark/move/arche_test.go
new file mode 100644
index 0000000..fc71c24
--- /dev/null
+++ b/benchmark/move/arche_test.go
@@ -0,0 +1,53 @@
+package move
+
+import (
+	"testing"
+
+	c "github.com/mlange-42/arche/benchmark/common"
+	"github.com/mlange-42/arche/ecs"
+	g "github.com/mlange-42/arche/generic"
+)
+
+func runArcheMove(b *testing.B, count int, add, rem []g.Comp) {
+	for i := 0; i < b.N; i++ {
+		for i := 0; i < b.N; i++ {
+			b.StopTimer()
+			world := ecs.NewConfig().WithCapacityIncrement(1024).Build()
+			c.RegisterAll(&world)
+
+			addIDs := make([]ecs.ID, len(add))
+			remIDs := make([]ecs.ID, len(rem))
+			for i, t := range add {
+				addIDs[i] = ecs.TypeID(&world, t)
+			}
+			for i, t := range rem {
+				remIDs[i] = ecs.TypeID(&world, t)
+			}
+
+			entities := make([]ecs.Entity, count)
+			for i := 0; i < count; i++ {
+				e := world.NewEntity(addIDs...)
+				entities[i] = e
+			}
+			b.StartTimer()
+
+			for _, e := range entities {
+				world.Remove(e, remIDs...)
+			}
+		}
+	}
+}
+
+func BenchmarkArcheMove_1C_1_000(b *testing.B) {
+	runArcheMove(b, 1000,
+		g.T1[c.TestStruct0](),
+		g.T1[c.TestStruct0](),
+	)
+}
+
+func BenchmarkArcheMove_5C_1_000(b *testing.B) {
+	runArcheMove(b, 1000,
+		g.T5[c.TestStruct0, c.TestStruct1, c.TestStruct2, c.TestStruct3, c.TestStruct4](),
+		g.T1[c.TestStruct0](),
+	)
+}
diff --git a/generic/mask_test.go b/generic/mask_test.go
index 77f58d8..a66ddbd 100644
--- a/generic/mask_test.go
+++ b/generic/mask_test.go
@@ -12,6 +12,13 @@ func TestGenericMask(t *testing.T) {
 		Comp(typeOf[testStruct0]()),
 	)
 
+	assert.Equal(t,
+		T1[testStruct0](),
+		[]Comp{
+			typeOf[testStruct0](),
+		},
+	)
+
 	assert.Equal(t,
 		T2[testStruct0, testStruct1](),
 		[]Comp{