elys icon indicating copy to clipboard operation
elys copied to clipboard

refactor: replace []byte(fmt.Sprintf) with fmt.Appendf

Open yetyear opened this issue 8 months ago • 2 comments

Description

Optimize code using a more modern writing style. Official support from Go, for more details visit https://pkg.go.dev/golang.org/x/tools/gopls/internal/analysis/modernize.

What has Changed?

What specific problem were you aiming to address, and how did you successfully resolve it? If tests were not uploaded for this pull request or if coverage decreased, please provide an explanation for the change.


Author Checklist

All items are required. Please add a note to the item if the item is not applicable and please add links to any relevant follow up issues.

I have...

  • [x] included the correct type prefix in the PR title
  • [ ] added ! to the type prefix if API or client breaking change
  • [x] targeted the correct branch (see PR Targeting)
  • [ ] provided a link to the relevant issue or specification
  • [ ] followed the guidelines for building modules
  • [ ] included the necessary unit and integration tests
  • [ ] included comments for documenting Go code
  • [ ] updated the relevant documentation or specification
  • [ ] reviewed "Files changed" and left comments if necessary
  • [ ] confirmed all CI checks have passed

Reviewers Checklist

All items are required. Please add a note if the item is not applicable and please add your handle next to the items reviewed if you only reviewed selected items.

I have...

  • [ ] confirmed the correct type prefix in the PR title
  • [ ] confirmed ! in the type prefix if API or client breaking change
  • [ ] confirmed all author checklist items have been addressed
  • [ ] reviewed state machine logic
  • [ ] reviewed API design and naming
  • [ ] reviewed documentation is accurate
  • [ ] reviewed tests and test coverage
  • [ ] manually tested (if applicable)

Deployment Notes

Are there any specific considerations to take into account when deploying these changes? This may include new dependencies, scripts that need to be executed, or any aspects that can only be evaluated in a deployed environment.

Screenshots and Videos

Please provide any relevant before and after screenshots by uploading them here. Additionally, demo videos can be highly beneficial in demonstrating the process.

yetyear avatar May 09 '25 04:05 yetyear

This seems slower than the current code based on benchmark tests and a same number of allocations:

import (
	"fmt"
	"testing"
)

var denom = "atom"

// Benchmark using fmt.Sprintf + conversion to []byte
func BenchmarkSprintfToByte(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		_ = []byte(fmt.Sprintf("%s/", denom))
	}
}

// Benchmark using fmt.Appendf (Go 1.21+)
func BenchmarkFmtAppendf(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		_ = fmt.Appendf(nil, "%s/", denom)
	}
}

// Benchmark using a plain append for one alloc
func BenchmarkAppendLiteral(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		_ = append([]byte(denom), '/')
	}
}

Result:

go test -bench=. -benchmem
goos: darwin
goarch: arm64
pkg: github.com/elys-network/elys
cpu: Apple M2
BenchmarkSprintfToByte-8        22679624                56.99 ns/op           21 B/op          2 allocs/op
BenchmarkFmtAppendf-8           20122227                59.30 ns/op           24 B/op          2 allocs/op
BenchmarkAppendLiteral-8        452519115                2.651 ns/op           0 B/op          0 allocs/op
PASS
ok      github.com/elys-network/elys    5.382s

Using the append literal seems a lot faster @yetyear

avkr003 avatar May 09 '25 16:05 avkr003

This seems slower than the current code based on benchmark tests and a same number of allocations:

import (
	"fmt"
	"testing"
)

var denom = "atom"

// Benchmark using fmt.Sprintf + conversion to []byte
func BenchmarkSprintfToByte(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		_ = []byte(fmt.Sprintf("%s/", denom))
	}
}

// Benchmark using fmt.Appendf (Go 1.21+)
func BenchmarkFmtAppendf(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		_ = fmt.Appendf(nil, "%s/", denom)
	}
}

// Benchmark using a plain append for one alloc
func BenchmarkAppendLiteral(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		_ = append([]byte(denom), '/')
	}
}

Result:

go test -bench=. -benchmem
goos: darwin
goarch: arm64
pkg: github.com/elys-network/elys
cpu: Apple M2
BenchmarkSprintfToByte-8        22679624                56.99 ns/op           21 B/op          2 allocs/op
BenchmarkFmtAppendf-8           20122227                59.30 ns/op           24 B/op          2 allocs/op
BenchmarkAppendLiteral-8        452519115                2.651 ns/op           0 B/op          0 allocs/op
PASS
ok      github.com/elys-network/elys    5.382s

Using the append literal seems a lot faster @yetyear

I conducted a test, and it is related to the length of the string. For example, the performance test below:

package main

import (
	"fmt"
	"testing"
)

var demoStr = "testStr"

func init() {
	for range 10 {
		demoStr += demoStr
	}
}

// Benchmark using fmt.Sprintf + conversion to []byte
func BenchmarkSprintfToByte(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		_ = []byte(fmt.Sprintf("%s/", demoStr))
	}
}

// Benchmark using fmt.Appendf (Go 1.21+)
func BenchmarkFmtAppendf(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		_ = fmt.Appendf(nil, "%s/", demoStr)
	}
}

// Benchmark using a plain append for one alloc
func BenchmarkAppendLiteral(b *testing.B) {
	b.ReportAllocs()
	for i := 0; i < b.N; i++ {
		_ = append([]byte(demoStr), '/')
	}
}


The result:

goos: darwin
goarch: arm64
pkg: demo/fmtappendf
cpu: Apple M2
BenchmarkSprintfToByte-8         1119710               927.7 ns/op          8216 B/op          2 allocs/op
BenchmarkSprintfToByte-8         1398566               859.2 ns/op          8217 B/op          2 allocs/op
BenchmarkSprintfToByte-8         1407386               852.1 ns/op          8217 B/op          2 allocs/op
BenchmarkSprintfToByte-8         1410823               849.3 ns/op          8217 B/op          2 allocs/op
BenchmarkSprintfToByte-8         1383681               854.2 ns/op          8217 B/op          2 allocs/op
BenchmarkFmtAppendf-8            1397036               853.7 ns/op          8217 B/op          2 allocs/op
BenchmarkFmtAppendf-8            1410096               863.1 ns/op          8216 B/op          2 allocs/op
BenchmarkFmtAppendf-8            1391970               960.4 ns/op          8216 B/op          2 allocs/op
BenchmarkFmtAppendf-8            1382676               863.9 ns/op          8216 B/op          2 allocs/op
BenchmarkFmtAppendf-8            1374649               859.2 ns/op          8216 B/op          2 allocs/op
BenchmarkAppendLiteral-8         1615996               743.3 ns/op          8192 B/op          1 allocs/op
BenchmarkAppendLiteral-8         1607259               763.0 ns/op          8192 B/op          1 allocs/op
BenchmarkAppendLiteral-8         1565144              1016 ns/op            8192 B/op          1 allocs/op
BenchmarkAppendLiteral-8         1588842               850.6 ns/op          8192 B/op          1 allocs/op
BenchmarkAppendLiteral-8         1383158              1134 ns/op            8192 B/op          1 allocs/op
PASS
ok     test/fmtappendf 32.094s

I don't think it's a good idea to change it to the third way here. Do you have any more suggestions?

yetyear avatar May 14 '25 16:05 yetyear