Open forchid opened 2 months ago
Run it in a loop and see that it does get collected:
import std/strformat
type
Person = object of RootObj
name: string
age: uint
Student = object of Person
id: uint
self: StudentRef
StudentRef = ref Student
proc say(s: Student) =
echo(fmt"Student {s.name}#{s.id} and {s.age} years old")
proc `=destroy`(s: var Student) = # Here `var` compatible with nim-1.6.20
echo(fmt"Student {s.name}#{s.id} destroyed.")
proc test(useCycle: bool, id: uint) =
echo(fmt"Use cycled ref? {useCycle}")
var t = StudentRef(name: "James", age: 36, id: id)
say(t[])
# `=destroy` not called if cycled ref exists when using orc!
if useCycle:
t.self = t
for _ in 0..1000:
test(true, 2)
And no, you really don't want the cycle collector to run on every scope exit, the performance would be ridiculously bad. (Been there, done that.)
Run it in a loop and see that it does get collected:
import std/strformat type Person = object of RootObj name: string age: uint Student = object of Person id: uint self: StudentRef StudentRef = ref Student proc say(s: Student) = echo(fmt"Student {s.name}#{s.id} and {s.age} years old") proc `=destroy`(s: var Student) = # Here `var` compatible with nim-1.6.20 echo(fmt"Student {s.name}#{s.id} destroyed.") proc test(useCycle: bool, id: uint) = echo(fmt"Use cycled ref? {useCycle}") var t = StudentRef(name: "James", age: 36, id: id) say(t[]) # `=destroy` not called if cycled ref exists when using orc! if useCycle: t.self = t for _ in 0..1000: test(true, 2)
And no, you really don't want the cycle collector to run on every scope exit, the performance would be ridiculously bad. (Been there, done that.)
@Araq But the orc
collector looks not hard realtime. As this issue shown, these collections of recyled ref objects are delayed.
I never claimed it to be hard realtime. Hard realtime requires you to either use arc/atomicArc or to disable the cycle collector and schedule it yourself when appropriate much like the older refc does.
@Araq out of memory
happened if ref the other object in cycled ref relation and return the referenced object in the proc test
.
import std/strutils
type
Person = object of RootObj
name: string
age: uint
data: array[1 shl 24, uint8]
Student = object of Person
id: uint
self: StudentRef
StudentRef = ref Student
proc say(s: Student) =
#echo(fmt"Student {s.name}#{s.id} and {s.age} years old")
discard
proc `=destroy`(s: var Student) = # Here `var` compatible with nim-1.6.20
#echo(fmt"Student {s.name}#{s.id} destroyed.")
discard
proc test(useCycle: bool, id: uint): StudentRef =
#echo(fmt"Use cycled ref? {useCycle}")
var t = StudentRef(name: "James", age: 36, id: id)
say(t[])
# `=destroy` not called if cycled ref exists when using orc!
if useCycle:
var oth = StudentRef(name: "Tome", age: 37, id: id + 1)
oth.self = t
t.self = oth # Ref the other ref
return t
for i in 0..100000:
var t = test(true, 2)
t.self = nil
if (i + 1) mod 1000 == 0: echo "Occupied mem " & $formatSize(getOccupiedMem())
`out of memory` if using `--mm:orc`!
mem>nim c -d:release --mm:orc dnc_araq.nim
Hint: used config file 'nim-1.6.20\config\nim.cfg' [Conf]
Hint: used config file 'nim-1.6.20\config\config.nims' [Conf]
.........................................................................
CC: dnc_araq.nim
Hint: [Link]
Hint: gc: orc; opt: speed; options: -d:release
38201 lines; 0.907s; 48.793MiB peakmem; proj: mem\dnc_araq.nim; out: mem\dnc_araq.exe [SuccessX]
mem>dnc_araq
Occupied mem 16.113GiB
out of memory
But if using the --mm:refc
, it works.
mem>nim c -d:release --mm:refc dnc_araq.nim
Hint: used config file 'nim-1.6.20\config\nim.cfg' [Conf]
Hint: used config file 'nim-1.6.20\config\config.nims' [Conf]
..........................................................................
CC: ../../../../Programs/nim-1.6.20/lib/std/private/digitsutils.nim
CC: ../../../../Programs/nim-1.6.20/lib/system/dollars.nim
CC: ../../../../Programs/nim-1.6.20/lib/system/io.nim
CC: ../../../../Programs/nim-1.6.20/lib/system.nim
CC: ../../../../Programs/nim-1.6.20/lib/pure/strutils.nim
CC: dnc_araq.nim
Hint: [Link]
Hint: gc: refc; opt: speed; options: -d:release
39362 lines; 4.783s; 48.777MiB peakmem; proj: mem\dnc_araq.nim; out: mem\dnc_araq.exe [SuccessX]
mem>dnc_araq
Occupied mem 82.559MiB
Occupied mem 49.559MiB
Occupied mem 49.559MiB
Occupied mem 49.559MiB
Occupied mem 49.559MiB
Occupied mem 49.559MiB
@Araq This case is the size of per object 4k.
import std/strutils
type
Person = object of RootObj
name: string
age: uint
data: array[1 shl 12, uint8]
Student = object of Person
id: uint
self: StudentRef
StudentRef = ref Student
proc say(s: Student) =
#echo(fmt"Student {s.name}#{s.id} and {s.age} years old")
discard
proc `=destroy`(s: var Student) = # Here `var` compatible with nim-1.6.20
#echo(fmt"Student {s.name}#{s.id} destroyed.")
discard
proc test(useCycle: bool, id: uint): StudentRef =
#echo(fmt"Use cycled ref? {useCycle}")
var t = StudentRef(name: "James", age: 36, id: id)
say(t[])
# `=destroy` not called if cycled ref exists when using orc!
if useCycle:
var oth = StudentRef(name: "Tome", age: 37, id: id + 1)
oth.self = t
t.self = oth # Ref the other ref
#t.self = t
return t
var i = 0
while true:
var t = test(true, 2)
t.self = nil
i.inc
if i mod 10_0000 == 0:
echo $i & ": Occupied mem " & $formatSize(getOccupiedMem())
Using orc
mem>dnc_araq
100000: Occupied mem 781.656MiB
200000: Occupied mem 1.526GiB
300000: Occupied mem 2.29GiB
400000: Occupied mem 3.052GiB
500000: Occupied mem 3.817GiB
600000: Occupied mem 4.578GiB
700000: Occupied mem 5.343GiB
800000: Occupied mem 6.108GiB
900000: Occupied mem 6.867GiB
1000000: Occupied mem 7.632GiB
1100000: Occupied mem 8.397GiB
1200000: Occupied mem 9.162GiB
1300000: Occupied mem 9.919GiB
1400000: Occupied mem 10.683GiB
1500000: Occupied mem 11.449GiB
1600000: Occupied mem 12.214GiB
1700000: Occupied mem 12.977GiB
1800000: Occupied mem 13.743GiB
1900000: Occupied mem 14.506GiB
2000000: Occupied mem 15.26GiB
2100000: Occupied mem 16.025GiB
2200000: Occupied mem 16.789GiB
2300000: Occupied mem 17.554GiB
out of memory
Using refc
mem>dnc_araq
100000: Occupied mem 3.988MiB
200000: Occupied mem 3.988MiB
300000: Occupied mem 3.988MiB
400000: Occupied mem 3.988MiB
500000: Occupied mem 3.988MiB
600000: Occupied mem 3.988MiB
700000: Occupied mem 3.988MiB
800000: Occupied mem 3.988MiB
900000: Occupied mem 3.988MiB
1000000: Occupied mem 3.988MiB
1100000: Occupied mem 3.988MiB
1200000: Occupied mem 3.988MiB
1300000: Occupied mem 3.988MiB
1400000: Occupied mem 3.988MiB
1500000: Occupied mem 3.988MiB
1600000: Occupied mem 3.988MiB
1700000: Occupied mem 3.988MiB
1800000: Occupied mem 3.988MiB
1900000: Occupied mem 3.988MiB
2000000: Occupied mem 3.988MiB
2100000: Occupied mem 3.988MiB
2200000: Occupied mem 3.988MiB
2300000: Occupied mem 3.988MiB
...
11000000: Occupied mem 3.988MiB
11100000: Occupied mem 3.988MiB
11200000: Occupied mem 3.988MiB
11300000: Occupied mem 3.988MiB
...
The case of the object size 256 bytes.
mem>dnc_araq
100000: Occupied mem 30.924MiB
200000: Occupied mem 61.645MiB
300000: Occupied mem 92.459MiB
400000: Occupied mem 122.477MiB
500000: Occupied mem 154.65MiB
600000: Occupied mem 183.715MiB
700000: Occupied mem 215.686MiB
800000: Occupied mem 248.766MiB
900000: Occupied mem 275.564MiB
1000000: Occupied mem 308.238MiB
1100000: Occupied mem 340.318MiB
1200000: Occupied mem 373.086MiB
1300000: Occupied mem 397.135MiB
1400000: Occupied mem 429.309MiB
1500000: Occupied mem 462.389MiB
1600000: Occupied mem 495.156MiB
1700000: Occupied mem 525.674MiB
...
6300000: Occupied mem 1.912GiB
6400000: Occupied mem 1.942GiB
6500000: Occupied mem 1.938GiB
6600000: Occupied mem 1.97GiB
6700000: Occupied mem 2.001GiB
...
33100000: Occupied mem 9.871GiB
33200000: Occupied mem 9.905GiB
33300000: Occupied mem 9.934GiB
33400000: Occupied mem 9.969GiB
33500000: Occupied mem 9.999GiB
33600000: Occupied mem 10.029GiB
...
57200000: Occupied mem 17.219GiB
57300000: Occupied mem 17.249GiB
57400000: Occupied mem 17.278GiB
57500000: Occupied mem 17.308GiB
57600000: Occupied mem 17.338GiB
out of memory
Looks like the collection heuristics need some tweaking :P
eh, don't you need to destroy the memory of self: StudentRef
?
proc `=destroy`(s: var Student) = # Here `var` compatible with nim-1.6.20
#echo(fmt"Student {s.name}#{s.id} destroyed.")
discard
Like
proc `=destroy`(s: Student) = # Here `var` compatible with nim-1.6.20
# echo("Student destroyed.")
`=destroy`(s.name)
`=destroy`(s.self)
100000: Occupied mem 28.016KiB
200000: Occupied mem 28.016KiB
300000: Occupied mem 28.016KiB
400000: Occupied mem 28.016KiB
500000: Occupied mem 28.016KiB
600000: Occupied mem 28.016KiB
700000: Occupied mem 28.016KiB
800000: Occupied mem 28.016KiB
900000: Occupied mem 28.016KiB
1000000: Occupied mem 28.016KiB
1100000: Occupied mem 28.016KiB
1200000: Occupied mem 28.016KiB
1300000: Occupied mem 28.016KiB
1400000: Occupied mem 28.016Ki
eh, don't you need to destroy the memory of
self: StudentRef
?
@ringabout No need. The ref object is traced by nim gc collector. If manually destroying the field self
in =destroy
, this breaks the cycled ref relation manually and recyle the self
(acutally this object maybe need to keep alive for other object referencing it! )
You do need to destroy the name
field though it should make no difference for your test program.
@Araq I find that memory leak also exists in the case of no cycled ref and only referenced when using orc
or arc
. This is where the problem.
import std/strutils
type
Person = object of RootObj
name: string
age: uint
data: array[1 shl 8, uint8]
Student = object of Person
id: uint
self: StudentRef
StudentRef = ref Student
proc say(s: Student) =
#echo(fmt"Student {s.name}#{s.id} and {s.age} years old")
discard
proc `=destroy`(s: var Student) = # Here `var` compatible with nim-1.6.20
#echo(fmt"Student {s.name}#{s.id} destroyed.")
`=destroy`(s.name)
discard
proc test(refed: bool, id: uint): StudentRef =
var t = StudentRef(name: "James", age: 36, id: id)
say(t[])
# `=destroy` not called if cycled ref exists when using orc!
if refed:
var oth = StudentRef(name: "Tome", age: 37, id: id + 1)
oth.self = t # No cycled ref relation, only referenced!
return t
var i = 0
while true:
var t = test(true, 2)
i.inc
if i mod 10_0000 == 0:
echo $i & ": Occupied mem " & $formatSize(getOccupiedMem())
mem>dnc_araq_nc
100000: Occupied mem 30.924MiB
200000: Occupied mem 61.645MiB
300000: Occupied mem 92.459MiB
400000: Occupied mem 122.477MiB
500000: Occupied mem 154.65MiB
...
20200000: Occupied mem 6.135GiB
20300000: Occupied mem 6.165GiB
20400000: Occupied mem 6.195GiB
20500000: Occupied mem 6.225GiB
20600000: Occupied mem 6.255GiB
...
@Araq If seting the field self
as nil
(this no problem instead of calling =destroy
) in the hook =destroy
, this test passes. So the nim bug is that the gc collector orc
can't recyle the other object B after recyling A in the cycled ref relation such as A <=> B and B returned from a proc
. And the refc
no this problem.
import std/strutils
type
Person = object of RootObj
name: string
age: uint
data: array[1 shl 8, uint8]
Student = object of Person
id: uint
self: StudentRef
StudentRef = ref Student
proc say(s: Student) =
#echo(fmt"Student {s.name}#{s.id} and {s.age} years old")
discard
proc `=destroy`(s: var Student) = # Here `var` compatible with nim-1.6.20
#echo(fmt"Student {s.name}#{s.id} destroyed.")
s.self = nil # ok
discard
proc test(useCycle: bool, id: uint): StudentRef =
#echo(fmt"Use cycled ref? {useCycle}")
var t = StudentRef(name: "James", age: 36, id: id)
say(t[])
# `=destroy` not called if cycled ref exists when using orc!
if useCycle:
var oth = StudentRef(name: "Tome", age: 37, id: id + 1)
oth.self = t
t.self = oth # Ref the other ref
return t
var i = 0
while true:
var t = test(true, 2)
t.self = nil
i.inc
if i mod 10_0000 == 0:
echo $i & ": Occupied mem " & $formatSize(getOccupiedMem())
mem>dnc_araq
100000: Occupied mem 20.328KiB
200000: Occupied mem 20.328KiB
300000: Occupied mem 20.328KiB
400000: Occupied mem 20.328KiB
500000: Occupied mem 20.328KiB
600000: Occupied mem 20.328KiB
700000: Occupied mem 20.328KiB
800000: Occupied mem 20.328KiB
900000: Occupied mem 20.328KiB
1000000: Occupied mem 20.328KiB
1100000: Occupied mem 20.328KiB
1200000: Occupied mem 20.328KiB
1300000: Occupied mem 20.328KiB
1400000: Occupied mem 20.328KiB
1500000: Occupied mem 20.328KiB
1600000: Occupied mem 20.328KiB
1700000: Occupied mem 20.328KiB
1800000: Occupied mem 20.328KiB
1900000: Occupied mem 20.328KiB
2000000: Occupied mem 20.328KiB
2100000: Occupied mem 20.328KiB
2200000: Occupied mem 20.328KiB
2300000: Occupied mem 20.328KiB
2400000: Occupied mem 20.328KiB
...
@Araq If using the default =destroy
instead of overriding it, no problem. And a programmer must set the ref field as nil manually if overriding the hook destroy. The collector refc
no memory leak for it using default destroy instead of this overrided =destroy
. So I'll close this issue.
import std/strutils
type
Person = object of RootObj
name: string
age: uint
data: array[1 shl 8, uint8]
Student = object of Person
id: uint
self: StudentRef
StudentRef = ref Student
proc say(s: Student) =
discard
# The collector `refc` no memory leak for using default destroy instead of this `=destroy`
#proc `=destroy`(s: var Student) = # Here `var` compatible with nim-1.6.20
#s.self = nil # Must set this field as nil if overriding `=destroy` otherwise memory leak!
#discard
proc test(useCycle: bool, id: uint): StudentRef =
var t = StudentRef(name: "James", age: 36, id: id)
say(t[])
if useCycle:
var oth = StudentRef(name: "Tome", age: 37, id: id + 1)
oth.self = t
t.self = oth # Reference the other ref
return t
var i = 0
while true:
var t = test(true, 2)
t.self = nil # break cyclic ref for `arc`
i.inc
if i mod 10_0000 == 0:
echo $i & ": Occupied mem " & $formatSize(getOccupiedMem())
Using the default hook destroy, or set the ref field as nil if overriding =destroy
otherwise memory leak!
This needs test cases and documentation as it's not obvious. :-) Previously the interactions between overridden destructors and the cycle collector were undefined.
Description
The test source
Nim Version
The nim 1.6.20
The nim 2.0.8
Current Output