Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Open
Xkonti opened this issue Sep 12, 2023 · 0 comments
Open

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

Xkonti opened this issue Sep 12, 2023 · 0 comments

Comments

@Xkonti
Copy link
Contributor

Xkonti commented Sep 12, 2023

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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant