0.11.0.7 – Performance Hotfix

One of the big pieces of feedback from the Steam Release (yay!) has been complaints about the performance of the game, and the couple bad reviews it’s gotten so far have focused on that specific issue, so I’m trying to do a bit of a performance pass.

Thanks to the subject matter, this will probably be a dry, technical post. Be warned. Be afraid.

Species is always going to be a somewhat sluggish game due to the nature of it’s gameplay (the game gets more interesting as you bring in more creatures and push the hardware to it’s limits) and the restrictions of making an accurate evolution simulator (I can’t lower the fidelity of the simulation where you’re not looking lest I affect the evolution), but there are certainly things I can do.

Let’s see if we can find some of them with the magic of profiling…

(For the record, I’m using a piece of software called Codetrack to analyse the game, since my old profiler stopped working. I’m quite happy with it so far, so I guess that’s a recommendation)

PerformanceChart

This is a flame graph of where all the CPU time went during a short Species game. Don’t worry, I don’t expect you to analyse it yourself. Here’s the same graph with labels:

PerformanceChart3

(You should be able to click the image to expand it and be able to actually read the labels. Or just keep reading, I’ll try to explain as we go)

On the left we have the time it took to go through startup and loading routines. For now we can ignore them, they don’t factor into the frame-to-frame CPU load.

The big red mountain in the middle: that’s where most of our time is going. That’s Creature AI, which is unsurprisingly one of the largest costs of the game.

What stands out most to me is the plateau on the top of the AI mound. The width of these bars indicates how much CPU they cost, so long, flat methods like “GetGeneticDistance” on the top there are prime candidates for optimization.

GetGeneticDistance does what it says on the tin: takes two genomes and works out how closely related they are. When we say things like “humans and apes share 98% of their genetic code”, GetGeneticDistance is the method that calculates and quantifies that 98% in Species.

It’s not surprising that it would be expensive: GetGeneticDistance has to compare 80+ genes of varying types and ranges. What is surprising is that it would be expensive there. Normally, GetGeneticDistance is used to find speciations and decide if two creatures can mate, but this is coming from FindOptimalFoodSource (which also does what it says on the tin: finds the food source (tree or creature) best suited for the current creature to target).

The reason GetGeneticDistance is called from FindOptimalFoodSource is, put simply, Empathy. Empathy prevents closely related creatures from considering each other as food sources unless there’s no other options, and how do two creatures know whether or not they are closely related? GetGeneticDistance.

 

I think we can all see where this is going. Empathy is computationally expensive. It’s gotta go.

Luckily, there are proxies in the game for genetic distance, so we don’t need to get rid of empathy entirely. A creature knows who it’s parents and children are, and who members of it’s own species are: it doesn’t need to know the exact genetic distance to understand that it’s own children are friends, not food.

[Insert coding montage set to Eye of the Tiger by Survivor here]

That leads us to this image:PerformanceChart4

Better! Previously, it took around 6x as long to calculate creature optimality than it did tree optimality. Now, creature optimality is barely double tree optimality! And a big chunk of that is a ToString method I later realised I didn’t need and removed entirely.

So that’s a big victory. But y’know what? we can still do better.

Looking into the AI’s call stack a bit, you’ll find “CalculateFoodOptimality“. That’s this one:

PerformanceChart7

It’s a big CPU cost, but I kind of expect this one to be. In spite of how dumb they often seem, a lot of work goes into giving creatures the ability to make seemingly rational decisions. There’s no way around it: that work needs to be done.

Even so, that’s still a lot of CPU going into it. It might be difficult to make CalculateFoodOptimality faster, but what if we called it less?

 

Creatures call this method whenever they make a new hunger-motivated decision, which is every time they finish off their current food source, or fail to reach it. But this means they’re calling it every time a corpse disappears, every time they finish eating from a tree, and most notably for our purposes, every time they graze off a ground pixel.

Focusing on that last one for now. Creatures don’t need to make a whole new food decision after they pick off every ground pixel: they just need to keep grazing from neighboring ground pixels. So, that’s what they’ll do! They’ll only make a new decision if they reach a ground pixel that doesn’t have any energy for them.

[Insert coding montage set to I Need A Hero by Bonnie Tyler here]

Aaaaand done. How’s our performance now?

PerformanceChart5

Okay, now we’re getting somewhere! Compare the size of CalculateFoodOptimality to the bar to it’s right (“WalkToTarget“, in case you’re wondering). We’re spending a fraction of the time on it, simply by reducing how often it needs to be called during grazing!

There’s still more optimization to be done here, but today was a good day. 0.11.0.7 now has significantly better performance than 0.11.0.6 did: it’s hard to quantify exactly how much, but it should be easily noticeable!

 

I’ve also fixed a number of bugs and unexpected behavior (mostly boring crashes, not really worth a blog post), so the game should be quite a bit more stable, too!

 

I’ve already uploaded 0.11.0.7 to Steam, so if you want to see the end result of all this, load it up. Personally, I’m quite happy with this hotfix.

Cheers,
Qu


 

No sign at all? Do we at least know if we sent them forward or back? No? So what you’re telling me is they could appear at literally any time in the history of earth?

Oh well. Out of sight out of mind, I guess.

Advertisements
  1. #1 by yuzepe on October 2, 2018 - 4:03 am

    Nice post as ever. I like the progression the game takes.
    I don’t know, if this behavior is still active, but as I last played, every creature was just focusing the nearest food source. No reserving. So after the first of them got the food, each creature had to recalculate. And this was happening the whole time. There might be some potential!

  2. #2 by ququasar on October 2, 2018 - 5:46 pm

    You’re correct yuzepe: I’ve seen that behaviour too. It’s actually worse than them just targeting the closest food: all creatures in an area will target the same tree in a forest because it has more energy than the rest.

    I think one of my next tricks will be to reduce the optimality of objects that have already been targeted by a different creature, so that creatures spend less time competing for optimal food sources and instead look for food of their own.

  3. #3 by White Parrot on October 3, 2018 - 11:04 pm

    Ah, empathy. Another case of problems that only seems obvious in hindsight.

    I guess inter-species genetic distance could be obtained from web of life drawing or something, but there is little point, is it?
    So, is empathy now the willingness to treat members of one’s own species/family as any other species (“how open are you to cannibalism?”), or has it been entirely scrapped?

  1. 3 Months on Steam! Past, Present and Future | Species Development Blog

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Google photo

You are commenting using your Google account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: