Ok, so let's say I have a ref object define like that:
MyObject* {.acyclic.} = ref object
a: string
b: int
case kind*: MyObjectKind:
of SomeKind:
c*: string
of AnotherKind:
d*: int
...
From what I've seen (correct me if I'm wrong), the idea is to reserve the maximum memory needed for the biggest type of MyObject. The question is: let's say some MyObject`s need an `.a field (regardless of their kind) - but most don't.
Would adding this a field impact the performance (memory- and cpu-wise) significantly, if we assume that these MyObject's are going to be heavily used throughout the program?
And if yes, what would suggest to tackle any relevant performance issue? (Would adding another case... kind... of switch help?)
P.S. I haven't thoroughly seen myself the differences in the generation code yet, but any input would be more than welcome! :)
Honestly, the best (IMO only) way to measure impact performance is benchmark.
Performance can depends on so many things, that any generic answer is probably going to be meaningless.
My gut feeling says it will not matter. Ref/non ref, variant/non variant it will be all washed out by other operations.
But you should always profile such things, this is what I have done:
name ............................... min time avg time std dv times
case ref object string ............. 0.080 ms 0.082 ms ±0.002 x1000
case object int .................... 0.054 ms 0.055 ms ±0.002 x1000
non-case ref object string ......... 0.077 ms 0.080 ms ±0.005 x1000
non-case object int ................ 0.051 ms 0.053 ms ±0.003 x1000
case object string ................. 0.052 ms 0.053 ms ±0.002 x1000
case object int .................... 0.027 ms 0.028 ms ±0.001 x1000
non-case object string ............. 0.052 ms 0.054 ms ±0.002 x1000
non-case object int ................ 0.027 ms 0.027 ms ±0.001 x1000
All object creation is very fast. But non-ref objects are faster to create because they don't have heap allocation overhead. There seem to be no difference between variant and non-variant objects. Any difference is lost in the std dv noise. The benchmark basically counts heap allocations. Do you allocation the object? Do you allocate a string? That is what appears to matter here.
This makes sense... ref objects take more to create but are easy to pass via pointer while objects are not and might require copies. The variant field does not matter - its just a field. Allocations are the real killer of performance.
nim c -r -d:release /p/try/uuu2.nim
import benchy
type
MyObjectKind = enum
SomeKind
AnotherKind
type
MyObject = ref object
a: string
b: int
case kind: MyObjectKind:
of SomeKind:
c: string
of AnotherKind:
d: int
timeIt "case ref object string", 1000:
for i in 0..1000:
var obj = MyObject(kind:SomeKind)
obj.a = "hi"
obj.b = 123
obj.c = "some string"
keep obj
timeIt "case object int", 1000:
for i in 0..1000:
var obj = MyObject(kind:AnotherKind)
obj.a = "hi"
obj.b = 123
obj.d = 1234
keep obj
type
MyObject2 = ref object
a: string
b: int
kind: MyObjectKind
c: string
d: int
timeIt "non-case ref object string", 1000:
for i in 0..1000:
var obj = MyObject2(kind:SomeKind)
obj.a = "hi"
obj.b = 123
obj.c = "some string"
keep obj
timeIt "non-case object int", 1000:
for i in 0..1000:
var obj = MyObject2(kind:AnotherKind)
obj.a = "hi"
obj.b = 123
obj.d = 1234
keep obj
type
MyObject3 = object
a: string
b: int
case kind: MyObjectKind:
of SomeKind:
c: string
of AnotherKind:
d: int
timeIt "case object string", 1000:
for i in 0..1000:
var obj = MyObject3(kind:SomeKind)
obj.a = "hi"
obj.b = 123
obj.c = "some string"
keep obj
timeIt "case object int", 1000:
for i in 0..1000:
var obj = MyObject3(kind:AnotherKind)
obj.a = "hi"
obj.b = 123
obj.d = 1234
keep obj
type
MyObject4 = object
a: string
b: int
kind: MyObjectKind
c: string
d: int
timeIt "non-case object string", 1000:
for i in 0..1000:
var obj = MyObject4(kind:SomeKind)
obj.a = "hi"
obj.b = 123
obj.c = "some string"
keep obj
timeIt "non-case object int", 1000:
for i in 0..1000:
var obj = MyObject4(kind:AnotherKind)
obj.a = "hi"
obj.b = 123
obj.d = 1234
keep obj