Skip to content
This repository was archived by the owner on Mar 4, 2026. It is now read-only.

Commit b3d336d

Browse files
authored
Run simulations concurrently. (#595)
This PR changes the `Simulator.Run` method to run multiple simulations in parallel. This does speed things up a bit (5x throughput for TestPassingSimulations), but things still aren't as fast as I'd like. I also need a lot of parallelism to speed things up fully which I don't yet fully understand. I'll try to do some profiling and optimizations in future PRs.
1 parent e9b3b83 commit b3d336d

2 files changed

Lines changed: 94 additions & 38 deletions

File tree

godeps.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -293,6 +293,7 @@ github.com/ServiceWeaver/weaver/internal/sim
293293
sort
294294
strings
295295
sync
296+
sync/atomic
296297
testing
297298
time
298299
github.com/ServiceWeaver/weaver/internal/status

internal/sim/api.go

Lines changed: 93 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,13 @@ import (
2727
"fmt"
2828
"math/rand"
2929
"reflect"
30+
"sync"
31+
"sync/atomic"
3032
"testing"
3133
"time"
3234

3335
"github.com/ServiceWeaver/weaver/internal/reflection"
34-
"github.com/ServiceWeaver/weaver/runtime"
36+
swruntime "github.com/ServiceWeaver/weaver/runtime"
3537
"github.com/ServiceWeaver/weaver/runtime/codegen"
3638
"github.com/ServiceWeaver/weaver/runtime/protos"
3739
)
@@ -231,7 +233,7 @@ func New(t *testing.T, x Workload, opts Options) *Simulator {
231233
app := &protos.AppConfig{}
232234
if opts.Config != "" {
233235
var err error
234-
app, err = runtime.ParseConfig("", opts.Config, codegen.ComponentConfigValidator)
236+
app, err = swruntime.ParseConfig("", opts.Config, codegen.ComponentConfigValidator)
235237
if err != nil {
236238
t.Fatalf("sim.New: parse config: %v", err)
237239
}
@@ -313,54 +315,107 @@ func validateWorkload(t reflect.Type) error {
313315

314316
// Run runs simulations for the provided duration.
315317
func (s *Simulator) Run(duration time.Duration) Results {
318+
// TODO(mwhittaker): Use a smarter algorithm to sweep over hyperparameters.
319+
// TODO(mwhittaker): Read and run graveyard entries.
316320
start := time.Now()
317321
deadline := start.Add(duration)
318322
ctx, cancel := context.WithDeadline(context.Background(), deadline)
319323
defer cancel()
320324

321-
// TODO(mwhittaker): Use a smarter algorithm to sweep over hyperparameters.
322-
// TODO(mwhittaker): Run simulations in multiple goroutines.
323-
// TODO(mwhittaker): Read and run graveyard entries.
324-
seed := time.Now().UnixNano()
325-
count := 0
326-
for numOps := 1; ; numOps++ {
327-
for _, failureRate := range []float64{0.0, 0.01, 0.05, 0.1} {
328-
for _, yieldRate := range []float64{0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0} {
329-
for i := 0; i < 10; i++ {
330-
if time.Now().After(deadline) {
331-
return Results{
332-
NumSimulations: count,
333-
Duration: time.Since(start),
334-
}
325+
// Spawn n concurrent simulators. The simulators read options from the opts
326+
// channel and write errors and failed results to the errs and
327+
// failedResults channels. Simulation ends when we encounter an error or
328+
// successfully find an invariant violation.
329+
//
330+
// TODO(mwhittaker): Optimize things and pick a smarter value of n.
331+
const n = 1000
332+
opts := make(chan options, n)
333+
errs := make(chan error, n)
334+
failedResults := make(chan Results, n)
335+
done := sync.WaitGroup{}
336+
numSimulations := int64(0)
337+
338+
// Spawn n simulating goroutines that read from the opts channel.
339+
done.Add(n)
340+
for i := 0; i < n; i++ {
341+
go func() {
342+
defer done.Done()
343+
for {
344+
select {
345+
case <-ctx.Done():
346+
return
347+
case o := <-opts:
348+
atomic.AddInt64(&numSimulations, 1)
349+
switch r, err := s.runOne(ctx, o); {
350+
case err != nil && err == ctx.Err():
351+
// The simulation was cancelled because the deadline
352+
// was met. Stop executing simulations.
353+
return
354+
case err != nil:
355+
// The simulation failed to execute properly. Abort.
356+
errs <- err
357+
return
358+
case r.Err != nil:
359+
// The simulation successfully found an invariant
360+
// violation. Return the result and stop execution.
361+
failedResults <- r
362+
return
363+
default:
364+
// The simulation ran without finding an invariant
365+
// violation. Move on to the next simulation.
335366
}
367+
}
368+
}
369+
}()
370+
}
336371

337-
seed++
338-
count++
339-
opts := options{
340-
Seed: seed,
341-
NumOps: numOps,
342-
NumReplicas: 1,
343-
FailureRate: failureRate,
344-
YieldRate: yieldRate,
345-
}
346-
results, err := s.runOne(ctx, opts)
347-
if err != nil && err == ctx.Err() {
348-
return Results{
349-
NumSimulations: count,
350-
Duration: time.Since(start),
372+
// Spawn a goroutine that writes to the opts channel.
373+
done.Add(1)
374+
go func() {
375+
defer done.Done()
376+
seed := time.Now().UnixNano()
377+
for numOps := 1; ; numOps++ {
378+
for _, failureRate := range []float64{0.0, 0.01, 0.05, 0.1} {
379+
for _, yieldRate := range []float64{0.0, 0.1, 0.2, 0.3, 0.4, 0.5, 0.6, 0.7, 0.8, 0.9, 1.0} {
380+
for i := 0; i < 10; i++ {
381+
seed++
382+
o := options{
383+
Seed: seed,
384+
NumOps: numOps,
385+
NumReplicas: 1,
386+
FailureRate: failureRate,
387+
YieldRate: yieldRate,
388+
}
389+
select {
390+
case <-ctx.Done():
391+
return
392+
case opts <- o:
351393
}
352-
}
353-
if err != nil {
354-
s.t.Fatal(err)
355-
}
356-
if results.Err != nil {
357-
results.NumSimulations = count
358-
results.Duration = time.Since(start)
359-
return results
360394
}
361395
}
362396
}
363397
}
398+
}()
399+
400+
select {
401+
case err := <-errs:
402+
// A simulation failed to execute properly.
403+
s.t.Fatal(err)
404+
return Results{}
405+
case r := <-failedResults:
406+
// A simulation successfully found an invariant violation.
407+
cancel()
408+
done.Wait()
409+
r.NumSimulations = int(numSimulations)
410+
r.Duration = time.Since(start)
411+
return r
412+
case <-ctx.Done():
413+
// We hit our deadline. All simulations passed.
414+
done.Wait()
415+
return Results{
416+
NumSimulations: int(numSimulations),
417+
Duration: time.Since(start),
418+
}
364419
}
365420
}
366421

0 commit comments

Comments
 (0)