﻿# PVS\-Studio helps optimize Unity Engine projects

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\.

![1071_Unity_optimizations/image1.png](https://import.viva64.com/docx/blog/1071_Unity_optimizations/image1.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\.

<details>
   <summary>How to enable the diagnostic rules?</summary>

The diagnostic rules are located in the Optimization group\. You can enable and disable them in the settings\. By default, the diagnostic rules of this group are **enabled**\. Please note that the diagnostic rules described here can be applied only to Unity Engine projects \(you'll learn why in the next section\)\.


</details>
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:

```cpp
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?

<details>
   <summary>Here's the answer:</summary>

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\.


</details>


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:

```cpp
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:

```cpp
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\.

<details>
   <summary>Isn't such code optimized automatically?</summary>

Judging by my experiments, it isn't\. First, I looked at the IL — you can clearly see the 'box' commands there\. Then I decided to try it at runtime — what if it's a JIT optimization?

I used the profiler built into Visual Studio to see if there was any difference between using _ToString_ and not using it\. After forcing a simple application to call _string\.Format_ a certain number of times, I saw that the number of allocations is **much lower** **when using** _ToString_\. From this we can conclude that calling _ToString_ on _string\.Format_ arguments definitely makes sense \(for value types, of course\)\.


</details>


_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:

```cpp
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_:

```cpp
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/)\.

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\!