ID:2080183
 
I just got done fixing Epoch so that the loading times were 30 seconds on my powerhouse of a machine when I started and now that I'm a day into the optimization cycle, I'm loading the game at 2 seconds.

The reason for this startup time was mostly confined to one or two issues that I see all over BYOND games' source code. This is a good time to explain something that you shouldn't be doing.

turf
New()
..()
spawn(TICK_LAG)
//change tile's appearance based on surrounding tiles


^This was basically the problem.

The reason that Yut Put was spawning on New() is because the map doesn't initialize fully all at once. turf/New() is called while the map is still initializing Yut needed information on turfs surrounding the turf to build autotiling information.

I fixed this very simply:

world
New()
..()
MapInitialized()
var
list/__post_init = list()
map_initialized = 0

proc
MapInitialized()
if(!map_initialized)
map_initialized = 1
for(var/atom/o in __post_init)
o.MapInit()
__post_init = null

atom
var
post_init = 0 //set to 1 if you need to do post-initialization stuff.
New()
if(map_initialized)
post_init&&MapInit()
else if(post_init)
__post_init[src] = 1
..() //don't supercall unless you have other /atom/New() functions

proc
MapInit()
//do map initialization stuff here


To the end user, this looks like:

//world startup is 2 seconds
turf
MapInit()
//change tile's appearance based on surrounding tiles


//world startup is 30 seconds
turf
New()
..()
spawn(TICK_LAG)
//change tile's appearance based on surrounding tiles


Also, blatant plug, read my Snippet Sunday #10 for more information on the world startup cycle: http://www.byond.com/forum/?post=2012623



And then I simplified a lot of the type tree structure to reduce the overhead required by the game's initialization.

If you are spawning inside of /turf/New() to make sure the map has a chance to load, you will wig out the scheduler and it will take much longer for your game to start than it should.

The reason that spawn() can create huge problems like this is that spawn() has to copy all of the arguments for the currently running function and insert a new function reference into the scheduler. To ensure the function is called the correct way, the scheduler has to search through all other pending events to find the right place to put the pending function call. If every turf in the world is spawning a function in that scheduler's pending function list, these searches take progressively longer.

spawn() is sometimes used as a "makes game faster" panacea. It's not. If you use it when you shouldn't, it will 100% make your game run like molasses.

Blatant Plug: Read my Snippet Sunday #4 for more information on the scheduler: http://www.byond.com/forum/?post=1638359
Is checking before calling an empty procedure faster than just calling the empty procedure?

Yes. Invocation overhead can be significant.
30 seconds on my powerhouse of a machine

What's your build :D?


I'm loading the game at 2 seconds.



It's actually pretty out of date. Mind you, I built most of this machine 5 years ago and it's still pretty close to bleeding edge. My processor is really the guy that needs work, and my memory needs a total replacement.

Core i7 920 (3.2ghz stock) oc @ 4.0ghz stable. Air cooled with a D92 heatsink.
8GB DDR3 1333
NVIDIA GTX 960 2GB.

Some parts have seen upgrades, others not so much. When I built the machine, the i7 920 was brand new. Dual-cores were still prevalent. I'd like to migrate to the i7 4880K though. I just haven't had the cash.
In response to Ter13
:o that's strong enough to play Diablands 2 handsum james collection with me and kumoii >:).

I actually like the look of the cpu you're going to upgrade to :].

Have you heard of the Nvidia Pascal :O?? Apparently it's supposed to make the titan look like cheese cake >.< which is crazy.
In response to Bravo1
Bravo1 wrote:
Is checking before calling an empty procedure faster than just calling the empty procedure?

I have a similar question, but regarding variables. Let's say you are finalizing an object:

if(var1) var1 = null
if(var2) var2 = null
if(var3) var3 = null

// versus

var1 = null
var2 = null
var3 = null


Which method would be recommended here? Going through conditional checks to avoid unnecessary variable setting, or resetting each variable regardless of their state to avoid the conditionals?

I started out checking variable states before resetting. Eventually though, I started resetting the variables regardless, on the idea that I'd be saving time avoiding the number of conditional checks.

And then I simplified a lot of the type tree structure to reduce the overhead required by the game's initialization.

I knew the map editor's load time was affected by the type tree but not the game's startup time. Good to know.
I think the look-up and set operations for variables used in those processes use about the same amount of resources, but in the example where you check with if() beforehand you have the potential for two operations instead of just one no matter what.

I'd say forego the checks, you're not really saving anything if the check fails but adding if the check succeeds.
I'd say forego the checks, you're not really saving anything if the check fails but adding if the check succeeds.

Exactly. In your example the condition can only make things worse.
On that note, the reverse is true for initializing an object and checking the New() args.

New(_var1, _var2, _var3)
if (_var1) var1 = _var1
if (_var2) var2 = _var2
if (_var3) var3 = _var3

Proc vars are stupid fast, o(1) (they compile to an array index) whereas datum vars are slower, o(n), so the savings is worth it once you get to complex types that have alot of parent vars.

On that note, if this object is gonna be accessed a lot in a performance dependant fashion, you can assign the most commonly accessed vars first in New() to make them end up higher in the var search list and access quicker. (its rare you can find a case where this matters, but it let us get 2% of speed out of atmos at /tg/station)