Skip to content

Hex layout optimization#6793

Open
xfractalino wants to merge 13 commits intomasterfrom
hex-layout-optimization
Open

Hex layout optimization#6793
xfractalino wants to merge 13 commits intomasterfrom
hex-layout-optimization

Conversation

@xfractalino
Copy link
Contributor

@xfractalino xfractalino commented Mar 11, 2026

Brief Description of What This PR Does

Avoiding deep-cloning for the organelle layout and hex layouts in general by using temporary pooled memory for diffs only.

Progress Checklist

Note: before starting this checklist the PR should be marked as non-draft.

  • PR author has checked that this PR works as intended and doesn't
    break existing features:
    https://wiki.revolutionarygamesstudio.com/wiki/Testing_Checklist
    (this is important as to not waste the time of Thrive team
    members reviewing this PR)
  • Initial code review passed (this and further items should not be checked by the PR author)
  • Functionality is confirmed working by another person (see above checklist link)
  • Final code review is passed and code conforms to the
    styleguide.

Before merging all CI jobs should finish on this PR without errors, if
there are automatically detected style issues they should be fixed by
the PR author. Merging must follow our
styleguide.

mutation.MutatedSpecies.Organelles.Approve(true);
}

mutation.MutatedSpecies.Organelles.Approve(false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should this be in an else clause? Because now if the if is true above then we first all Approve and then Approve false.

return existingHexes.Contains(item);
}

public void Approve(bool isApproved = true)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why is this method named Approve? Doesn't calling Approve(false) mean that this is rejected? I think for clarity it would be nicer to have two methods: Approve and Reject without any parameters each.


IEnumerator IEnumerable.GetEnumerator()
{
return GetEnumerator();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This causes boxing due to the way the enumerator is implemented. Hopefully this is not called much?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this shouldn't be called at all.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a GD.PrintErr warning about the performance implications then? (if in the future this somehow ends up being called)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This also happens in the current HexLayout implementation, by the way, also in the standard GetEnumerator (which is used in 66 places, Rider tells me)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But there's a comment with a TODO about this very issue.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this case is different, because this is a struct so any allocated enumerator will have a copy of the struct data and not just a reference to the data. So I kind of think that this is a slightly more problematic situation than a simple lightweight enumerator allocation. But I admit I don't fully know how structs having an enumerator function in them behave.

@xfractalino xfractalino force-pushed the hex-layout-optimization branch from 2bcd594 to 48f85e9 Compare March 11, 2026 16:43
public void WriteToArchive(ISArchiveWriter writer)
{
writer.WriteObject(existingHexes);
writer.WriteObject(existingHexes.MainHexes);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm about to start the performance / memory testing of this, so I'll just comment one more thing: I think we need a safety check here in case the data is shared as it would just cut off a portion when saving, right? So I think all of these updated save methods need a method call that the hex layout can react to and make sure it is not shared in order to save correctly. (it could also print an usage warning that only accepted species should be saved)

But silently just not writing all the data should be avoided.

Copy link
Member

@hhyyrylainen hhyyrylainen left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Trying to run a bunch of auto-evo steps I got this error:

Auto-evo failed with an exception: System.ArgumentException: Destination array was not long enough. Check the destination index, length, and the array's lower bounds. (Parameter 'destinationArray')
   at System.Array.CopyImpl(Array sourceArray, Int32 sourceIndex, Array destinationArray, Int32 destinationIndex, Int32 length, Boolean reliable)
   at System.Array.Copy(Array sourceArray, Int32 sourceIndex, Array destinationArray, Int32 destinationIndex, Int32 length)
   at System.Collections.Generic.Queue`1.SetCapacity(Int32 capacity)
   at System.Collections.Generic.Queue`1.Enqueue(T item)
   at AutoEvoRun.GatherInfo(Queue`1 steps) in /home/hhyyrylainen/Projects/Thrive/src/auto-evo/AutoEvoRun.cs:line 405
   at AutoEvoRun.Step() in /home/hhyyrylainen/Projects/Thrive/src/auto-evo/AutoEvoRun.cs:line 559
   at AutoEvoRun.Run() in /home/hhyyrylainen/Projects/Thrive/src/auto-evo/AutoEvoRun.cs:line 533

Note that I merged master to your branch locally before testing as I wanted to test the combined effect of this + my memory optimization. (though I guess I should merge master to this branch here on github as well to make sure we are testing the same code).

@hhyyrylainen
Copy link
Member

hhyyrylainen commented Mar 12, 2026

Here's my initial testing numbers that aren't yet super in-depth:

new times (alive species):

  • 03:00 (76)
  • 02:03 (25)
  • 02:56 (113)
  • 03:53 (87)
  • 06:17 (91)

3.072 GB memory use at the end (top of graph)

old times from master branch (alive species):

  • 01:49 (72)
  • 02:28 (57)
  • 02:33 (114)
  • 08:33 (170)
  • 03:26 (82)

5.120 GB memory use at the end (top of graph), peak likely much more

More data is needed to say for sure what the effect is, but it kind of seems like that memory usage is reduced a bit (though that one massive outlier run probably affected the old memory measurement) but it also kind of seems like processing time might have increased. But we definitely need more measurements. What has your results so far looked like, just to confirm we are seeing similar things?

I'm running 5 worlds to generation 15 as my usual auto-evo speed benchmark in the auto-evo exploring tool (and I'm just taking memory usages at the end for now but I think I might need to swap to running a single world to generation 15/20 and then taking the peak memory usage and fully restarting Thrive between measurements).

@xfractalino xfractalino force-pushed the hex-layout-optimization branch from 7fb338e to c0aa276 Compare March 13, 2026 12:59
@xfractalino
Copy link
Contributor Author

Trying to run a bunch of auto-evo steps I got this error:

Auto-evo failed with an exception: System.ArgumentException: Destination array was not long enough. Check the destination index, length, and the array's lower bounds. (Parameter 'destinationArray')
   at System.Array.CopyImpl(Array sourceArray, Int32 sourceIndex, Array destinationArray, Int32 destinationIndex, Int32 length, Boolean reliable)
   at System.Array.Copy(Array sourceArray, Int32 sourceIndex, Array destinationArray, Int32 destinationIndex, Int32 length)
   at System.Collections.Generic.Queue`1.SetCapacity(Int32 capacity)
   at System.Collections.Generic.Queue`1.Enqueue(T item)
   at AutoEvoRun.GatherInfo(Queue`1 steps) in /home/hhyyrylainen/Projects/Thrive/src/auto-evo/AutoEvoRun.cs:line 405
   at AutoEvoRun.Step() in /home/hhyyrylainen/Projects/Thrive/src/auto-evo/AutoEvoRun.cs:line 559
   at AutoEvoRun.Run() in /home/hhyyrylainen/Projects/Thrive/src/auto-evo/AutoEvoRun.cs:line 533

Note that I merged master to your branch locally before testing as I wanted to test the combined effect of this + my memory optimization. (though I guess I should merge master to this branch here on github as well to make sure we are testing the same code).

I've never gotten this error, and it seems unrelated, as I didn't make any change there.

@hhyyrylainen
Copy link
Member

I think that error may have been a fluke / due to some unexpected merge thing. I'll resume testing this next week.

@xfractalino
Copy link
Contributor Author

xfractalino commented Mar 17, 2026

I did some measurements. Memory usage seems to be roughly the same between master and this branch now with the latest merges from master, although GC pressure and allocations seem to be more predictable on this branch.
Memory usage on this branch
Memory usage on master
Top is on this branch.

Processing times also seem to be roughly the same. Sample size is 10 worlds. y is time taken per generation in seconds, x is the generation number. Note that this is a log-log graph.
Processing times graph

@hhyyrylainen
Copy link
Member

Seems like the difference is now very small.

I guess I have to also test the speed effect now?

I'm not sure what we'll do if the effect is too small to really notice as this does make the layout handling quite a bit more complex and introduces the potential for new bugs...

@hhyyrylainen hhyyrylainen added this to the Release 1.1.0 milestone Mar 17, 2026
@xfractalino
Copy link
Contributor Author

xfractalino commented Mar 17, 2026

I'm not measuring any relevant difference now, indeed. If you don't find any difference either then idk, we can simply close this I guess.

@hhyyrylainen
Copy link
Member

I ran a new set of measurements and it kind of maybe seems like this might have a slight advantage, though way more data would be needed:

Master:
1,19 GB mem at end

- 1:06 (113)
- 0:49 (110)
- 1:03 (171)
- 0:50 (103)
- 0:34 (74)

Hex optimization:
1,19 GB mem at end

- 0:50 (106)
- 0:53 (115)
- 0:39 (113)
- 0:47 (83)
- 0:39 (99)

I think that before any conclusion can be reached on this, we'll need a separate PR merged into master that adds alive species at the end, total processing time, and peak memory usage stats to the worlds export (as talked about on Discord) so that it is possible to do batches like 30 worlds to generation 20 for me (these manual time checks are quite annoying for me to do and don't show a clear enough pattern with this few data points). Without that kind of large amount of data it will be quite hard to tell if this is actually speeding things up measurably.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

Status: In progress

Development

Successfully merging this pull request may close these issues.

2 participants