:::danger Hey, Listen! We've moved our research over to [tgstation/dev-cycles-initiative](https://github.com/tgstation/dev-cycles-initiative). Check the issues for progress, and some links to see profiles. ::: # Init Time Research Lowering the "dev cycle" (time it takes to make a change and test) is of very high importance. While we have been unable to find actionable clues to lowering compile time (other than BYOND updates), we do have control over our init times. Our moon goal is **sub-30 second initialization times on MetaStation, without any configuration changes**. Developers should NOT be required to make any configuration changes, including using something like Runtime Station, in order to have reasonable init times. Runtime Station is NOT SS13. Optimizations that are made should, where possible, avoid anything dramatic to either contributors. Meaning, code that people want to write should, generally, work. We should not arbitrarily complicate general interfaces that every contributor uses in order to eek out performance. Likewise, players should, generally, also not notice any changes to the game. ## "Config'd" When profiling init times, it can be helpful to reduce uncertainty, as well as reduce known high costs. "Config'd" means the following changes: - Comment out `CACHE_ASSETS 0` in config.txt - Set budgets (LAVALAND_BUDGET, SPACE_BUDGET) to 0 in game_options.txt ## Known Costs Measurements are just in local times, and will vary from machine to machine. They are all taken on MetaStation, and are taken config'd as to reduce uncertainty. ## Preferences assets Even with lazy asset loading, every preference still creates lots of icons in their `init_possible_values()` functions. `init_possible_values()`, of many preferences, returns both the asset name AND the icons. ### Estimated Cost `/datum/asset/json/preferences/generate` is 3.3s with CACHE_ASSETS on (not sure how much higher off). Of that 3.3s, 2.21s (67%) is hairstyle/compile_constant_data. 553ms is choiced/compile_constant_data *generally*, followed by 484ms of facial_hairstyle/compile_constant_data. ### Solutions `init_possible_values()` can be changed to only give text values, with icon generation being a separate proc. This would be accompanied by a unit test to make sure every value has an associated icon (if icons are applicable to the setting). This would, ideally, defer costs until the preferences menu is loaded when clicking setup character (since preference asset generation is lazy, even when uncached). ## GAGS GAGS has to perform very expensive icon operations for every color variation that is needed. This creates a very nice interface, where you can just slot in `icon`, but it carries a huge weight on init times. Worse, the cost of GAGS makes profiling the cost of anything related to GAGS harder to measure. More on this later. ### Estimated Cost `/datum/controller/subsystem/processing/greyscale/proc/GetColoredIconByType` is 1.31 seconds of init time. Howver, 91% of calls are only 22ms. The rest are fairly scattered at the end. This is most likely the cache. ![](https://i.imgur.com/d8bKaaQ.png) The bulk cost is simply in icon operations. `GetPixel` (1.19s cost, from `/datum/greyscale_config/proc/GenerateBundle`) especially is very slow, but because it is serving its purpose of forcing the icon to generate--any icon operation would be doing the same. ### Solutions It is possible to precompile statically-inferrable GAGS usages. As in, everything we can *predict* is going to use GAGS (like from constant `greyscale_config` and `greyscale_colors` values) can be put into a .dmi, pushing the costs away from init time. This would come with an extra compile time cost, but it could only be incurred when the inputs are different, like if a .dmi changes or a variable changes. Dynamic generation still exists, but we statically create as much as we can. There is a prototype of this in Rust, but we could even potentially have a DM backend for this and run dd.exe when it comes out to use it. It is also potentially possible for objects to choose between generating icons and using something like overlays, when applicable. However this could carry non-negligible runtime costs from either adding the overlays, or maptick costs if vis_contents was used. It also is under the assumption the bulk of this cost is in these simple icons, which we have not yet proven. ## Storage items Every storage item, such as toolboxes and backpacks (but not closets/crates) initializes all their contents immediately. ### Estimated Cost `/obj/item/storage/Initialize` is 846.59ms, but this cost is hard to truly assess, as not only are a lot of toolboxes GAGS, but so are their contents. The cost of just PopulateContents (which is still impacted by, say, GAGS screwdrivers) is very close to this number, though. ### Solutions The most obvious solution is to lazily initialize the contents of storage until requested. This would fix the issue, but it's not clear if the fragility of this approach is worth the cost. ## Machine parts Every machine initializes all of its machine parts immediately. ### Estimated Cost `/obj/item/circuitboard/machine/apply_default_parts` is 280ms. ### Solutions Machine parts should be instantiated only when necessary. This in and of itself is simple enough, however an issue arises with an extremely common pattern of: ```dm for (var/obj/item/stock_parts/capacitor/capacitor in machine_parts) power += capacitor.capacitating_power ``` This pattern is seen for every stock part. You could change a lot of this by turning them into datums, and then a find-and-replace should work, but it makes the change tricky. Power cells would likely need to always exist. ## NTNet and Radio frequencies Code that creates NTNet interfaces, broadcasts signals, etc is very slow. ### Estimated Cost - `/datum/component/ntnet_interface/Initialize` is 125.11ms. - `/datum/radio_frequency/proc/post_signal` is 198.41ms. It is fired 588 times, from lots of different atmospherics machinery for the purpose of connecting to air alarms and similar computers. Its self cost, as in the cost of this proc minus it calling other procs, is 81.44ms. - The `broadcast_status` proc on atmos stuff is about 218ms. What's more, this is in `atmos_init()`, where if it takes too long, `setup_atmos_machinery()` forces a `CHECK_TICK`, costing an entire tick's worth of delay. - `/obj/proc/init_network_id` is 114.75ms over 1,076 calls. - Most of this is in `log_telecomms` (81.72ms) and is easily fixable. There is a path where it replaces the current network with a new one, which is ran regardless of if the networks are the same, which happens thousands of times in init. Check telecomms.log of any round! ### Solutions NTNet and the general radio framework is an enormous piece of code that is completely invisible to players, as there is no interesting way to utilize NTNet itself. There exist things like the NTNet circuit components, but they are on their own isolated network and can be trivially replaced. NTNet and the radio frequencies from atmos stuff can be completely removed. Aside from that, fixing the `log_telecomms` will lower a significant cost. ### Progress [#71232 - Atmospheric machineries now interact with each other directly, rather than going through a radio layer -- saves about 0.4s of init time](https://github.com/tgstation/tgstation/pull/71232) ## Guns Guns create every bullet inside them before they are used. ### Estimated Cost `/obj/item/gun/ballistic/automatic/wt550/Initialize` is 54.22ms. This is from 11 creations in CentCom. `/obj/item/gun/energy/Initialize`, with 48 calls, is 35.27ms. ### Solutions The obvious solution is only creating bullets when they are necessary. Gun code is extremely old and so this may not be super trivial. Some interim solutions could be to just put the guns inside lockers, which lazily create their contents, but this conflicts with our goal for efforts of this project to be invisible to players, and could present real balance implications. ## Elements (but mostly decals) Adding an element calls `/datum/controller/subsystem/processing/dcs/proc/GetIdFromArguments`, which at the scale it is being called (114,575 times), is very slow. ### Estimated Cost `/datum/controller/subsystem/processing/dcs/proc/GetIdFromArguments` is 1.27 seconds. A lot of this is in decals. `/datum/element/decal/Attach` clocks at 356.76ms, though not all of that is GetIdFromArguments. Similarly, `/datum/element/bump_click/Attach` has a cost of 581.38ms, but it is presently being removed from its primary source (mineral turfs). ### Solutions `GetIdFromArguments` exists to ensure that elements with the same parameters always resolve to the same instance. Prior efforts to try to guarantee this statically have not been successful with edge cases. It is also possible to cache this ID where it is known not to change. ![](https://i.imgur.com/1mD1m7x.png) It is not obvious (to me, anyway) how this could be done without introducing a footgun, breaking our 99th percentile goal. ## Space There's a lot of it. There's one last piece of `/turf/open/space/Initialize` that isn't hyper-optimized, setting the dynamic icon_state. ### Estimated Cost Though not in its own proc, the icon_state set has been measured at 707ms. ### Solutions The icon state is only used for non-parallax space, which is a rarely used setting, and is more commonly seen in something like camera consoles. A single, nice, tiled icon state should be used. This change is unnoticable to nearly all players. ## Armor Every atom with integrity in the game calls `getArmor`, which maps armor to singletons. This includes armor lists, of which nearly 500 exist in typedefs. ### Estimated Cost `getArmor` is 285ms over 67,671 calls. ### Solutions Armor has always been a pain point for maintainers since it's very hard to tell why values are the way there are. Ideally there would be no armor lists and instead, you would do `armor = /datum/armor/fireproof` or similar. Anything that needs custom armor could make its own subtype. These could then be used as keys to a singleton. Customizing armor for admins is already not done by editing armor directly and thus there would be no difference in UX there. ## Reagents Lots of atoms create reagents, and this creation process is very expensive. ### Estimated cost `/datum/reagents/proc/add_reagent` is 247.52ms, with 3,616 calls. Half of this cost is in `/datum/reagents/proc/handle_reactions`, an enormous proc that forces any chemical combination to react. ### Solutions Work has not yet been done in seeing if handle_reactions has some obvious complexity issues, but it would be worth investigating if any current init calls to add_reagent force chemical reactions. If that is not a common pattern, or if it never comes up, we can block this proc call entirely on init, with a unit test to ensure that nothing in init does this (or if it does, it has to opt in to calling `handle_reactions` somehow). ## Turf Lighting Lighting is pretty slow on its own, but there aren't a great many potential methods to speed it up. Something that does come to mind is the color matrix work we do Whenever a turf's lighting changes, we need to do two overlay operations. These require "flattening" mutable appearances, and involve modifying the turf's appearance, which is slow on its own. What we used to do was stick lighting "objects" on each turf But that comes with a persistenct sendmaps cost, which scales with pop and is therefor not worth it with our current lighting churn rates (thank you overlay lighting) ### Estimated cost I think it was like 0.7 seconds, tho it's hard to say for sure and I am kinda pulling that number out of my ass ### Solutions Outside of just making lighting faster somehow, if we got to a point where sendmaps costs were less important (say if it was threaded effectively) we could return to the object model and save a good chunk.