PVS-Studio helps optimize Unity Engine projects
Tags: gamedev, csharp, unity3d, programming
Published: 2023-10-17 11:33:39

With the recent update, the PVS-Studio analyzer can issue warnings to possible code optimization in Unity Engine projects. If you are wondering what kind of warnings the analyzer issues, how it understands what code to optimize, and why this is done specifically for Unity Engine, I invite you to read this article.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4rsdlqfn2bwm1wt2w5gj.png)

**What does the analyzer have to offer?**
Currently, PVS-Studio has 4 diagnostic rules that show how to optimize the code of Unity Engine projects:

- [V4001](https://pvs-studio.com/en/docs/warnings/v4001/)indicates the code fragments in which [boxing ](https://pvs-studio.com/en/blog/terms/6695/)is performed;
- [V4002](https://pvs-studio.com/en/docs/warnings/v4002/)finds expressions where it's better to replace string concatenations with StringBuilder;
- [V4003](https://pvs-studio.com/en/docs/warnings/v4003/)detects where the variable capture by an anonymous function can be avoided;
- [V4004](https://pvs-studio.com/en/docs/warnings/v4004/)shows that the code can be potentially optimized by reducing the use of "heavy" properties that create new collections each time they are accessed.

These seemingly simple diagnostic rules are based on the [official recommendations](https://docs.unity3d.com/Manual/performance-garbage-collection-best-practices.html) from the Unity Engine documentation.

The main feature of these diagnostic rules is that they issue warnings only to code that is likely to be executed frequently. Certainly, it's useful to optimize such code.

**Note**
_Of course, we could create diagnostic rules that would issue warnings in all cases of boxing or a variable capture. However, in this case, the PVS-Studio users would have to deal with a lot of warnings. Moreover, most of them wouldn't be very interesting._

_That's why we try to minimize the number of warnings that point to code fragments where optimization will not deliver tangible benefits._

**What kind of code is executed frequently?**
It seems simple at first glance. Unity Engine projects contain many special methods that are called frequently (e.g. Update, UpdateFixed, and others). First, PVS-Studio checks the code of these methods for possible optimizations.

However, these "primary" frequently called methods may contain different calls. Let's look at the example:

```
class Test : MonoBehaviour
{
  struct ValueStruct { int a; int b; }

  ValueStruct _previousValue;

  void Update()
  {
    ValueStruct newValue = ....
    
    if (CheckValue(newValue))
      ....
  }

  bool CheckValue(ValueStruct value)
  {
    if(_previousValue.Equals(value))
      ....
  }
}
```
The code in the Update method is executed every frame, so there are several relevant optimizations. However, there is nothing in the Update method itself that needs to be optimized — there is just a normal assignment and method call. On the other hand, it's obvious that the code of the CheckValue method is executed just as often as the Update method.

So, it would also be useful to optimize the CheckValue method. What exactly can we optimize here?

_The Equals method called on _previousValue takes an object type as an argument. Therefore, [boxing ](https://pvs-studio.com/en/blog/terms/6695/)is done when value is passed. To avoid boxing, just add the Equals method, which takes the ValueStruct type as an argument, to the definition of the ValueStruct structure._

By analyzing calls, PVS-Studio understands what code needs to be optimized. For the previous example, the [V4001 ](https://pvs-studio.com/en/docs/warnings/v4001/)diagnostic rule would have issued a warning indicating that, in the _Update_ method, there is the _CheckValue_ call where boxing is performed:

[V4001](https://pvs-studio.com/en/docs/warnings/v4001/). The frequently called 'Update' method contains the 'CheckValue(newValue)' call which performs boxing. This may decrease performance.

The message may not tell exactly where boxing occurred. However, the message includes information about all line numbers and file paths to which the analyzer issued the warning. For the example above, these are:

- The line where the corresponding Update method is declared;
- The line where CheckValue is called;
- The line where the boxing occurs, i.e., the code where Equals is called.

Tools for viewing the analyzer reports (for example, plugins for Visual Studio, VS Code, or Rider) allow to easily jump to the code fragments that the warning is telling about. This helps understand exactly where boxing (or any other operation) that can be optimized is taking place.

**Analysis depth**

_After reading the previous section, you may wonder: "What if the code that needs optimization is deeper?"_

_For example, in the Update method, the Foo method can be called, within which the Foo2 method can be called, within which the Foo3 method can be called (and so on). And then in some FooN of this call chain, boxing is performed, for example._

_In this case, the analyzer will also issue a warning about the possibility of optimization. The call depth is not relevant for PVS-Studio. The only important thing is that the code should be directly or indirectly linked to the Update method or something similar._

**When is it better not to issue warnings?**

In the last section, we talked about methods like _Update_that are called very often. Can we conclude from the example that all the code in the method will be frequently executed?

Of course, we can't. The code almost always contains branches and loops. As a result, some fragments will be executed more often (or less often) than others. The analyzer tries to take this into account, but usually it's impossible to predict how often a condition will have the true value. However, there are some patterns where PVS-Studio clearly sees code that is rarely executed.

For example, the code that can be executed only when a button is pressed (i.e., when _Input.GetKeyDown or GUI.Button_ returns _true_). Most likely, optimizations in such code won't yield much results. Of course, there may be exceptions to this rule, but the analyzer should still focus on the general case.

Another case is when the code performs an initialization that is done once (or at least rarely). Here's the example:

```
class Test : MonoBehaviour
{
  private bool _initialized;
  
  void Update()
  {
    if (!_initialized)
    {
      Initialize();
      _initialized = true;
    }
  }
}
```

In this example, you can see that _Initialize_ is called only if the field is set to _false_. Immediately after the call, the field is set to _true_. It's reasonable to assume that the _Initialize_ method won't be executed on subsequent _Update_ calls. Therefore, micro-optimizations within it are unlikely to yield noticeable results. So, there will be no warnings concerning performance within _Initialize_.

There are other cases when the analyzer avoids issuing warnings. PVS-Studio tries to show only fragments that **can and should be** optimized.

For example, the [V4002](https://pvs-studio.com/en/docs/warnings/v4002/)diagnostic rule indicates that you can use StringBuilder instead of string concatenation. However, it doesn't issue warnings to every concatenation. Instead, the diagnostic rule tracks instances where strings are added to the same variable multiple times.

**Examples from real projects**
As usual, we tested the diagnostic rules on various open-source projects and enhanced the analyzer based on the outcome we got. As a result, we seem to have reached the point where the analyzer gives good, unobtrusive tips on how to micro-optimize various projects.

For example, in the [Daggerfall](https://github.com/Interkarma/daggerfall-unity) project, the [V4001](https://pvs-studio.com/en/docs/warnings/v4001/)diagnostic rule found several cases of boxing when the _string.Format_ method is called. One of them is shown below:

```
public static string GetTerrainName(int mapPixelX, int mapPixelY)
{
  return string.Format("DaggerfallTerrain [{0},{1}]",
                       mapPixelX,
                       mapPixelY);
}
```
The _string.Format_ overload, which has the _string.Format(string, object, object)_ signature, is called here. That is, the call will result in boxing, which can negatively affect performance. However, it's easy to get rid of boxing. Just call the _ToString_ method on the _mapPixelX_ and _mapPixelY_ variables.

_GetTerrainName_ is called indirectly from the Update method of the [_StreamingWorld_ ](https://github.com/Interkarma/daggerfall-unity/blob/2650483567df57e9a8410c082d971a95d9059f97/Assets/Scripts/Terrain/StreamingWorld.cs#L38)class. It's hard to say if _GetTerrainName_ gets called often, but the fragment is worth noting.

Another example of suggested micro-optimizations are the [V4003](https://pvs-studio.com/en/docs/warnings/v4003/) warnings about variable capture in the [jyx2](https://github.com/jynew/jynew)project:

```
public BattleBlockData GetBlockData(int xindex, int yindex)
{
  return _battleBlocks.FirstOrDefault(x =>    x.BattlePos.X == xindex
                                           && x.BattlePos.Y == yindex);
}

public BattleBlockData GetRangelockData(int xindex, int yindex)
{
  return _rangeLayerBlocks.FirstOrDefault(x =>    x.BattlePos.X == xindex
                                               && x.BattlePos.Y == yindex);
}
```
The anonymous functions used in these methods capture the _xindex_ and _yindex_ variables. Thus, each call creates an additional object, which can be easily avoided by rewriting the _FirstOrDefault_ calls to foreach.

And in the [hogwarts](https://github.com/OpenHogwarts/hogwarts) project, the [V4002](https://pvs-studio.com/en/docs/warnings/v4002/) diagnostic rule found a good place to use _StringBuilder_:

```
private void OnGUI()
{
  if (!this.pView.isMine)
  {
    return;
  }

  string subscribedAndActiveCells = "Inside cells:\n";
  string subscribedCells = "Subscribed cells:\n";

  for (int index = 0; index < this.activeCells.Count; ++index)
  {
    if (index <= this.cullArea.NumberOfSubdivisions)
    {
      subscribedAndActiveCells += this.activeCells[index] + " | ";
    }

    subscribedCells += this.activeCells[index] + " | ";
  }
  ....
}
```
The analyzer issued more warnings for this and other projects. However, I think that what I've shown so far is enough for a first demonstration. If you are curious to see what optimization tips PVS-Studio can give you for other Unity Engine projects (for example, yours), you can download the analyzer for free [here](https://pvs-studio.com/en/pvs-studio/try-free-a/?utm_source=website&utm_medium=pvs&utm_campaign=article&utm_content=1071).

It's also worth mentioning that we're looking for ideas for new diagnostic rules. If you have any thoughts on what would be useful to check with the analyzer, please leave your comments :).

Thank you for reading and good luck!