Skip to content

Function "Within" is slower than iterating with "At" #8

@Xkonti

Description

@Xkonti

I noticed that when using the Within on the grid to iterate through values of all tiles it's slower than when iterating by simply using At. Here are benchmarks I quickly wrote:

Benchmark testing the performance of the built-in Within:

func BenchmarkGridWithin(b *testing.B) {
	// Generate map
	theMap := tile.NewGridOf[tileData](900, 900)
	theMap.Each(func(pos point, currentTile tile.Tile[tileData]) {
		currentTile.Write(getRandomSliceIndex(tileImages))
	})

	// Benchmark
	chunkSize := 9
	for i := 0; i < b.N; i++ {
		pos := point{X: int16((i * 9) % 900), Y: int16((i * 45) % 900)}
		theMap.Within(pos, pos.Add(point{X: chunkSize * 3, Y: chunkSize * 3}), func(tilePos point, currentTile tile.Tile[tileData]) {
			value := currentTile.Value()
			_ = tileImages[int(value)]
		})
	}
}

Benchmark using At where whole chunk is scanned with a simple double for loop:

func BenchmarkGridAtScan(b *testing.B) {
	// Generate map
	theMap := tile.NewGridOf[tileData](900, 900)
	theMap.Each(func(pos point, currentTile tile.Tile[tileData]) {
		currentTile.Write(getRandomSliceIndex(tileImages))
	})

	// Benchmark
	chunkSize := 9
	for i := 0; i < b.N; i++ {
		pos := point{X: int16((i * 9) % 900), Y: int16((i * 45) % 900)}
		for x := pos.X; x < pos.X+chunkSize*3; x++ {
			for y := pos.Y; y < pos.Y+chunkSize*3; y++ {
				currentTile, ok := theMap.At(x, y)
				if !ok {
					continue
				}

				value := currentTile.Value()
				_ = tileImages[int(value)]
			}
		}
	}
}

A benchmark that tries to get values using At but working on a page at a time:

func BenchmarkGridAtPage(b *testing.B) {
	// Generate map
	theMap := tile.NewGridOf[tileData](900, 900)
	theMap.Each(func(pos point, currentTile tile.Tile[tileData]) {
		currentTile.Write(getRandomSliceIndex(tileImages))
	})

	// Benchmark
	chunkSize := 9
	for i := 0; i < b.N; i++ {
		pos := point{X: int16((i * 9) % 900), Y: int16((i * 45) % 900)}
                // Iterate through pages
		for x := pos.X; x < pos.X+chunkSize*3; x += 3 {
			for y := pos.Y; y < pos.Y+chunkSize*3; y += 3 {
                                // Get all tiles in a page
				for tileX := x; tileX < x+3; tileX++ {
					for tileY := y; tileY < y+3; tileY++ {
						currentTile, ok := theMap.At(tileX, tileY)
						if !ok {
							continue
						}

						value := currentTile.Value()
						_ = tileImages[int(value)]
					}
				}
			}
		}
	}
}

Results show that At is significantly faster than Within. Also accessing tile's values page-at-a-time increases performance by only ≈0.82%:

goos: windows
goarch: amd64
pkg: tile-game
cpu: 12th Gen Intel(R) Core(TM) i9-12900KF
BenchmarkGridWithin-24            531561              2251 ns/op
BenchmarkGridAtScan-24            656180              1826 ns/op
BenchmarkGridAtPage-24            661200              1811 ns/op

I'm not sure if that's intended behavior but something tells me there's an opportunity for adding an optimized values getter.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions