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
Improvements #1
Improvements #1
Conversation
- try nonblocking channel send, which is faster. - stop timer, so runtime doesn't need to fire goroutine when it expires.
react on no messages in requestsChan instead of flush timeout. probably it will use a bit more CPU, but decrease request latency for service without big load.
react on no responses in responsesChan instead of flush timeout. probably it will use a bit more CPU, but decrease request latency for service without big load.
Thanks for the participation, but unfortunately I cannot merge these changes due to problems described above. |
It's a pity that you even measured proposed changes and their impact. I share not just my "thoughts" but experience, and you talk about it as if it is "premature optimizations". Yeah, it is "premature optimizations" if you never plan to reach 100000rps and more. But it is profile guided optimization at that rate, cause i did profile at that rate. |
…th. Thanks for this hack to funny-falcon. See #1 for details
I just benchmarked blocking select with non-blocking select and was impressed by the numbers: func makeChans() (bCh chan struct{}, nbCh chan struct{}) {
nbCh = make(chan struct{})
close(nbCh)
bCh = make(chan struct{})
return
}
func BenchmarkBlockingSelect(b *testing.B) {
bCh, nbCh := makeChans()
b.ResetTimer()
for i := 0; i < b.N; i++ {
select {
case <-nbCh: // query queue emulation
case <-bCh: // timer emulation
}
}
}
func BenchmarkNonBlockingSelect(b *testing.B) {
bCh, nbCh := makeChans()
b.ResetTimer()
for i := 0; i < b.N; i++ {
select {
case <-nbCh:
default:
b.Fatalf("unexpected code path")
select {
case <-nbCh:
case <-bCh:
}
}
}
} Results:
This proves that your change with select increases its' performance by more than 6 times! The change has been adopted in the code - see ec89681 . |
FYI, I slightly updated benchmark code to be closer to the reality: func makeChans(n int) (bCh chan struct{}, nbCh chan struct{}) {
nbCh = make(chan struct{}, n)
bCh = make(chan struct{})
return
}
func BenchmarkBlockingSelect(b *testing.B) {
bCh, nbCh := makeChans(b.N)
b.ResetTimer()
for i := 0; i < b.N; i++ {
select {
case nbCh <- struct{}{}: // query queue emulation
case <-bCh: // timer emulation
}
}
}
func BenchmarkNonBlockingSelect(b *testing.B) {
bCh, nbCh := makeChans(b.N)
b.ResetTimer()
for i := 0; i < b.N; i++ {
select {
case nbCh <- struct{}{}:
default:
b.Fatalf("Unexpected code path")
select {
case nbCh <- struct{}{}:
case <-bCh:
}
}
}
} But this didn't change benchmark results. |
As to the change with time.After() -> time.NewTimer(), benchark results prove this was worthless optimization: func BenchmarkTimerStop(b *testing.B) {
for i := 0; i < b.N; i++ {
t := time.NewTimer(time.Millisecond)
select {
case <-t.C:
default:
t.Stop()
}
}
}
func BenchmarkTimerNoStop(b *testing.B) {
for i := 0; i < b.N; i++ {
tc := time.After(time.Millisecond)
select {
case <-tc:
default:
}
}
} Results:
|
Yeah, you are right about timers: |
…s to FlushDelay. Thanks to funny-falcon to the idea - see #1 for details
FYI, I added an ability to disable message buffering on both client and server for those who need minimal rpc latency. But benchark results say that gorpc has better throughput with enabled message buffering: 10K workers, default 5ms delay for message buffering:
10K workers, disabled message buffering:
|
your code is not equivalent to my proposal. I'll try to fix it and post bench soon. |
Made a separate pool request #2 with benching |
…s non-rpc messages in a bounded worker pool
such select doesn't need heap allocation nor complex logic
with default PendingRequests == PendingResponses == 32768 it is good both for throughput and latency
(but i'll recomend to decrease default write buffers a bit).