Skip to content

Commit

Permalink
add benchmark and feature flags
Browse files Browse the repository at this point in the history
  • Loading branch information
duongphuhiep committed Nov 7, 2024
1 parent fa6cb01 commit fe0444e
Show file tree
Hide file tree
Showing 14 changed files with 546 additions and 17 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -334,7 +334,7 @@ option 1 (run `ore.Validate` on test) is often a better choice.
`ore.Validate()` invokes ALL your registered resolvers, it panics when something gone wrong. The purpose of this function is to panic early when the Container is bad configured:

- Missing depedency: you forgot to register certain resolvers.
- Circular dependency: A depends on B whic depends on A.
- Circular dependency: A depends on B which depends on A.
- Lifetime misalignment: a longer lifetime service (eg. Singleton) depends on a shorter one (eg Transient).

### Graceful application termination
Expand Down
74 changes: 74 additions & 0 deletions examples/benchperf/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Benchmark comparison

This sample will compare Ore (current commit of Nov 2024) to [samber/do/v2 v2.0.0-beta.7](https://github.com/samber/do).
We registered the below dependency graphs to both Ore and SamberDo, then ask them to create the concrete A.

We will only benchmark the creation, not the registration. Because registration usually happens only once on application startup =>
not very interesting to benchmark.

## Data Model

- This data model has only 2 singletons `F` and `Gb` => they will be created only once
- Every other concrete are `Transient` => they will be created each time the container create a new `A`
- We don't test the "Scoped" lifetime in this excercise because SamberDo doesn't has equivalent support for it. The "Scoped" functionality of SamberDo means "Sub Module" rather than a lifetime.

```mermaid
flowchart TD
A["A<br><sup></sup>"]
B["B<br><sup></sup>"]
C["C<br><sup></sup>"]
D["D<br><sup><br></sup>"]
E["E<br><sup><br></sup>"]
F["F<br><sup>Singleton</sup>"]
G(["G<br><sup>(interface)</sup>"])
Gb("Gb<br><sup>Singleton</sup>")
Ga("Ga<br><sup></sup>")
DGa("DGa<br><sup>(decorator)</sup>")
H(["H<br><sup>(interface)<br></sup>"])
Hr["Hr<br><sup>(real)</sup>"]
Hm["Hm<br><sup>(mock)</sup>"]
A-->B
A-->C
B-->D
B-->E
D-->H
D-->F
Hr -.implement..-> H
Hm -.implement..-> H
E-->DGa
E-->Gb
E-->Gc
DGa-->|decorate| Ga
Ga -.implement..-> G
Gb -.implement..-> G
Gc -.implement..-> G
DGa -.implement..-> G
```

## Run the benchmark by yourself

```sh
go test -benchmem -bench .
```

## Sample results

On my machine, Ore always perform faster and use less memory than Samber/Do:

```text
Benchmark_Ore-12 415822 2565 ns/op 2089 B/op 57 allocs/op
Benchmark_SamberDo-12 221941 4954 ns/op 2184 B/op 70 allocs/op
```

And with `ore.DisableValidation = true`

```text
Benchmark_Ore-12 785088 1668 ns/op 1080 B/op 30 allocs/op
Benchmark_SamberDo-12 227851 4940 ns/op 2184 B/op 70 allocs/op
```

As any benchmarks, please take these number "relatively" as a general idea:

- These numbers are probably outdated at the moment you are reading them
- You might got a very different numbers when running them on your machine or on production machine.
36 changes: 36 additions & 0 deletions examples/benchperf/bench_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package main

import (
"context"
i "examples/benchperf/internal"
"testing"

"github.com/firasdarwish/ore"
"github.com/samber/do/v2"
)

// func Benchmark_Ore_NoValidation(b *testing.B) {
// i.BuildContainerOre()
// ore.DisableValidation = true
// ctx := context.Background()
// b.ResetTimer()
// for n := 0; n < b.N; n++ {
// _, ctx = ore.Get[*i.A](ctx)
// }
// }

var _ = i.BuildContainerOre()
var injector = i.BuildContainerDo()
var ctx = context.Background()

func Benchmark_Ore(b *testing.B) {
for n := 0; n < b.N; n++ {
_, ctx = ore.Get[*i.A](ctx)
}
}

func Benchmark_SamberDo(b *testing.B) {
for n := 0; n < b.N; n++ {
_ = do.MustInvoke[*i.A](injector)
}
}
52 changes: 52 additions & 0 deletions examples/benchperf/internal/DiOre.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package internal

import (
"context"

"github.com/firasdarwish/ore"
)

func BuildContainerOre() bool {
ore.RegisterLazyFunc(ore.Transient, func(ctx context.Context) (*A, context.Context) {
b, ctx := ore.Get[*B](ctx)
c, ctx := ore.Get[*C](ctx)
return NewA(b, c), ctx
})
ore.RegisterLazyFunc(ore.Transient, func(ctx context.Context) (*B, context.Context) {
d, ctx := ore.Get[*D](ctx)
e, ctx := ore.Get[*E](ctx)
return NewB(d, e), ctx
})
ore.RegisterLazyFunc(ore.Transient, func(ctx context.Context) (*C, context.Context) {
return NewC(), ctx
})
ore.RegisterLazyFunc(ore.Transient, func(ctx context.Context) (*D, context.Context) {
f, ctx := ore.Get[*F](ctx)
h, ctx := ore.Get[H](ctx)
return NewD(f, h), ctx
})
ore.RegisterLazyFunc(ore.Transient, func(ctx context.Context) (*E, context.Context) {
gs, ctx := ore.GetList[G](ctx)
return NewE(gs), ctx
})
ore.RegisterLazyFunc(ore.Singleton, func(ctx context.Context) (*F, context.Context) {
return NewF(), ctx
})
ore.RegisterLazyFunc(ore.Transient, func(ctx context.Context) (*Ga, context.Context) {
return NewGa(), ctx
})
ore.RegisterLazyFunc(ore.Singleton, func(ctx context.Context) (G, context.Context) {
return NewGb(), ctx
})
ore.RegisterLazyFunc(ore.Transient, func(ctx context.Context) (G, context.Context) {
return NewGc(), ctx
})
ore.RegisterLazyFunc(ore.Transient, func(ctx context.Context) (G, context.Context) {
ga, ctx := ore.Get[*Ga](ctx)
return NewDGa(ga), ctx
})
ore.RegisterLazyFunc(ore.Transient, func(ctx context.Context) (H, context.Context) {
return NewHr(), ctx
})
return true
}
48 changes: 48 additions & 0 deletions examples/benchperf/internal/DiSamber.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package internal

import (
"github.com/samber/do/v2"
)

func BuildContainerDo() do.Injector {
injector := do.New()
do.ProvideTransient(injector, func(inj do.Injector) (*A, error) {
return NewA(do.MustInvoke[*B](inj), do.MustInvoke[*C](inj)), nil
})
do.ProvideTransient(injector, func(inj do.Injector) (*B, error) {
return NewB(do.MustInvoke[*D](inj), do.MustInvoke[*E](inj)), nil
})
do.ProvideTransient(injector, func(inj do.Injector) (*C, error) {
return NewC(), nil
})
do.ProvideTransient(injector, func(inj do.Injector) (*D, error) {
return NewD(do.MustInvoke[*F](inj), do.MustInvoke[H](inj)), nil
})
do.ProvideTransient(injector, func(inj do.Injector) (*E, error) {
gs := []G{
do.MustInvoke[*DGa](inj),
do.MustInvoke[*Gb](inj),
do.MustInvoke[*Gc](inj),
}
return NewE(gs), nil
})
do.Provide(injector, func(inj do.Injector) (*F, error) {
return NewF(), nil
})
do.ProvideTransient(injector, func(inj do.Injector) (*Ga, error) {
return NewGa(), nil
})
do.Provide(injector, func(inj do.Injector) (*Gb, error) {
return NewGb(), nil
})
do.ProvideTransient(injector, func(inj do.Injector) (*Gc, error) {
return NewGc(), nil
})
do.ProvideTransient(injector, func(inj do.Injector) (*DGa, error) {
return NewDGa(do.MustInvoke[*Ga](inj)), nil
})
do.ProvideTransient(injector, func(inj do.Injector) (H, error) {
return NewHr(), nil
})
return injector
}
Loading

0 comments on commit fe0444e

Please sign in to comment.