Skip to content

Curious performance difference between callbacks and promises #579

Closed
@flimzy

Description

@flimzy

All of the benchmarks mentioned below, and a full description can be found here.

I'm experiencing some performance issues with my GopherJS PouchDB bindings, so this morning I wrote some benchmarks, and found that Promises seem to have 100~200x the overhead of callbacks:

BenchmarkDoPromise1         1000           1318000 ns/op
BenchmarkDoPromise2         2000           1253000 ns/op
BenchmarkDoPromise3         1000           2358000 ns/op
BenchmarkDoCallback1      200000              9715 ns/op
BenchmarkDoCallback2      200000              6280 ns/op

In the spirit of @r-l-x's benchmarks on PR #558, I added two more tests, to benchmark only the channel operations:

func BenchmarkRawPromise(b *testing.B) {
	ch := make(chan promiseResult)
	defer close(ch)
	b.ResetTimer()
	b.StopTimer()
	for i := 0; i < b.N; i++ {
		js.Global.Call("EmptyPromise").Call("then", func(r *js.Object) {
			b.StartTimer()
			ch <- promiseResult{result: r.String()}
		})
		<-ch
		b.StopTimer()
	}
}

func BenchmarkRawCalback(b *testing.B) {
	ch := make(chan promiseResult, 1)
	defer close(ch)
	b.ResetTimer()
	b.StopTimer()
	for i := 0; i < b.N; i++ {
		js.Global.Call("EmptyCallback", func(r *js.Object) {
			b.StartTimer()
			ch <- promiseResult{result: r.String()}
		})
		<-ch
		b.StopTimer()
	}
}

Of particular note, the code between b.StartTimer() and b.StopTimer() is identical in both tests:

			b.StartTimer()
			ch <- promiseResult{result: r.String()}
		})
		<-ch
		b.StopTimer()

Yet the results are drastically different (by a factor of ~200x):

BenchmarkRawPromise         2000           1156000 ns/op
BenchmarkRawCalback       200000              6070 ns/op

So I then added BenchmarkChannel, and BenchmarkChannelInGoroutine:

func BenchmarkChannel(b *testing.B) {
	ch := make(chan int, 1)
	defer close(ch)
	b.ResetTimer()
	b.StopTimer()
	for i := 0; i < b.N; i++ {
		b.StartTimer()
		ch <- 0
		<-ch
		b.StopTimer()
	}
}

func BenchmarkChannelInGoroutine(b *testing.B) {
	ch := make(chan int, 1)
	defer close(ch)
	b.ResetTimer()
	b.StopTimer()
	for i := 0; i < b.N; i++ {
		go func() {
			b.StartTimer()
			ch <- 0
		}()
		<-ch
		b.StopTimer()
	}
}

With the following results:

BenchmarkChannel                  500000              2968 ns/op
BenchmarkChannelInGoroutine       300000              5056 ns/op

This seems intuitive to me, and aligns with the result of BenchmarkRawCallback. So why the drastic difference with Promises?

What am I overlooking? Is there a reason to expect this disparity?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions