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

Ripley performance improvements #8

Closed
wants to merge 41 commits into from
Closed

Ripley performance improvements #8

wants to merge 41 commits into from

Conversation

eugenepaniot
Copy link
Contributor

@eugenepaniot eugenepaniot commented Mar 6, 2023

  1. Transitioned to using https://github.com/valyala/fasthttp/ for the HTTP client;
  2. Transitioned to using github.com/valyala/fastjson/ for fast json parsing;
  3. Add custom Connection Diler to count TCP metrics (connections, bytes);
  4. Add metrics server;
  5. Add other metrics;
  6. Add handleResult listener rather create goroutine;
  7. Buffered channels;
  8. Rewrite dummyweb server on fasthttp with reuseport;
  9. Add store and print N results with the longest latency;
  10. Improve error log reporting (connection errors, http errors 5xx);
  11. Other UX improvements.

Copy link
Contributor

@georgemalamidis-lh georgemalamidis-lh left a comment

Choose a reason for hiding this comment

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

@eugenepaniot I haven't been able to finish the review. Sharing my comments so far. I currently can't run the tests because:

# command-line-arguments [command-line-arguments.test]
pkg/replay.go:136:16: undefined: unsafe.String
pkg/replay.go:136:30: undefined: unsafe.SliceData


return exitCode
}

func b2s(b []byte) string {
return unsafe.String(unsafe.SliceData(b), len(b))
Copy link
Contributor

Choose a reason for hiding this comment

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

Why not just return string(b)? And we don't need a function for it, we can just inline to string(b) in all the places we make this conversion.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Directly converting string(bytes) or []byte(str) will bring data duplication and poor performance, so in the pursuit of the ultimate performance scenario, need to use the “hack” way to achieve these two types of conversion. Like k8s does

Copy link
Contributor

Choose a reason for hiding this comment

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

Directly converting string(bytes) or []byte(str) will bring data duplication and poor performance, so in the pursuit of the ultimate performance scenario, need to use the “hack” way to achieve these two types of conversion. Like k8s does

Have we measured with and without the "hack"? What was the difference in performance?
What do you mean "data duplication", are we making more allocations and more GC as a result?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Right, it converts a byte to a string without memory allocation.
To be honest, I didn't verify it myself, but I come across this function's implementation in many Golang repos. My findings of the root cause of the issue led to discussion.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just tested:

go test -bench=.
goos: darwin
goarch: amd64
pkg: github.com/loveholidays/ripley/tmp
cpu: VirtualApple @ 2.50GHz
BenchmarkTestJsonRequestString-10    	20031897	        52.29 ns/op
BenchmarkTestJsonRequestB2S-10       	1000000000	         0.5111 ns/op
PASS
ok  	github.com/loveholidays/ripley/tmp	1.980s

Copy link
Contributor Author

Choose a reason for hiding this comment

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

go test -run=. -bench=. -benchtime=5s -count 5 -benchmem -cpuprofile=cpu.out -memprofile=mem.out -trace=trace.out

goos: darwin
goarch: amd64
pkg: github.com/loveholidays/ripley/tmp
cpu: VirtualApple @ 2.50GHz
BenchmarkTestJsonRequestString-10    	79934452	        63.35 ns/op	     288 B/op	       1 allocs/op
BenchmarkTestJsonRequestString-10    	90362182	        63.70 ns/op	     288 B/op	       1 allocs/op
BenchmarkTestJsonRequestString-10    	95226892	        63.04 ns/op	     288 B/op	       1 allocs/op
BenchmarkTestJsonRequestString-10    	89002558	        64.04 ns/op	     288 B/op	       1 allocs/op
BenchmarkTestJsonRequestString-10    	89922666	        64.37 ns/op	     288 B/op	       1 allocs/op
BenchmarkTestJsonRequestB2S-10       	1000000000	         0.5158 ns/op	       0 B/op	       0 allocs/op
BenchmarkTestJsonRequestB2S-10       	1000000000	         0.5165 ns/op	       0 B/op	       0 allocs/op
BenchmarkTestJsonRequestB2S-10       	1000000000	         0.5161 ns/op	       0 B/op	       0 allocs/op
BenchmarkTestJsonRequestB2S-10       	1000000000	         0.5161 ns/op	       0 B/op	       0 allocs/op
BenchmarkTestJsonRequestB2S-10       	1000000000	         0.5148 ns/op	       0 B/op	       0 allocs/op
PASS
ok  	github.com/loveholidays/ripley/tmp	36.985s

string memory:
Screenshot 2023-03-22 at 21 58 30

b2s memory: no memory usage


b2s cpu:

Screenshot 2023-03-22 at 22 03 25

string cpu:

Screenshot 2023-03-22 at 22 05 07

if pacer.done {
break
}
waitGroupResults.Add(1)
Copy link
Contributor

@georgemalamidis-lh georgemalamidis-lh Mar 20, 2023

Choose a reason for hiding this comment

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

I think this reads cleaner if we put it right before line 100 (results <- res)

Copy link
Contributor Author

@eugenepaniot eugenepaniot Mar 21, 2023

Choose a reason for hiding this comment

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

waitGroupResults.Add is necessary there because the subsequent code sends to the results channel that runs waitGroupResults.Done function. If we move it after, an error may occur.

I also moved to the beginning - the if pacer.done condition to stop read requests as quickly as possible. However, I don't mind to move it back.

pkg/metrics.go Outdated
"github.com/VictoriaMetrics/metrics"
)

var metricsRequestReceived = make(chan bool)
Copy link
Contributor

Choose a reason for hiding this comment

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

No global variables, please. Use the same pattern we use for the channels in https://github.com/loveholidays/ripley/blob/main/pkg/client.go#L34

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The desire to use global was simply too strong to overcome.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

}
}()
select {
case <-metricsRequestReceived:
Copy link
Contributor

Choose a reason for hiding this comment

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

I was confused as to where this came from and had to search for the global var in metrics.go. I've left a comment about it there.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

pkg/result_longest_results.go Outdated Show resolved Hide resolved
pkg/replay.go Outdated
PrintStat bool
MetricsServerEnable bool
MetricsServerAddr string
NlongestPrint bool
Copy link
Contributor

Choose a reason for hiding this comment

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

Can we combine NlongestPrint and NlongestResults into one flag, e.g. printNSlowest?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done

@eugenepaniot
Copy link
Contributor Author

@eugenepaniot I haven't been able to finish the review. Sharing my comments so far. I currently can't run the tests because:

# command-line-arguments [command-line-arguments.test]
pkg/replay.go:136:16: undefined: unsafe.String
pkg/replay.go:136:30: undefined: unsafe.SliceData

The golang 1.20 is required as these function introduced in 1.20 version.


flag.StringVar(&opts.Pace, "pace", "10s@1", `[duration]@[rate], e.g. "1m@1 [email protected] 1h@2"`)
flag.BoolVar(&opts.Silent, "silent", false, "Suppress output")
flag.BoolVar(&opts.SilentHttpError, "silentHttpError", false, "Suppress HTTP errors (http codes 5xx) output")
Copy link
Contributor

Choose a reason for hiding this comment

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

When is this useful?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

silentHttpError could be useful when need to do some bechmark testing exclusively or in situations when it's acceptable to overlook any HTTP errors.

@eugenepaniot eugenepaniot mentioned this pull request Jul 15, 2023
Copy link

Quality Gate Failed Quality Gate failed

Failed conditions

2 Security Hotspots

See analysis details on SonarCloud

This was referenced Jul 25, 2024
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

Successfully merging this pull request may close these issues.

2 participants