simgo
simgo copied to clipboard
Simulation speed issue
I created the bank renege example model respectively with simpy and simgo.
What suprised me is that the run time of both models are quite similar, Python even faster for 10%.
I never used Go before, so wondering what the cause may be. Do you have any idea?
simpy code:
import time
import random
import simpy
RANDOM_SEED = 42
N_COUNTERS = 50
N_CUSTOMERS = 1000000 # Total number of customers
MEAN_ARRIVAL_INTERVAL = 10.0 # Generate new customers roughly every x seconds
PATIENCE = 16
MEAN_TIME_IN_BANK = 12.0
def source(env, number, interval, counter):
"""Source generates customers randomly"""
for i in range(number):
c = customer(env, f"Customer{i:02d}", counter, time_in_bank=MEAN_TIME_IN_BANK)
env.process(c)
t = random.expovariate(1.0 / interval)
yield env.timeout(t)
def customer(env, name, counter, time_in_bank):
"""Customer arrives, is served and leaves."""
with counter.request() as req:
# Wait for the counter or abort at the end of our tether
results = yield req | env.timeout(PATIENCE)
if req in results:
# We got to the counter
tib = random.expovariate(1.0 / time_in_bank)
yield env.timeout(tib)
# else:
# We reneged
# Setup and start the simulation
# print("Bank renege")
random.seed(RANDOM_SEED)
env = simpy.Environment()
# Start processes and run
counter = simpy.Resource(env, capacity=N_COUNTERS)
env.process(source(env, N_CUSTOMERS, MEAN_ARRIVAL_INTERVAL, counter))
start_time = time.time()
env.run()
end_time = time.time()
print("Elapsed time:", end_time - start_time)
print(env.now)
Python 3.12.1 output:
Elapsed time: 16.356430292129517
simgo code:
package main
import (
"fmt"
"math/rand"
"time"
"github.com/fschuetz04/simgo"
)
const (
RandomSeed = 42
NCounters = 50
NCustomers = 1000000
MeanArrivalInterval = 10
MaxWaitTime = 16
MeanTimeInBank = 12
)
func customerSource(proc simgo.Process) {
counters := NewResource(proc, NCounters)
for id := 1; id <= NCustomers; id++ {
proc.ProcessReflect(customer, id, counters)
delay := rand.ExpFloat64() * MeanArrivalInterval
proc.Wait(proc.Timeout(delay))
}
}
func customer(proc simgo.Process, id int, counters *Resource) {
request := counters.Request()
timeout := proc.Timeout(MaxWaitTime)
proc.Wait(proc.AnyOf(request, timeout))
if !request.Triggered() {
request.Abort()
return
}
delay := rand.ExpFloat64() * MeanTimeInBank
proc.Wait(proc.Timeout(delay))
counters.Release()
}
func timeCost() func() {
start := time.Now()
return func() {
tc := time.Since(start)
fmt.Printf("time cost = %v\n", tc)
}
}
func runSim(sim *simgo.Simulation) {
defer timeCost()()
sim.Run()
fmt.Printf("%.f\n", sim.Now())
}
func main() {
rand.Seed(RandomSeed)
sim := simgo.Simulation{}
sim.Process(customerSource)
runSim(&sim)
}
Golang go version go1.22.2 linux/amd64 output:
time cost = 18.406199488s
On my machine, both versions take around 6.5 seconds, but the Python version is indeed a bit faster on average.
Unfortunately, Go doesn't offer Coroutines, so instead I use one Goroutine per process. The Goroutines are synchronized via channels, so effectively only one runs at any given time. You might find https://research.swtch.com/coro interesting. In the case of the bank example, one process per customer is created, so the Go version needs 1 million Goroutines. Creating them and constantly switching between them of course impacts performance quite a bit.
Compared to Go, Python offers Generators, which are used to make SimPy work without creating a thread or something similar per process. Even if Python is much slower than Go in principle, this leads to the similar performance for this example.
If you want better performance right now, you might want to try the C++ version of this library: https://github.com/fschuetz04/simcpp20. On my machine, the same bank example with 1 million customers takes about 0.5 seconds :)
Thanks for your reply.
I tried simcpp20 actually. However, not every simpy functionality is implemented in simcpp20 yet (store, container for example), so it seems there are a lot work to do before it can be applied to a project to replace simpy.