Skip to content

Commit d0252cd

Browse files
committed
add benchmark and feature flags
1 parent fa6cb01 commit d0252cd

File tree

14 files changed

+546
-17
lines changed

14 files changed

+546
-17
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -334,7 +334,7 @@ option 1 (run `ore.Validate` on test) is often a better choice.
334334
`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:
335335

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

340340
### Graceful application termination

examples/benchperf/README.md

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
# Benchmark comparison
2+
3+
This sample will compare Ore (current commit of Nov 2024) to [samber/do/v2 v2.0.0-beta.7](https://github.com/samber/do).
4+
We registered the below dependency graphs to both Ore and SamberDo, then ask them to create the concrete A.
5+
6+
We will only benchmark the creation, not the registration. Because registration usually happens only once on application startup =>
7+
not very interesting to benchmark.
8+
9+
## Data Model
10+
11+
- This data model has only 2 singletons `F` and `Gb` => they will be created only once
12+
- Every other concrete are `Transient` => they will be created each time the container create a new `A`
13+
- 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.
14+
15+
```mermaid
16+
flowchart TD
17+
A["A<br><sup></sup>"]
18+
B["B<br><sup></sup>"]
19+
C["C<br><sup></sup>"]
20+
D["D<br><sup><br></sup>"]
21+
E["E<br><sup><br></sup>"]
22+
F["F<br><sup>Singleton</sup>"]
23+
G(["G<br><sup>(interface)</sup>"])
24+
Gb("Gb<br><sup>Singleton</sup>")
25+
Ga("Ga<br><sup></sup>")
26+
DGa("DGa<br><sup>(decorator)</sup>")
27+
H(["H<br><sup>(interface)<br></sup>"])
28+
Hr["Hr<br><sup>(real)</sup>"]
29+
Hm["Hm<br><sup>(mock)</sup>"]
30+
31+
A-->B
32+
A-->C
33+
B-->D
34+
B-->E
35+
D-->H
36+
D-->F
37+
Hr -.implement..-> H
38+
Hm -.implement..-> H
39+
E-->DGa
40+
E-->Gb
41+
E-->Gc
42+
DGa-->|decorate| Ga
43+
Ga -.implement..-> G
44+
Gb -.implement..-> G
45+
Gc -.implement..-> G
46+
DGa -.implement..-> G
47+
```
48+
49+
## Run the benchmark by yourself
50+
51+
```sh
52+
go test -benchmem -bench .
53+
```
54+
55+
## Sample results
56+
57+
On my machine, Ore always perform faster and use less memory than Samber/Do:
58+
59+
```text
60+
Benchmark_Ore-12 415822 2565 ns/op 2089 B/op 57 allocs/op
61+
Benchmark_SamberDo-12 221941 4954 ns/op 2184 B/op 70 allocs/op
62+
```
63+
64+
And with `ore.DisableValidation = true`
65+
66+
```text
67+
Benchmark_Ore-12 785088 1668 ns/op 1080 B/op 30 allocs/op
68+
Benchmark_SamberDo-12 227851 4940 ns/op 2184 B/op 70 allocs/op
69+
```
70+
71+
As any benchmarks, please take these number "relatively" as a general idea:
72+
73+
- These numbers are probably outdated at the moment you are reading them
74+
- You might got a very different numbers when running them on your machine or on production machine.

examples/benchperf/bench_test.go

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package main
2+
3+
import (
4+
"context"
5+
i "examples/benchperf/internal"
6+
"testing"
7+
8+
"github.com/firasdarwish/ore"
9+
"github.com/samber/do/v2"
10+
)
11+
12+
// func Benchmark_Ore_NoValidation(b *testing.B) {
13+
// i.BuildContainerOre()
14+
// ore.DisableValidation = true
15+
// ctx := context.Background()
16+
// b.ResetTimer()
17+
// for n := 0; n < b.N; n++ {
18+
// _, ctx = ore.Get[*i.A](ctx)
19+
// }
20+
// }
21+
22+
var _ = i.BuildContainerOre()
23+
var injector = i.BuildContainerDo()
24+
var ctx = context.Background()
25+
26+
func Benchmark_Ore(b *testing.B) {
27+
for n := 0; n < b.N; n++ {
28+
_, ctx = ore.Get[*i.A](ctx)
29+
}
30+
}
31+
32+
func Benchmark_SamberDo(b *testing.B) {
33+
for n := 0; n < b.N; n++ {
34+
_ = do.MustInvoke[*i.A](injector)
35+
}
36+
}
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
package internal
2+
3+
import (
4+
"context"
5+
6+
"github.com/firasdarwish/ore"
7+
)
8+
9+
func BuildContainerOre() bool {
10+
ore.RegisterLazyFunc(ore.Transient, func(ctx context.Context) (*A, context.Context) {
11+
b, ctx := ore.Get[*B](ctx)
12+
c, ctx := ore.Get[*C](ctx)
13+
return NewA(b, c), ctx
14+
})
15+
ore.RegisterLazyFunc(ore.Transient, func(ctx context.Context) (*B, context.Context) {
16+
d, ctx := ore.Get[*D](ctx)
17+
e, ctx := ore.Get[*E](ctx)
18+
return NewB(d, e), ctx
19+
})
20+
ore.RegisterLazyFunc(ore.Transient, func(ctx context.Context) (*C, context.Context) {
21+
return NewC(), ctx
22+
})
23+
ore.RegisterLazyFunc(ore.Transient, func(ctx context.Context) (*D, context.Context) {
24+
f, ctx := ore.Get[*F](ctx)
25+
h, ctx := ore.Get[H](ctx)
26+
return NewD(f, h), ctx
27+
})
28+
ore.RegisterLazyFunc(ore.Transient, func(ctx context.Context) (*E, context.Context) {
29+
gs, ctx := ore.GetList[G](ctx)
30+
return NewE(gs), ctx
31+
})
32+
ore.RegisterLazyFunc(ore.Singleton, func(ctx context.Context) (*F, context.Context) {
33+
return NewF(), ctx
34+
})
35+
ore.RegisterLazyFunc(ore.Transient, func(ctx context.Context) (*Ga, context.Context) {
36+
return NewGa(), ctx
37+
})
38+
ore.RegisterLazyFunc(ore.Singleton, func(ctx context.Context) (G, context.Context) {
39+
return NewGb(), ctx
40+
})
41+
ore.RegisterLazyFunc(ore.Transient, func(ctx context.Context) (G, context.Context) {
42+
return NewGc(), ctx
43+
})
44+
ore.RegisterLazyFunc(ore.Transient, func(ctx context.Context) (G, context.Context) {
45+
ga, ctx := ore.Get[*Ga](ctx)
46+
return NewDGa(ga), ctx
47+
})
48+
ore.RegisterLazyFunc(ore.Transient, func(ctx context.Context) (H, context.Context) {
49+
return NewHr(), ctx
50+
})
51+
return true
52+
}
Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
package internal
2+
3+
import (
4+
"github.com/samber/do/v2"
5+
)
6+
7+
func BuildContainerDo() do.Injector {
8+
injector := do.New()
9+
do.ProvideTransient(injector, func(inj do.Injector) (*A, error) {
10+
return NewA(do.MustInvoke[*B](inj), do.MustInvoke[*C](inj)), nil
11+
})
12+
do.ProvideTransient(injector, func(inj do.Injector) (*B, error) {
13+
return NewB(do.MustInvoke[*D](inj), do.MustInvoke[*E](inj)), nil
14+
})
15+
do.ProvideTransient(injector, func(inj do.Injector) (*C, error) {
16+
return NewC(), nil
17+
})
18+
do.ProvideTransient(injector, func(inj do.Injector) (*D, error) {
19+
return NewD(do.MustInvoke[*F](inj), do.MustInvoke[H](inj)), nil
20+
})
21+
do.ProvideTransient(injector, func(inj do.Injector) (*E, error) {
22+
gs := []G{
23+
do.MustInvoke[*DGa](inj),
24+
do.MustInvoke[*Gb](inj),
25+
do.MustInvoke[*Gc](inj),
26+
}
27+
return NewE(gs), nil
28+
})
29+
do.Provide(injector, func(inj do.Injector) (*F, error) {
30+
return NewF(), nil
31+
})
32+
do.ProvideTransient(injector, func(inj do.Injector) (*Ga, error) {
33+
return NewGa(), nil
34+
})
35+
do.Provide(injector, func(inj do.Injector) (*Gb, error) {
36+
return NewGb(), nil
37+
})
38+
do.ProvideTransient(injector, func(inj do.Injector) (*Gc, error) {
39+
return NewGc(), nil
40+
})
41+
do.ProvideTransient(injector, func(inj do.Injector) (*DGa, error) {
42+
return NewDGa(do.MustInvoke[*Ga](inj)), nil
43+
})
44+
do.ProvideTransient(injector, func(inj do.Injector) (H, error) {
45+
return NewHr(), nil
46+
})
47+
return injector
48+
}

0 commit comments

Comments
 (0)