Closed RecursiveStar closed 4 years ago
I only do programming as a hobby, so hopefully my understanding is not too off here. Please correct me if I am wrong.
As a side note, I think the impact is higher on certain database tables, where the Firaxis method is to initialise an array to store the values for all possible settings. This is not really relevant to you I think, but might be of interest.
For example, looking at the table which allows traits to give yield per turn to building classes (I think its called Trait_BuildingClassYieldChanges or something similar), if we follow the Firaxis method, for each entry in the Traits table, we will need to define a 3-dimensional array to store all combinations of building class, yield type, and yield. Each of them will be use an int (building class and yield's int will be the id in the database). Let's say you have 40 building classes and 6 yield types defined. This means for each trait, I will have an array which contains 40 × 6 = 240 integers (240 × 4 bytes = 960 bytes, almost a kilobyte) .
This array is initialised, meaning the size will not change. So it will always use the kilobyte, even if in the database, we define nothing. Inside the DLL, it will then be an array full of zeroes.
Usually a trait table might only be used by one trait entry (in our example, only Japan uses it), so this is very wasteful, as out of the 40+ arrays (1 for each trait), only one array might be useful. The other 40 kilobytes are wasted, as they are all zeroes.
What I have been trying with my new tables is to use std::map instead of arrays, and only insert elements when values are non-zero. So, back to our examples, all non-Japan traits will have a std::map with nothing inside. The std::map itself still takes memory though, even if empty, so I still need to check what this actually is, to see if I'm actually doing any saving.
If you only define a variable within the function, and not as a public variable in the header file, the memory will be released once the function ends. The next time the function is called, the game will not know the value from the previous call.
How large is large? Integers and booleans do not use much individually, and the memory is released after the function ends anyway. I think from your example, there are less than 20 integers and booleans each, so 20 × 4 bytes + 20 × 1 bytes = 100 bytes. Doesn't seem much.
in general everything we do in the DLL pales in comparison to the memory used for the UI. however you should be careful with large arrays and high multiplicity (eg adding something to each plot which may be instantiated 10000 times vs something added to the diplo AI which exists 64 times). using std::maps in general saves memory compared to arrays which contain mostly zeros, but access is a bit slower (typically not a concern in diplo AI)
local variables live on the stack (a kind of scratch pad). by default max stack size is 1MB iirc. each function has a "stack frame", which is cleared when the function returns. however complex types may internally also do heap allocations (eg a vector<>) so they take only a few bytes on the stack independent of their actual size.
no problem but if the call is cheap and/or the result is only used once, there is little point in having a local cache besides maybe easier debugging. in a release build the compiler will probably optimize it away anyway.
@HungryForFood @ilteroi Thank you for the reply!
It's my understanding that:
Memory values and local variables have a negative impact on memory, and function calls have a negative impact on performance.
If a function call is expensive and a local variable can be used to cache the value, this is a performance optimization.
I have three questions for modders who are more familiar with the game and with C++ programming than I am:
1) Do integer and boolean memory values have a significant impact on memory? I've added new memory values to the diplomacy AI, and I know Civ V has limited memory. Obviously they have some impact, but if the total is relatively negligible and much more memory is consumed by, say, art assets, text, and so on, I won't be too worried. As I previously observed in #5945, there are already lots of zombie memory values from the base game that have not been removed. My assumption is that the impact of integer and boolean values is therefore not that high.
2) When a function ends (returns nothing, or an empty value), what happens to the values of the local variables within that function, and their impact on memory?
3) If a function has a large number of local variables (mostly integer/boolean), would this cause a problem? I'm contemplating a reorganization of the GetBestApproachTowardsMajorCiv function that minimizes the number of function calls by caching a lot of values at the start of the function before evaluating conditions.
For e.g. victory competition, this also has benefits in that I can simply set the default value of all the "TheyAreCloseToXVictory" values to false, and only run the checks if victory competition is enabled. Then I don't need to call IsNoVictoryCompetition a bunch of times.
For optional components like C4DF, by caching the value of all relevant values at the start, I only need to check if C4DF is enabled once, allowing me to remove many #if defined and if (MOD_DIPLOMACY_CIV4_FEATURES) checks later in the function.
Draft code attached below: most of these values already exist as variables or function calls within GetBestApproachTowardsMajorCiv. Would this kind of local caching within the function cause problems?