Skip to content

Instantly share code, notes, and snippets.

@rprtr258
Last active June 30, 2024 15:29
Show Gist options
  • Save rprtr258/f7af255831dd0b6db3b969c9c1ea7319 to your computer and use it in GitHub Desktop.
Save rprtr258/f7af255831dd0b6db3b969c9c1ea7319 to your computer and use it in GitHub Desktop.
golang coroutines benchmark
$ go test -ldflags '-checklinkname=0' -bench=. -benchmem . ./...
goos: linux
goarch: amd64
cpu: 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz
BenchmarkChan-8     	   61932	     19671 ns/op	     536 B/op	      10 allocs/op
BenchmarkIter-8     	 3740444	       323.4 ns/op	     120 B/op	       4 allocs/op
BenchmarkJust-8     	 5439428	       200.8 ns/op	     120 B/op	       4 allocs/op
BenchmarkDirect-8   	 7154991	       177.1 ns/op	     120 B/op	       4 allocs/op
BenchmarkCoro2-8    	  544339	      2007 ns/op	     360 B/op	      13 allocs/op
PASS
package main
import "strings"
func chan_fromstring(input string) <-chan byte {
ch := make(chan byte)
go func() {
for _, c := range input {
ch <- byte(c)
}
close(ch)
}()
return ch
}
func chan_decode(input <-chan byte) <-chan byte {
ch := make(chan byte)
go func() {
for cc := range input {
switch cc {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
d := cc - '0'
c := <-input
for j := 0; j <= int(d); j++ {
ch <- c
}
case '.':
ch <- '.'
default:
ch <- cc
}
}
close(ch)
}()
return ch
}
func chan_chunked(input <-chan byte) <-chan byte {
ch := make(chan byte)
go func() {
n := 0
for cc := range input {
ch <- cc
n++
if n == 3 {
ch <- ' '
n = 0
}
}
close(ch)
}()
return ch
}
func chan_f(input string) string {
var sb strings.Builder
for c := range chan_chunked(chan_decode(chan_fromstring(input))) {
sb.WriteByte(c)
}
return sb.String()
}
package main
import (
"strings"
exp_coro "github.com/rprtr258/fun/exp/coro"
)
func coro_fromstring(input string) exp_coro.Coro[struct{}, byte] {
return exp_coro.New(func(yield func(byte) struct{}) {
for _, c := range input {
yield(byte(c))
}
})
}
func coro_decode(getchar func(struct{}) (byte, bool)) exp_coro.Coro[struct{}, byte] {
return exp_coro.New(func(yield func(byte) struct{}) {
for {
cc, ok := getchar(struct{}{})
if !ok {
return
}
switch cc {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
d := cc - '0'
cc, ok := getchar(struct{}{})
if !ok {
return
}
for j := 0; j <= int(d); j++ {
yield(cc)
}
case '.':
yield('.')
return
default:
yield(cc)
}
}
})
}
func coro_chunked(getchar func(struct{}) (byte, bool)) exp_coro.Coro[struct{}, byte] {
n := 0
return exp_coro.New(func(yield func(byte) struct{}) {
for {
cc, ok := getchar(struct{}{})
if !ok {
return
}
yield(cc)
n++
if n == 3 {
yield(' ')
n = 0
}
}
})
}
func coro_f(input string) string {
var sb strings.Builder
c1 := coro_fromstring(input)
defer c1.Cancel()
c2 := coro_decode(c1.Resume)
defer c2.Cancel()
c3 := coro_chunked(c2.Resume)
defer c3.Cancel()
for {
c, ok := c3.Resume(struct{}{})
if !ok {
break
}
sb.WriteByte(c)
}
return sb.String()
}
package main
import (
"iter"
"strings"
_ "unsafe"
)
func coro2_fromstring(input string) iter.Seq[byte] {
return func(yield func(byte) bool) {
for _, c := range []byte(input) {
if !yield(c) {
return
}
}
}
}
type coro struct{}
//go:linkname newcoro runtime.newcoro
func newcoro(func(*coro)) *coro
//go:linkname coroswitch runtime.coroswitch
func coroswitch(*coro)
// iter.Pull w/ no panicking
func Pull[V any](seq iter.Seq[V]) (next func() (V, bool), stop func()) {
var (
v V
ok bool
done bool
)
c := newcoro(func(c *coro) {
if done {
return
}
defer func() { done = true }() // Invalidate iterator
seq(func(v1 V) bool {
if done {
return false
}
v, ok = v1, true
coroswitch(c)
return !done
})
v, ok = *new(V), false
})
return func() (v1 V, ok1 bool) {
if done {
return
}
coroswitch(c)
return v, ok
}, func() {
if !done {
done = true
coroswitch(c)
}
}
}
func coro2_decode(seq iter.Seq[byte]) iter.Seq[byte] {
return func(yield func(byte) bool) {
getchar, stop := Pull(seq)
defer stop()
for {
cc, ok := getchar()
if !ok {
return
}
switch cc {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
d := cc - '0'
cc, ok := getchar()
if !ok {
return
}
for j := 0; j <= int(d); j++ {
if !yield(cc) {
return
}
}
case '.':
yield('.')
return
default:
if !yield(cc) {
return
}
}
}
}
}
func coro2_chunked(seq iter.Seq[byte]) iter.Seq[byte] {
n := 0
return func(yield func(byte) bool) {
for cc := range seq {
if !yield(cc) {
return
}
n++
if n == 3 {
if !yield(' ') {
return
}
n = 0
}
}
}
}
func coro2_f(input string) string {
var sb strings.Builder
for c := range coro2_chunked(coro2_decode(coro2_fromstring(input))) {
sb.WriteByte(c)
}
return sb.String()
}
package main
import "strings"
func just_f(input string) string {
var sb strings.Builder
state := byte(0)
n := 0
// iterating over, so no lookahead
for _, c := range []byte(input) {
switch state {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
d := state - '0'
for j := 0; j <= int(d); j++ {
sb.WriteByte(c)
n++
if n == 3 {
sb.WriteByte(' ')
n = 0
}
}
state = 0
case '.':
sb.WriteByte('.')
n++
if n == 3 {
sb.WriteByte(' ')
n = 0
}
default:
switch c {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
state = c
default:
sb.WriteByte(c)
n++
if n == 3 {
sb.WriteByte(' ')
n = 0
}
}
}
}
return sb.String()
}
package main
import "strings"
// with lookahead
func direct_f(input string) string {
var sb strings.Builder
chunkSize := 0
i := 0
LOOP:
var c byte
if i == len(input) {
goto LOOP_END
}
c = input[i]
if '0' <= c && c <= '9' {
times := c - '0'
i++
c = input[i] // lookahead
LOOP2:
if times == 0 {
goto LOOP_END2
}
sb.WriteByte(c)
chunkSize++
if chunkSize == 3 {
sb.WriteByte(' ')
chunkSize = 0
}
times--
goto LOOP2
LOOP_END2:
}
sb.WriteByte(c)
chunkSize++
if chunkSize == 3 {
sb.WriteByte(' ')
chunkSize = 0
}
i++
goto LOOP
LOOP_END:
return sb.String()
}
package main
import "testing"
const input = "A2B5E3426FG0ZYW3210PQ89R."
const output = "ABB BEE EEE E44 446 66F GZY W22 220 0PQ 999 999 999 R."
func BenchmarkChan(b *testing.B) {
for i := 0; i < b.N; i++ {
if chan_f(input) != output {
b.FailNow()
}
}
}
func BenchmarkIter(b *testing.B) {
for i := 0; i < b.N; i++ {
if iter_f(input) != output {
b.FailNow()
}
}
}
func BenchmarkJust(b *testing.B) {
for i := 0; i < b.N; i++ {
if actual := just_f(input); actual != output {
b.Logf("expected %q, got %q", output, actual)
b.FailNow()
}
}
}
func BenchmarkDirect(b *testing.B) {
for i := 0; i < b.N; i++ {
if actual := direct_f(input); actual != output {
b.Logf("expected %q, got %q", output, actual)
b.FailNow()
}
}
}
func BenchmarkCoro(b *testing.B) {
b.SkipNow() // panics
for i := 0; i < b.N; i++ {
if actual := coro_f(input); actual != output {
b.Logf("expected %q, got %q", output, actual)
b.FailNow()
}
}
}
func BenchmarkCoro2(b *testing.B) {
for i := 0; i < b.N; i++ {
if actual := coro2_f(input); actual != output {
b.Logf("expected %q, got %q", output, actual)
b.FailNow()
}
}
}
module a
go 1.23rc1
require github.com/rprtr258/fun v0.0.17-0.20240630142417-330a75aa6b36
package main
import "strings"
type iterator = func(func(byte))
func iter_fromstring(input string) iterator {
return func(yield func(byte)) {
for _, c := range input {
yield(byte(c))
}
}
}
func iter_decode(input iterator) iterator {
return func(yield func(byte)) {
state := byte(0)
input(func(cc byte) {
switch state {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
d := state - '0'
for j := 0; j <= int(d); j++ {
yield(cc)
}
state = 0
case '.':
yield('.')
default:
switch cc {
case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
state = cc
default:
yield(cc)
}
}
})
}
}
func iter_chunked(input iterator) iterator {
return func(yield func(byte)) {
n := 0
input(func(cc byte) {
yield(cc)
n++
if n == 3 {
yield(' ')
n = 0
}
})
}
}
func iter_f(input string) string {
var sb strings.Builder
iter_chunked(iter_decode(iter_fromstring(input)))(func(c byte) {
sb.WriteByte(c)
})
return sb.String()
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment