github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/imports/wasi_snapshot_preview1/wasi_bench_test.go (about) 1 package wasi_snapshot_preview1_test 2 3 import ( 4 "embed" 5 "io/fs" 6 "os" 7 "testing" 8 9 "github.com/bananabytelabs/wazero" 10 "github.com/bananabytelabs/wazero/api" 11 experimentalsys "github.com/bananabytelabs/wazero/experimental/sys" 12 "github.com/bananabytelabs/wazero/imports/wasi_snapshot_preview1" 13 "github.com/bananabytelabs/wazero/internal/sys" 14 "github.com/bananabytelabs/wazero/internal/testing/proxy" 15 "github.com/bananabytelabs/wazero/internal/wasip1" 16 "github.com/bananabytelabs/wazero/internal/wasm" 17 ) 18 19 // configArgsEnviron ensures the result data are the same between args and ENV. 20 var configArgsEnviron = wazero.NewModuleConfig(). 21 WithArgs("aa=bbbb", "cccccc=dddddddd", "eeeeeeeeee=ffffffffffff"). 22 WithEnv("aa", "bbbb"). 23 WithEnv("cccccc", "dddddddd"). 24 WithEnv("eeeeeeeeee", "ffffffffffff") 25 26 func Benchmark_ArgsEnviron(b *testing.B) { 27 r := wazero.NewRuntime(testCtx) 28 defer r.Close(testCtx) 29 30 mod, err := instantiateProxyModule(r, configArgsEnviron) 31 if err != nil { 32 b.Fatal(err) 33 } 34 35 for _, n := range []string{ 36 wasip1.ArgsGetName, 37 wasip1.ArgsSizesGetName, 38 wasip1.EnvironGetName, 39 wasip1.EnvironSizesGetName, 40 } { 41 n := n 42 fn := mod.ExportedFunction(n) 43 b.Run(n, func(b *testing.B) { 44 b.ReportAllocs() 45 for i := 0; i < b.N; i++ { 46 results, err := fn.Call(testCtx, uint64(0), uint64(4)) 47 if err != nil { 48 b.Fatal(err) 49 } 50 requireESuccess(b, results) 51 } 52 }) 53 } 54 } 55 56 type money struct{} 57 58 // Read implements io.Reader by returning endless '$'. 59 func (money) Read(b []byte) (n int, err error) { 60 for i := range b { 61 b[i] = '$' 62 } 63 64 return len(b), nil 65 } 66 67 func Benchmark_fdRead(b *testing.B) { 68 r := wazero.NewRuntime(testCtx) 69 defer r.Close(testCtx) 70 71 mod, err := instantiateProxyModule(r, wazero.NewModuleConfig().WithStdin(money{})) 72 if err != nil { 73 b.Fatal(err) 74 } 75 fn := mod.ExportedFunction(wasip1.FdReadName) 76 77 mod.Memory().Write(0, []byte{ 78 32, 0, 0, 0, // = iovs[0].offset 79 8, 0, 0, 0, // = iovs[0].length 80 40, 0, 0, 0, // = iovs[1].offset 81 8, 0, 0, 0, // = iovs[1].length 82 48, 0, 0, 0, // = iovs[2].offset 83 16, 0, 0, 0, // = iovs[2].length 84 64, 0, 0, 0, // = iovs[3].offset 85 16, 0, 0, 0, // = iovs[3].length 86 }) 87 88 benches := []struct { 89 name string 90 iovs uint32 91 iovsCount uint32 92 }{ 93 { 94 name: "1x8", 95 iovs: 0, 96 iovsCount: 1, 97 }, 98 { 99 name: "2x16", 100 iovs: 16, 101 iovsCount: 2, 102 }, 103 } 104 105 for _, bb := range benches { 106 bc := bb 107 108 b.ReportAllocs() 109 b.Run(bc.name, func(b *testing.B) { 110 resultNread := uint32(128) // arbitrary offset 111 112 for i := 0; i < b.N; i++ { 113 results, err := fn.Call(testCtx, uint64(0), uint64(bc.iovs), uint64(bc.iovsCount), uint64(resultNread)) 114 if err != nil { 115 b.Fatal(err) 116 } 117 requireESuccess(b, results) 118 } 119 }) 120 } 121 } 122 123 //go:embed testdata 124 var testdata embed.FS 125 126 func Benchmark_fdReaddir(b *testing.B) { 127 embedFS, err := fs.Sub(testdata, "testdata") 128 if err != nil { 129 b.Fatal(err) 130 } 131 132 benches := []struct { 133 name string 134 fs fs.FS 135 // dirMount ensures direct use of sys.FS 136 dirMount string 137 // twoCalls tests performance of reading a directory in two calls. 138 twoCalls bool 139 }{ 140 { 141 name: "embed.FS", 142 fs: embedFS, 143 }, 144 { 145 name: "embed.FS - two calls", 146 fs: embedFS, 147 twoCalls: true, 148 }, 149 { 150 name: "os.DirFS", 151 fs: os.DirFS("testdata"), 152 }, 153 { 154 name: "os.DirFS - two calls", 155 fs: os.DirFS("testdata"), 156 twoCalls: true, 157 }, 158 { 159 name: "sysfs.DirFS", 160 dirMount: "testdata", 161 }, 162 { 163 name: "sysfs.DirFS - two calls", 164 dirMount: "testdata", 165 twoCalls: true, 166 }, 167 } 168 169 for _, bb := range benches { 170 bc := bb 171 172 b.Run(bc.name, func(b *testing.B) { 173 r := wazero.NewRuntime(testCtx) 174 defer r.Close(testCtx) 175 176 fsConfig := wazero.NewFSConfig() 177 if bc.fs != nil { 178 fsConfig = fsConfig.WithFSMount(bc.fs, "") 179 } else { 180 fsConfig = fsConfig.WithDirMount(bc.dirMount, "") 181 } 182 183 mod, err := instantiateProxyModule(r, wazero.NewModuleConfig().WithFSConfig(fsConfig)) 184 if err != nil { 185 b.Fatal(err) 186 } 187 188 fn := mod.ExportedFunction(wasip1.FdReaddirName) 189 190 b.ResetTimer() 191 b.ReportAllocs() 192 for i := 0; i < b.N; i++ { 193 b.StopTimer() 194 195 cookie := 0 // where to begin (last read d_next) 196 resultBufused := 0 // where to write the amount used out of bufLen 197 buf := 8 // where to start the dirents 198 bufLen := 8096 // allow up to 8KB buffer usage 199 200 // Open the root directory as a file-descriptor. 201 fsc := mod.(*wasm.ModuleInstance).Sys.FS() 202 fd, errno := fsc.OpenFile(fsc.RootFS(), ".", experimentalsys.O_RDONLY, 0) 203 if errno != 0 { 204 b.Fatal(errno) 205 } 206 207 // Time the call to write the dirents 208 b.StartTimer() 209 210 if bc.twoCalls { 211 // Read the dot entries 212 bufLen := wasip1.DirentSize + 1 // size of "." 213 bufLen += wasip1.DirentSize + 2 // size of ".." 214 215 results, err := fn.Call(testCtx, uint64(fd), uint64(buf), uint64(bufLen), uint64(cookie), uint64(resultBufused)) 216 if err != nil { 217 b.Fatal(err) 218 } 219 requireESuccess(b, results) 220 cookie = 2 // d_next of "..", the real file we couldn't read. 221 } 222 223 results, err := fn.Call(testCtx, uint64(fd), uint64(buf), uint64(bufLen), uint64(cookie), uint64(resultBufused)) 224 if err != nil { 225 b.Fatal(err) 226 } 227 b.StopTimer() 228 229 requireESuccess(b, results) 230 if errno = fsc.CloseFile(fd); errno != 0 { 231 b.Fatal(errno) 232 } 233 } 234 }) 235 } 236 } 237 238 func Benchmark_pathFilestat(b *testing.B) { 239 embedFS, err := fs.Sub(testdata, "testdata") 240 if err != nil { 241 b.Fatal(err) 242 } 243 244 benches := []struct { 245 name string 246 fs fs.FS 247 // dirMount ensures direct use of sys.FS 248 dirMount string 249 path string 250 fd int32 251 }{ 252 { 253 name: "embed.FS fd=root", 254 fs: embedFS, 255 path: "zig", 256 fd: sys.FdPreopen, 257 }, 258 { 259 name: "embed.FS fd=directory", 260 fs: embedFS, 261 path:/* zig */ "wasi.zig", 262 }, 263 { 264 name: "os.DirFS fd=root", 265 fs: os.DirFS("testdata"), 266 path: "zig", 267 fd: sys.FdPreopen, 268 }, 269 { 270 name: "os.DirFS fd=directory", 271 fs: os.DirFS("testdata"), 272 path:/* zig */ "wasi.zig", 273 }, 274 { 275 name: "sysfs.DirFS fd=root", 276 dirMount: "testdata", 277 path: "zig", 278 fd: sys.FdPreopen, 279 }, 280 { 281 name: "sysfs.DirFS fd=directory", 282 dirMount: "testdata", 283 path:/* zig */ "wasi.zig", 284 }, 285 } 286 287 for _, bb := range benches { 288 bc := bb 289 290 b.Run(bc.name, func(b *testing.B) { 291 r := wazero.NewRuntime(testCtx) 292 defer r.Close(testCtx) 293 294 fsConfig := wazero.NewFSConfig() 295 if bc.fs != nil { 296 fsConfig = fsConfig.WithFSMount(bc.fs, "") 297 } else { 298 fsConfig = fsConfig.WithDirMount(bc.dirMount, "") 299 } 300 301 mod, err := instantiateProxyModule(r, wazero.NewModuleConfig().WithFSConfig(fsConfig)) 302 if err != nil { 303 b.Fatal(err) 304 } 305 306 // If the benchmark's file descriptor isn't root, open the file 307 // under a pre-determined directory: zig 308 fd := sys.FdPreopen 309 if bc.fd != sys.FdPreopen { 310 fsc := mod.(*wasm.ModuleInstance).Sys.FS() 311 fd, errno := fsc.OpenFile(fsc.RootFS(), "zig", experimentalsys.O_RDONLY, 0) 312 if errno != 0 { 313 b.Fatal(errno) 314 } 315 defer fsc.CloseFile(fd) //nolint 316 } 317 318 fn := mod.ExportedFunction(wasip1.PathFilestatGetName) 319 320 b.ResetTimer() 321 b.ReportAllocs() 322 for i := 0; i < b.N; i++ { 323 b.StopTimer() 324 325 flags := uint32(0) 326 path := uint32(0) 327 pathLen := len(bc.path) 328 resultFilestat := 1024 // where to write the stat 329 330 if !mod.Memory().WriteString(path, bc.path) { 331 b.Fatal("could not write path") 332 } 333 334 // Time the call to write the stat 335 b.StartTimer() 336 results, err := fn.Call(testCtx, uint64(fd), uint64(flags), uint64(path), uint64(pathLen), uint64(resultFilestat)) 337 if err != nil { 338 b.Fatal(err) 339 } 340 b.StopTimer() 341 342 requireESuccess(b, results) 343 } 344 }) 345 } 346 } 347 348 func requireESuccess(b *testing.B, results []uint64) { 349 b.Helper() 350 if errno := wasip1.Errno(results[0]); errno != 0 { 351 b.Fatal(wasip1.ErrnoName(errno)) 352 } 353 } 354 355 type writerFunc func(buf []byte) (n int, err error) 356 357 // Write implements io.Writer by calling writerFunc. 358 func (f writerFunc) Write(buf []byte) (n int, err error) { 359 return f(buf) 360 } 361 362 func Benchmark_fdWrite(b *testing.B) { 363 r := wazero.NewRuntime(testCtx) 364 defer r.Close(testCtx) 365 366 mod, err := instantiateProxyModule(r, wazero.NewModuleConfig(). 367 WithStdout(writerFunc(func(buf []byte) (n int, err error) { return len(buf), nil })), 368 ) 369 if err != nil { 370 b.Fatal(err) 371 } 372 fn := mod.ExportedFunction(wasip1.FdWriteName) 373 374 iovs := uint32(1) // arbitrary offset 375 mod.Memory().Write(0, []byte{ 376 '?', // `iovs` is after this 377 18, 0, 0, 0, // = iovs[0].offset 378 4, 0, 0, 0, // = iovs[0].length 379 23, 0, 0, 0, // = iovs[1].offset 380 2, 0, 0, 0, // = iovs[1].length 381 '?', // iovs[0].offset is after this 382 'w', 'a', 'z', 'e', // iovs[0].length bytes 383 '?', // iovs[1].offset is after this 384 'r', 'o', // iovs[1].length bytes 385 '?', 386 }) 387 388 iovsCount := uint32(2) // The count of iovs 389 resultNwritten := uint32(26) // arbitrary offset 390 391 benches := []struct { 392 name string 393 fd int32 394 }{ 395 { 396 name: "io.Writer", 397 fd: sys.FdStdout, 398 }, 399 { 400 name: "io.Discard", 401 fd: sys.FdStderr, 402 }, 403 } 404 405 for _, bb := range benches { 406 bc := bb 407 408 b.ReportAllocs() 409 b.Run(bc.name, func(b *testing.B) { 410 for i := 0; i < b.N; i++ { 411 results, err := fn.Call(testCtx, uint64(bc.fd), uint64(iovs), uint64(iovsCount), uint64(resultNwritten)) 412 if err != nil { 413 b.Fatal(err) 414 } 415 requireESuccess(b, results) 416 } 417 }) 418 } 419 } 420 421 // instantiateProxyModule instantiates a guest that re-exports WASI functions. 422 func instantiateProxyModule(r wazero.Runtime, config wazero.ModuleConfig) (api.Module, error) { 423 wasiModuleCompiled, err := wasi_snapshot_preview1.NewBuilder(r).Compile(testCtx) 424 if err != nil { 425 return nil, err 426 } 427 428 if _, err = r.InstantiateModule(testCtx, wasiModuleCompiled, wazero.NewModuleConfig()); err != nil { 429 return nil, err 430 } 431 432 proxyBin := proxy.NewModuleBinary(wasi_snapshot_preview1.ModuleName, wasiModuleCompiled) 433 434 proxyCompiled, err := r.CompileModule(testCtx, proxyBin) 435 if err != nil { 436 return nil, err 437 } 438 439 return r.InstantiateModule(testCtx, proxyCompiled, config) 440 }