Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 0 additions & 4 deletions .github/workflows/benchmark.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,6 @@ jobs:
run: |
# Update actively-maintained direct dependencies individually so a
# failure on any one package is logged but does not block the rest.
# pkujhd/goloader is excluded: it references cmd/objfile/* internal
# packages that cannot be resolved outside the Docker build environment,
# so `go get -u` would fail. The Dockerfile's `cp -r cmd/internal …`
# workaround handles it at build time instead.
PKGS=(
github.com/GoCodeAlone/go-plugin
github.com/GoCodeAlone/yaegi
Expand Down
4 changes: 1 addition & 3 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ WORKDIR /app

ENV GO111MODULE=on
ENV GOARCH=amd64
ENV GOLANG_PROTOBUF_REGISTRATION_CONFLICT=warn

RUN apt-get update
RUN apt-get install -y gcc
Expand All @@ -26,14 +27,11 @@ COPY . ./
COPY --from=tinygo-builder /app/wazero.wasm ./

RUN go get
RUN cp -r /usr/local/go/src/cmd/internal /usr/local/go/src/cmd/objfile
RUN go build -buildmode=plugin -o plugin.so golangplugin/main.go
RUN go build -o ./hashicorpgoplugin ./hashicorp-go-plugin/main.go
RUN go build -o ./pieplugin ./pie/main.go
RUN go build -o ./pingoplugin ./pingo/main.go
RUN go build -o ./plugplugin ./plug/plugin/main.go
RUN go build -o ./gocodalonegoplugin ./gocodalone-go-plugin/main.go
RUN go list -export -f '{{if .Export}}packagefile {{.ImportPath}}={{.Export}}{{end}}' std `go list -f {{.Imports}} ./goloader/main.go | awk '{sub(/^\[/, ""); print }' | awk '{sub(/\]$/, ""); print }'` > importcfg
RUN CGO_ENABLED=0 go tool compile -importcfg importcfg -o ./goloader.o ./goloader/main.go

CMD ["go", "test", "-bench=."]
24 changes: 12 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,17 +6,17 @@ A comparison of the [go plugin package](https://golang.org/pkg/plugin/) and othe

| Name | Operations (higher is better) | ns/op (lower is better) | type |
|-----------------------------------------------------------------------------------|:-----------------------------:|------------------------:|:-----------:|
| [go plugin package](https://golang.org/pkg/plugin/) | 44219324 | 30.35 ns/op | native |
| [hashicorp/go-plugin](https://github.com/hashicorp/go-plugin) | 3682 | 413257 ns/op | rpc |
| [GoCodeAlone/go-plugin](https://github.com/GoCodeAlone/go-plugin) | TBD | TBD | grpc |
| [natefinch/pie](https://github.com/natefinch/pie) | 3933 | 328025 ns/op | rpc |
| [dullgiulio/pingo](https://github.com/dullgiulio/pingo) | 4197 | 329354 ns/op | tcp |
| [dullgiulio/pingo](https://github.com/dullgiulio/pingo) | 3110 | 465628 ns/op | unix |
| [elliotmr/plug](https://github.com/elliotmr/plug) | 7998 | 162677 ns/op | ipc |
| [traefik/yaegi](https://github.com/traefik/yaegi) | 1000000 | 1184 ns/op | interpreter |
| [GoCodeAlone/yaegi](https://github.com/GoCodeAlone/yaegi) | TBD | TBD | interpreter |
| [pkujhd/goloader](https://github.com/pkujhd/goloader) | 68201743 | 19.11 ns/op | native |
| [tetratelabs/wazero](https://github.com/tetratelabs/wazero) | 11401358 | 105.0 ns/op | native |
| [go plugin package](https://golang.org/pkg/plugin/) | 466388373 | 12.90 ns/op | native |
| [hashicorp/go-plugin](https://github.com/hashicorp/go-plugin) | 42607 | 137687 ns/op | rpc |
| [GoCodeAlone/go-plugin](https://github.com/GoCodeAlone/go-plugin) | 36901 | 162668 ns/op | grpc |
| [natefinch/pie](https://github.com/natefinch/pie) | 66715 | 89390 ns/op | rpc |
| [dullgiulio/pingo](https://github.com/dullgiulio/pingo) | 51844 | 114880 ns/op | tcp |
| [dullgiulio/pingo](https://github.com/dullgiulio/pingo) | 55180 | 108367 ns/op | unix |
| [elliotmr/plug](https://github.com/elliotmr/plug) | 180102 | 32750 ns/op | ipc |
| [traefik/yaegi](https://github.com/traefik/yaegi) | 6725710 | 895.6 ns/op | interpreter |
| [GoCodeAlone/yaegi](https://github.com/GoCodeAlone/yaegi) | 6666477 | 901.1 ns/op | interpreter |
| [pkujhd/goloader](https://github.com/pkujhd/goloader) [¹](#goloader-note) | N/A | N/A | native |
| [tetratelabs/wazero](https://github.com/tetratelabs/wazero) | 32659566 | 184.4 ns/op | native |

Several of the other packages use RPC or similar methods instead of the go plugin package which gets around issues such as, but not limited to, [not being compatible with Windows](https://github.com/golang/go/issues/19282) and [package paths and GOPATH needing to be the same between apps and plugins](https://github.com/golang/go/issues/20481).

Expand Down Expand Up @@ -46,7 +46,7 @@ docker run go-plugin-benchmark:local

Most plugins tested are using RPC which adds about 30 - 50 microseconds to plugin calls (or 0.03 - 0.05 milliseconds) over the golang plugin package.

The [goloader](https://github.com/pkujhd/goloader) package is interesting and may provide a good alternative to the go plugin package. One drawback is that it uses internal packages which requires renaming the internal folder locally and I have not tested compatibility to see if it solves the problems with the go plugin package.
<a name="goloader-note"></a>¹ The [goloader](https://github.com/pkujhd/goloader) package is interesting and may provide a good alternative to the go plugin package. One drawback is that it uses internal packages which requires renaming the internal folder locally and I have not tested compatibility to see if it solves the problems with the go plugin package. **Note: goloader is currently excluded from benchmarks because it relies on `//go:linkname` access to unexported `runtime` symbols that are no longer accessible in Go 1.26.1.**

## Contributing

Expand Down
1 change: 0 additions & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ require (
github.com/hashicorp/go-hclog v1.6.3
github.com/hashicorp/go-plugin v1.7.0
github.com/natefinch/pie v0.0.0-20170715172608-9a0d72014007
github.com/pkujhd/goloader v0.0.24-0.20260211091157-860e2b19f73f
github.com/tetratelabs/wazero v1.11.0
github.com/traefik/yaegi v0.16.1
google.golang.org/grpc v1.79.1
Expand Down
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,6 @@ github.com/natefinch/pie v0.0.0-20170715172608-9a0d72014007 h1:Ohgj9L0EYOgXxkDp+
github.com/natefinch/pie v0.0.0-20170715172608-9a0d72014007/go.mod h1:wKCOWMb6iNlvKiOToY2cNuaovSXvIiv1zDi9QDR7aGQ=
github.com/oklog/run v1.2.0 h1:O8x3yXwah4A73hJdlrwo/2X6J62gE5qTMusH0dvz60E=
github.com/oklog/run v1.2.0/go.mod h1:mgDbKRSwPhJfesJ4PntqFUbKQRZ50NgmZTSPlFA0YFk=
github.com/pkujhd/goloader v0.0.24-0.20260211091157-860e2b19f73f h1:sNbr3bp/1tWirr4DBzWhtDsdSqtEpKbeytPrD6aWs7Y=
github.com/pkujhd/goloader v0.0.24-0.20260211091157-860e2b19f73f/go.mod h1:NBZlcY477N1nyopY6p3YcoiL5dtXHzj/F12F8b3ui/o=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA=
Expand Down
54 changes: 13 additions & 41 deletions plugin_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@ import (
"plugin"
"runtime"
"testing"
"unsafe"

gocplugin "github.com/GoCodeAlone/go-plugin"
gcyaegiinterp "github.com/GoCodeAlone/yaegi/interp"
Expand All @@ -18,7 +17,6 @@ import (
"github.com/hashicorp/go-hclog"
hashicorpplugin "github.com/hashicorp/go-plugin"
"github.com/natefinch/pie"
"github.com/pkujhd/goloader"
"github.com/tetratelabs/wazero"
"github.com/tetratelabs/wazero/imports/wasi_snapshot_preview1"
"github.com/traefik/yaegi/interp"
Expand Down Expand Up @@ -182,44 +180,6 @@ func RandInt(i int) int { return rand.Int() }`
})
}

func BenchmarkGoloaderRandInt(b *testing.B) {
linker, err := goloader.ReadObjs([]string{"goloader.o"}, []string{"main"})
if err != nil {
b.Error(err)
return
}

run := "main.RandInt"

symPtr := make(map[string]uintptr)
err = goloader.RegSymbol(symPtr)
if err != nil {
b.Error(err)
return
}

b.Run("goloader", func(b *testing.B) {
codeModule, err := goloader.Load(linker, symPtr)
if err != nil {
fmt.Println("Load error:", err)
return
}
runFuncPtr, ok := codeModule.Syms[run]
if !ok || runFuncPtr == 0 {
fmt.Println("Load error! not find function:", run)
return
}
funcPtrContainer := (uintptr)(unsafe.Pointer(&runFuncPtr))
runFunc := *(*func() int)(unsafe.Pointer(&funcPtrContainer))
for i := 0; i < b.N; i++ {
_ = runFunc()
}

os.Stdout.Sync()
codeModule.Unload()
})
}

func BenchmarkWazeroRandInt(b *testing.B) {
wasmFile, err := os.ReadFile("wazero.wasm")
if err != nil {
Expand All @@ -233,11 +193,23 @@ func BenchmarkWazeroRandInt(b *testing.B) {

wasi_snapshot_preview1.MustInstantiate(ctx, runtime)

module, err := runtime.Instantiate(ctx, wasmFile)
compiledModule, err := runtime.CompileModule(ctx, wasmFile)
if err != nil {
fmt.Println("failed to compile module:", err)
return
}

// WithStartFunctions() with no arguments skips the auto-start (_start)
// entry point so the WASI module stays alive for repeated RandInt calls.
// Without this, TinyGo's _start calls main() and then proc_exit(0),
// closing the module before any benchmark iterations can complete.
cfg := wazero.NewModuleConfig().WithStartFunctions()
module, err := runtime.InstantiateModule(ctx, compiledModule, cfg)
if err != nil {
fmt.Println("failed to instantiate module:", err)
return
}
Comment on lines +196 to 211
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

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

In this benchmark, compile/instantiate failures are handled by printing and returning, which makes go test succeed while silently skipping the wazero sub-benchmark. That can reintroduce the "missing benchmark results" failure mode (or leave README rows stale). Prefer failing the benchmark (e.g., b.Fatalf / b.Fatal) so CI clearly reports the problem.

Copilot uses AI. Check for mistakes.
defer module.Close(ctx)

b.Run("wazero", func(b *testing.B) {
randIntFunction := module.ExportedFunction("RandInt")
Comment on lines 214 to 215
Copy link

Copilot AI Mar 7, 2026

Choose a reason for hiding this comment

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

Within the b.Run("wazero", ...) body, an error from randIntFunction.Call is currently handled by printing and returning, which ends the sub-benchmark early and can yield misleading results. Prefer failing the benchmark via the testing.B API (e.g., b.Fatalf / b.FailNow) so the run is marked unsuccessful instead of reporting partial numbers.

Copilot uses AI. Check for mistakes.
Expand Down
1 change: 0 additions & 1 deletion scripts/update_readme.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@
"plug": r"github\.com/elliotmr/plug",
"yaegi": r"github\.com/traefik/yaegi",
"gocodalone-yaegi": r"github\.com/GoCodeAlone/yaegi",
"goloader": r"github\.com/pkujhd/goloader",
"wazero": r"github\.com/tetratelabs/wazero",
}

Expand Down