-
Notifications
You must be signed in to change notification settings - Fork 5
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
Conversation
eugenepaniot
commented
Mar 6, 2023
•
edited
Loading
edited
- Transitioned to using https://github.com/valyala/fasthttp/ for the HTTP client;
- Transitioned to using github.com/valyala/fastjson/ for fast json parsing;
- Add custom Connection Diler to count TCP metrics (connections, bytes);
- Add metrics server;
- Add other metrics;
- Add handleResult listener rather create goroutine;
- Buffered channels;
- Rewrite dummyweb server on fasthttp with reuseport;
- Add store and print N results with the longest latency;
- Improve error log reporting (connection errors, http errors 5xx);
- Other UX improvements.
It is impossible to customise Host http header as fasthttp set it automaticaly from URI. To deal with it need to use fasthttp.HostClient with Addr instead. This change adds new extra parameter -target to set connection address.
There was a problem hiding this 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)) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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
b2s memory: no memory usage
b2s cpu:
string cpu:
if pacer.done { | ||
break | ||
} | ||
waitGroupResults.Add(1) |
There was a problem hiding this comment.
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
)
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
} | ||
}() | ||
select { | ||
case <-metricsRequestReceived: |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
pkg/replay.go
Outdated
PrintStat bool | ||
MetricsServerEnable bool | ||
MetricsServerAddr string | ||
NlongestPrint bool |
There was a problem hiding this comment.
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
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done
The golang 1.20 is required as these function introduced in 1.20 version. |
panic: runtime error: index out of range [0] with length 0 in waitDuration
07db321
to
f1e5bd0
Compare
|
||
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") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When is this useful?
There was a problem hiding this comment.
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.