Opened 6 years ago
Last modified 11 days ago
#5011 new defect
random map script performance improvements
Reported by: | elexis | Owned by: | |
---|---|---|---|
Priority: | Should Have | Milestone: | Backlog |
Component: | Maps | Keywords: | |
Cc: | Patch: |
Description
Some map generations are inacceptably slow and consume up to 3 minutes, whereas most maps are done within 15 seconds.
With #4950 we have log message entries measuring the time for each step in all maps. Here the biggest hits with normal mapsize:
Alpine valleys Creating mountainrange with 500 possible edges... 133.229s. english channel Creating tributary rivers... 183.528s. danubius Creating spawn points for ships... 7.621s. Creating patrol points for ships... 4.989s. Creating ungarrison points for ships... 11.109s. deep forest Painting paths... 6.879s. extinct volcano Creating hills... 6.028s. Creating forests... 5.960s. Creating hill patches... 12.319s. frontier Creating plateaus... 20.481s. hells pass Creating lakes... 15.883s. Creating bluffs... 30.356s. islands Creating big islands... 6.262s. Creating small islands... 6.127s. kerala Creating hills... 9.072s. migration Creating shore jaggedness... 26.056s. pyrenean sierra Creating forests... 20.743s. rhine marshlands Creating bumps... 13.460s. sahel Creating big patches... 5.457s. sahel watering holes Creating rivers... 6.017s. Creating bumps... 6.743s. Creating hills... 4.524s. Creating forests... 0.001s. Creating dirt patches... 7.111s. Stronghold Creating valleys... 7.431s. Creating plateaus... 20.617s. Creating forests... 8.884s. Syria Creating bumps... 11.347s.
As reported in #4695, the 25 seconds on danubius are needless and can be cut down by not using the deprecated group placement functions.
Attachments (1)
Change History (25)
comment:1 by , 6 years ago
comment:3 by , 6 years ago
Often rmgen is slow when we need to do many retries to place entities that have a low likelihood of being placed by chance in a position that meets all constraints.
For instance on Danubius it is unlikely to get a random coordinate that is on an island to place a tower there. Then we need a high amount of retries to compensate and it consumes up to 10 seconds. Should use createObjectGroupsByAreas
.
comment:4 by , 6 years ago
6 player normal-sized Danubius
before improvement:
Generating Danubius of size 320 and 6 players. Creating gallic villages... 0.259s. Creating playerbases... 0.109s. Creating river... 0.215s. Creating shores... 0.057s. Creating bumps... 2.794s. Creating hills... 0.924s. Creating forests... 3.752s. Creating grass patches... 0.838s. Creating islands... 0.836s. Creating bumps... 0.361s. Painting seabed... 0.042s. Creating island metal mines... 0.543s. Creating island stone mines... 0.582s. Creating island towers... 0.859s. Creating island outposts... 0.957s. Creating metal mines... 0.629s. Creating stone mines... 0.887s. Creating stone ruins... 0.860s. Creating decoratives... 0.000s. Creating decoration... 0.207s. Creating decoration... 0.098s. Creating fish... 0.000s. Creating food... 0.170s. Creating huntable animals... 0.000s. Creating food... 0.043s. Creating violent animals... 0.000s. Creating food... 0.040s. Creating fruits... 0.000s. Creating food... 0.021s. Creating straggler trees... 0.312s. Creating straggler trees... 0.915s. Creating animals on islands... 0.000s. Creating food... 0.318s. Creating treasures... 0.006s. Creating gallic decoratives... 0.001s. Creating decoration... 0.027s. Creating spawn points for ships... 7.456s. Creating patrol points for ships... 4.959s. Creating ungarrison points for ships... 10.847s. Creating riverdirection triggerpoint... 0.005s. Creating patrol points for land attackers... 44.786s. Creating water logs... 0.021s. Setting day theme... 0.002s. Total map generation time: 85.806s.
Same replay after improvement:
Generating Danubius of size 320 and 6 players. Creating gallic villages... 0.267s. Creating playerbases... 0.111s. Creating river... 0.479s. Creating shores... 0.074s. Creating bumps... 2.791s. Creating hills... 0.927s. Creating forests... 3.746s. Creating grass patches... 0.824s. Creating islands... 0.822s. Creating bumps... 0.345s. Painting seabed... 0.032s. Creating island metal mines... 0.087s. Creating island stone mines... 0.128s. Creating island towers... 0.086s. Creating island outposts... 0.201s. Creating metal mines... 0.047s. Creating stone mines... 0.220s. Creating stone ruins... 0.012s. Creating decoratives... 0.000s. Creating decoration... 0.192s. Creating decoration... 0.101s. Creating fish... 0.000s. Creating food... 0.166s. Creating huntable animals... 0.000s. Creating food... 0.052s. Creating violent animals... 0.000s. Creating food... 0.039s. Creating fruits... 0.000s. Creating food... 0.017s. Creating straggler trees... 0.289s. Creating straggler trees... 0.934s. Creating animals on islands... 0.000s. Creating food... 0.311s. Creating treasures... 0.000s. Creating gallic decoratives... 0.000s. Creating decoration... 0.019s. Creating spawn points for ships... 0.261s. Creating patrol points for ships... 0.319s. Creating ungarrison points for ships... 1.566s. Creating riverdirection triggerpoint... 0.000s. Creating patrol points for siege engines... 0.467s. Creating patrol points for soldiers... 0.167s. Creating water logs... 0.008s. Setting night theme... 0.000s. Total map generation time: 16.174s. Total entities: 4646.
comment:7 by , 6 years ago
I suspect we could use a StaticConstraint
, or pass two constraints (static and mutable) to createAreas
.
For instance when placing animals, we have avoidClasses(clForest, 1, clMetal, 4, clRock 4, ..., clFood 2)
. In createAreas
we pick a random coordinate of the map or given areas and then throw that away if it's too close to mines, trees or food. But the tree and mine tileclasses never change when placing, only the clFood tileclass. So only the clFood has to be reevaluated, while the other classes can be evaluated at the beginning of a call.
This is similar to the createAreasByAreas
function, but we likely want to avoid creating the area for the performance improvement in every single call, hence the StaticConstraint
idea, in case that is achievable.
comment:8 by , 6 years ago
avoidClasses becomes slow with numbers of 10 and greater. One could improve the performance by marking more of the area with the tileclass and avoiding that are a less.
For example clMetal
is only placed at the center tile of the mine afaik, but a 4x4 square (which covers the current obstruction size) would be better for performance.
comment:18 by , 6 years ago
NearTileClassConstraint
can return true early if the first match has been found, doesn't have to compute all matches.
comment:19 by , 5 years ago
If avoidClasses
receives multiple classes, it will iterate the affected area once per tileclass.
Instead it could iterate only once over the largest area and test the affected classes from the center to the border. But that might also be slower because usually the tileclasses shold be ordered to have the most likely one to avoid first (for performance reasons).
comment:20 by , 5 years ago
problem: One can often not estimate well how many entities should be placed for every mapsize and how many retries are necessary to place as many entities as one wants to. So one overshoots and then the code runs hundreds of retries that can never succeed and cost lot of time.
mapscript solution: first compute the area, then place entities inside there. Disadvantage: scales badly with larger mapsizes, more code complexity, repetitive.
rmgen solution: createObjectGruops could have a new option to keep track of locations already tested and not test that location again, thence return early before all retries are performed.
comment:21 by , 5 years ago
avoidClasses
precomputation optimization:
The performance of an avoidClasses(tileclass, distance)
constraint can be optimized by computing the grid differently after the last tile was marked with tileclass. The tileclass can remember the points that were marked. Then when the last tileclass was marked, it can do breadth-first-search outwards from the given points and remember the closest distance to the tileclass. The BFS can be stopped after the largest distance to be searched was processed.
This would be faster in situations where the constraint is performed on many tiles.
Anti-use-case: For example if an area that makes up 10% of the map is marked with that tileclass and with the avoidClasses distance it would extend to 20% of the maparea, then the BFS will take a lot longer to be performed than the current "test-on-demand" approach if the latter would succeed after only a handful of calls (for example only 5 entities placed with that constraint and the constraint is true with 80% probability in our example, so almost no retries).
Use-case: Sometimes many dozens of areas and objectgroup calls must avoid the playerbase, but with different distances (for example 15 for resources and actors, 25 for animals, water and hills, 60 for fortresses). Here we can assume that the order of magnitude of the map area is tested (the constraint tested multiple times per tile).
Precomputing the constraint with createArea is wasteful, because the area is processed once per constraint and because it doesn't consume the benefit of skipping the computation of avoidClasses
calls that are known to return false
, while the proposed algorithm does. The StaticConstraint
doesn't help either, so the proposal maybe worth to be implemented if we carefully observe enough use cases that can't be solved more elegantly.
comment:22 by , 5 years ago
RangeOp optimization:
In 'tileclass.js` we find:
if (radius >= 27) // Switchover point before RangeOp actually performs better than a straight algorithm
To me it's always faster, cutting mapgen time from 60s to 30s on one map! It does change mapgen, so it either influences RNG or has a bug somewhere.
We should have a performance test that figures out the best value for the given test, computer, SpiderMonkey version, providing an indicator for easy maintenance by a developer and testing that the two algorithms compute the same result.
by , 5 years ago
Attachment: | rangeOp.patch added |
---|
Test to visualize the difference between RangeOp and the default algorithm. They ought to compute the same result.
In 21082: