I really struggle to explain this one. I discovered some kind of permanent fuckup state that BYOND can encounter when dereferencing a large number of items by assigning a list's length to zero, cutting a list, or derefing a list. My test cases have been getting weirder and weirder, but I've got a consistent one that shows the problem in action.
The weird part of this problem is that it's outright fucking catastrophic, and oddly specific as to why it happens. It just doesn't make sense at all, and I have the feeling this one is causing pretty much everyone's games horrible grief. I'm talking slowing object creation down 10 to 100 fold, and slowing global code execution down by up to 33%.
Numbered Steps to Reproduce Problem:
1) Create a large number of objects on the map, storing them in a list belonging to an object (local lists won't work).
2) Assign the length of the list to 0, cut the list, or throw the list away.
3) loop over the world to move all the objects you created on the map to null. This will cause them to be garbage collected.
BYOND now permanently runs like utter shit when creating new objects, and every proc I've tested runs about 33% slower. There's also a 20 meg permanent memory footprint that never goes away, but doesn't get bigger on subsequent tests.
Now, here's the really fucky part. If you do it in this order: The problem doesn't happen:
1) Create a large number of objects on the map, storing them in a list belonging to an object (again, local lists do not work).
2) loop over the world to move all the objects you created on the map to null. This will not cause them to be garbage collected, because they are still referenced by the list.
3) Assign the list's len to 0. This will trigger garbage collection of all the objects.
Interestingly, THIS process does not cause the bug, yet is affected by the bug when it's caused.
Even more interesting, when performing the above process, it does not happen again within the same running proc even if it has been slept. Only with procs started on a subsequent frame. So iterations of my test 1 always show the slowdown, but not iterations 2+. But when triggering the proc the first time, iteration 1 doesn't show the slowdown, then iteration 2 shows the slowdown, but iteration 3 does not.
Code Snippet (if applicable) to Reproduce Problem:
This code snippet will show the problem in action and act as a compilable test case.
world
maxx = 1000
maxy = 1000
maxz = 1
#define ACCURATE_TIME (world.time + world.tick_lag * world.tick_usage/100)
var
bugged = 0
mob
var
list/l = list()
test_repetitions = 5
test_iterations = 100000
verb
demonstrate_stable()
world.log << "Demonstrating stable behavior[global.bugged ? " is affected by unstable behavior" : ""]..."
var/start_time, duration, r, i, c
for(r in 1 to test_repetitions)
world.log << "Creating [test_iterations] objs..."
sleep(1)
start_time = ACCURATE_TIME
c = 0
for(i in 1 to test_iterations)
l += new/obj(locate(rand(1,1000),rand(1,1000),1))
++c
duration = ACCURATE_TIME - start_time
world.log << "Created [c] objs in [duration*100]ms"
world.log << "Deleting [test_iterations] objs..."
sleep(1)
start_time = ACCURATE_TIME
c = 0
for(var/obj/o in world)
o.loc = null
++c
world.log << "Clearing list (triggering garbage collection)"
l.len = 0 //this line does not cause the bug
duration = ACCURATE_TIME - start_time
world.log << "Deleted [c] objs in [duration*100]ms"
sleep(1)
world.log << "Completed demonstrating stable behavior..."
demonstrate_unstable()
world.log << "Demonstrating unstable behavior..."
var/start_time, duration, r, i, c
for(r in 1 to test_repetitions)
world.log << "Creating [test_iterations] objs..."
sleep(1)
start_time = ACCURATE_TIME
c = 0
for(i in 1 to test_iterations)
l += new/obj(locate(rand(1,1000),rand(1,1000),1))
++c
duration = ACCURATE_TIME - start_time
world.log << "Created [c] objs in [duration*100]ms"
world.log << "Deleting [test_iterations] objs..."
sleep(1)
start_time = ACCURATE_TIME
c = 0
world.log << "Clearing list (not triggering garbage collection)"
l.len = 0 //this line causes the bug
for(var/obj/o in world)
o.loc = null
++c
duration = ACCURATE_TIME - start_time
world.log << "Deleted [c] objs in [duration*100]ms"
global.bugged = 1
sleep(1)
world.log << "Completed demonstrating unstable behavior..."
The really compelling part about that theory, is that if you never give these objs a loc, but still induce the deref problem another way, the bug never happens. So this bug has something to do with putting the objects on the map and then fucking with the length of a list in such a way that the items are unrefcounted, but not derefed completely.