Skip to content

perf(spanner): improve mutationProto allocations and performance#12740

Merged
rahul2393 merged 2 commits intomainfrom
improve-mutation-proto
Aug 22, 2025
Merged

perf(spanner): improve mutationProto allocations and performance#12740
rahul2393 merged 2 commits intomainfrom
improve-mutation-proto

Conversation

@rahul2393
Copy link
Contributor

Fixes: #12680

@rahul2393 rahul2393 requested a review from a team as a code owner August 20, 2025 17:49
@rahul2393 rahul2393 requested a review from a team August 20, 2025 17:49
@product-auto-label product-auto-label bot added the api: spanner Issues related to the Spanner API. label Aug 20, 2025
@rahul2393
Copy link
Contributor Author

The benchmark results indicate a significant performance improvement. The mutationsProto function is now approximately 81% faster, uses 57% less memory, and makes 16% fewer memory allocations on average.

goos: darwin
goarch: arm64
pkg: cloud.google.com/go/spanner
cpu: Apple M1 Pro
                                            │  before.txt  │              after.txt              │
                                            │    sec/op    │   sec/op     vs base                │
MutationsProto/small_number_of_mutations-10   13.778µ ± 1%   1.548µ ± 3%  -88.77% (p=0.000 n=20)
MutationsProto/large_number_of_mutations-10    43.83µ ± 1%   30.51µ ± 1%  -30.38% (p=0.000 n=20)
MutationsProto/mixed_type_of_mutations-10     13.289µ ± 1%   1.140µ ± 1%  -91.42% (p=0.000 n=20)
geomean                                        20.02µ        3.776µ       -81.14%

                                            │  before.txt  │              after.txt               │
                                            │     B/op     │     B/op      vs base                │
MutationsProto/small_number_of_mutations-10   8.844Ki ± 0%   2.961Ki ± 0%  -66.52% (p=0.000 n=20)
MutationsProto/large_number_of_mutations-10   67.10Ki ± 0%   59.16Ki ± 0%  -11.84% (p=0.000 n=20)
MutationsProto/mixed_type_of_mutations-10     7.979Ki ± 0%   2.164Ki ± 0%  -72.88% (p=0.000 n=20)
geomean                                       16.79Ki        7.237Ki       -56.90%

                                            │ before.txt  │              after.txt              │
                                            │  allocs/op  │  allocs/op   vs base                │
MutationsProto/small_number_of_mutations-10    84.00 ± 0%    67.00 ± 0%  -20.24% (p=0.000 n=20)
MutationsProto/large_number_of_mutations-10   1.343k ± 0%   1.321k ± 0%   -1.64% (p=0.000 n=20)
MutationsProto/mixed_type_of_mutations-10      64.00 ± 0%    49.00 ± 0%  -23.44% (p=0.000 n=20)
geomean                                        193.3         163.1       -15.63%

@rahul2393 rahul2393 requested a review from olavloite August 20, 2025 18:02
@rahul2393 rahul2393 requested a review from codyoss August 20, 2025 18:23
@rahul2393
Copy link
Contributor Author

@codyoss quick review here is really appreciated.

Comment on lines +494 to +496
if rand.Intn(nonInsertCount) == 0 {
selectedNonInsertIdx = i
}
Copy link

@pbotros pbotros Aug 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like how we're avoiding allocs now, but one improvement here is to avoid doing the rand.Intn() in a tightloop. Would you consider doing an extra loop through the mutations? I'd imagine that may actually result in better performance since rand methods are relatively slow.

Suggested change
if rand.Intn(nonInsertCount) == 0 {
selectedNonInsertIdx = i
}

and then later:

if nonInsertCount > 0 {
  selectedNonInsertIdx := rand.Intn(nonInsertCount)
  var selectedMutation *Mutation
	for i, outM := range out {
         inM := ms[i]
         if inM.op == opInsert {
			if selectedNonInsertIdx == 0 {
				selectedMutation = outM
                 break
			}
			selectedNonInsertIdx--
		}
      return out, selectedMutation, nil
	}
}

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, tried out the diff on my local branch and it didn't seem to be any faster. So nevermind here.

Comment on lines +494 to +496
if rand.Intn(nonInsertCount) == 0 {
selectedNonInsertIdx = i
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually, tried out the diff on my local branch and it didn't seem to be any faster. So nevermind here.

Comment on lines +494 to +496
if rand.Intn(nonInsertCount) == 0 {
selectedNonInsertIdx = i
}
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: at first glance it's a bit surprising that this ends up being a uniform distribution, so perhaps a comment can help here:

Suggested change
if rand.Intn(nonInsertCount) == 0 {
selectedNonInsertIdx = i
}
// Pick a non-insert mutation uniformly at random.
if rand.Intn(nonInsertCount) == 0 {
selectedNonInsertIdx = i
}

}

return l, nil, nil
return out, nil, nil
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This point will never be reached, right? Because we always have:

  1. An empty input slice of mutations, which causes an empty return
  2. At least one non-insert mutation.
  3. Or one insert mutation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is here because Golang expects a return at the end of method.

@rahul2393 rahul2393 merged commit 2a4add5 into main Aug 22, 2025
10 checks passed
@rahul2393 rahul2393 deleted the improve-mutation-proto branch August 22, 2025 06:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

api: spanner Issues related to the Spanner API.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

spanner: large number of unnecessary allocations constructing mutations in BatchWrite

3 participants