Replace TraitContainer.AllEnumerator with ApplyToAll.

As proposed in the past in #13577.

Replace TraitContainer.All() that uses the custom AllEnumerator with
TraitContainer.ApplyToAllX() that takes an action as argument.

The AllEnumerator.Current function show up in profiling reports since it is
used each tick multiple times for multiple traits. The function is 'heavy'
because it creates TraitPair<T>'s temporary objects for each actor
trait combination.

In the past about 20k ITick trait pairs were present during an average
multi player game.

Using an Apply function that takes an action avoid the need to create
these temporary objects.

To be able to still use 'DoTimed' somewhat efficiently the measurement
was moved to inside the trait container method.

Results in a 25% performance improvement in accessing all traits of
a certain type.

Apply function could be used for other TraitContainer functions as well
for further improvements.

Test result for calling on a dummy trait on 20k actors a 1000 times:
  1315 ms traitcontainer.AllEnumerator (current)
   989 ms traitcontainer.Apply (this commit)
This commit is contained in:
Vapre
2020-07-04 20:27:35 +02:00
committed by Paul Chote
parent 4cdbf74256
commit 38f1f1e5c2
2 changed files with 45 additions and 2 deletions

View File

@@ -11,8 +11,10 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using OpenRA.Primitives;
using OpenRA.Support;
namespace OpenRA
{
@@ -122,6 +124,16 @@ namespace OpenRA
t.Value.RemoveActor(a.ActorID);
}
public void ApplyToActorsWithTrait<T>(Action<Actor, T> action)
{
InnerGet<T>().ApplyToAll(action);
}
public void ApplyToActorsWithTraitTimed<T>(Action<Actor, T> action, string text)
{
InnerGet<T>().ApplyToAllTimed(action, text);
}
interface ITraitContainer
{
void Add(Actor actor, object trait);
@@ -277,6 +289,32 @@ namespace OpenRA
actors.RemoveRange(startIndex, count);
traits.RemoveRange(startIndex, count);
}
public void ApplyToAll(Action<Actor, T> action)
{
for (var i = 0; i < actors.Count; i++)
action(actors[i], traits[i]);
}
public void ApplyToAllTimed(Action<Actor, T> action, string text)
{
var longTickThresholdInStopwatchTicks = PerfTimer.LongTickThresholdInStopwatchTicks;
var start = Stopwatch.GetTimestamp();
for (var i = 0; i < actors.Count; i++)
{
var actor = actors[i];
var trait = traits[i];
action(actor, trait);
var current = Stopwatch.GetTimestamp();
if (current - start > longTickThresholdInStopwatchTicks)
{
PerfTimer.LogLongTick(start, current, text, trait);
start = Stopwatch.GetTimestamp();
}
else
start = current;
}
}
}
}
}

View File

@@ -465,7 +465,7 @@ namespace OpenRA
foreach (var a in actors.Values)
a.Tick();
ActorsWithTrait<ITick>().DoTimed(x => x.Trait.Tick(x.Actor), "Trait");
ApplyToActorsWithTraitTimed<ITick>((Actor actor, ITick trait) => trait.Tick(actor), "Trait");
effects.DoTimed(e => e.Tick(this), "Effect");
}
@@ -477,7 +477,7 @@ namespace OpenRA
// For things that want to update their render state once per tick, ignoring pause state
public void TickRender(WorldRenderer wr)
{
ActorsWithTrait<ITickRender>().DoTimed(x => x.Trait.TickRender(wr, x.Actor), "Render");
ApplyToActorsWithTraitTimed<ITickRender>((Actor actor, ITickRender trait) => trait.TickRender(wr, actor), "Render");
ScreenMap.TickRender();
}
@@ -536,6 +536,11 @@ namespace OpenRA
return TraitDict.ActorsWithTrait<T>();
}
public void ApplyToActorsWithTraitTimed<T>(Action<Actor, T> action, string text)
{
TraitDict.ApplyToActorsWithTraitTimed<T>(action, text);
}
public IEnumerable<Actor> ActorsHavingTrait<T>()
{
return TraitDict.ActorsHavingTrait<T>();