CPU 100%

I am trying to make a traffic simulator.
I created a small city, with 300 nodes ( four roads ). I generated 200 cars and it works fine.
This is the small city:
4378252--397237--upload_2019-3-31_23-56-35.png

After this i created a bigger city with 4000 nodes, if I generate one car, the CPU is 92-100% all time. For the current node and the next node from path i have O(1).
I think the problem is that i have attached a script to every node. So, 4000 of same script.

Script for Node:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;
using UnityEditor;

[ExecuteInEditMode]
public class Waypoint : MonoBehaviour
{

    [SerializeField]
    [HideInInspector]
    // Parent graph of the waypoint
    protected WaypointCluster parent;

    // The outgoing list of edges
    public List<WaypointPercent> outs = new List<WaypointPercent>();

    // The distance between waypoints
    public List<WaypointDistance> nodes = new List<WaypointDistance>();


    [HideInInspector]
    // Incoming list of edges, hidden in the inspector
    public List<Waypoint> ins = new List<Waypoint>();

    // Main node color
    Color color = Color.white;

    public void setParent(WaypointCluster wc)
    {
        parent = wc;
    }

    public WaypointCluster getParent()
    {
        return parent;
    }

    // Returns a random waypoint based on the probabilty defined in the WaypointPercent class
    public Waypoint getNextWaypoint()
    {
        int prob = Random.Range(0, 100);
        int sum = 0;
        for (int i = 0; i < outs.Count; ++i)
        {
            sum += outs[i].probability;
            if (prob <= sum)
                return outs[i].waypoint;
        }
        Debug.LogError("Last waypoint was returned on waypoint " + this.name + ". Check that the probabilities correctly sum at least 100 on that waypoint.");
        setSame();
        Debug.Log(this.name + " Probabilities has been corrected");
        return outs[outs.Count - 1].waypoint;
    }

    public bool CheckProbabilities()
    {
        int sum = 0;
        for (int i = 0; i < outs.Count; ++i)
        {
            sum += outs[i].probability;
        }
        if (sum != 100)
            return false;
        else
            return true;
    }

    public void setSame()
    {
        int size = outs.Count;
        for (int i = 0; i < size; ++i)
        {
            outs[i].probability = 100 / size;
            if (i < 100 % size)
                outs[i].probability++;
        }
    }

    public void linkTo(Waypoint waypoint)
    {
        if (waypoint == this)
        {
            Debug.LogError("A waypoint cannot be linked to itself");
            return;
        }
        for (int i = 0; i < outs.Count; ++i)
            if (waypoint == outs[i].waypoint)
                return;

        if (waypoint.ins.Contains(this))
            return;

        outs.Add(new WaypointPercent(waypoint));
        float distance = Vector3.Distance(transform.position, waypoint.transform.position);
        waypoint.ins.Add(this);
        setSame();
        //parent.CreateWaypoint(Vector3.zero);
    }

    public void unlinkFrom(Waypoint waypoint)
    {
        for (int i = 0; i < outs.Count; ++i)
            if (outs[i].waypoint == waypoint)
                outs.RemoveAt(i);
        waypoint.ins.Remove(this);
    }

    public void setColor(Color color)
    {
        this.color = color;
    }

    private static void ForGizmo(Vector3 pos, Vector3 direction, Color c, float arrowHeadLength = 0.25f, float arrowHeadAngle = 20.0f)
    {
        Gizmos.color = c;
        Gizmos.DrawRay(pos, direction);
        Vector3 right = Quaternion.LookRotation(direction) * Quaternion.Euler(0, 180 + arrowHeadAngle, 0) * new Vector3(0, 0, 1);
        Vector3 left = Quaternion.LookRotation(direction) * Quaternion.Euler(0, 180 - arrowHeadAngle, 0) * new Vector3(0, 0, 1);
        Gizmos.DrawRay(pos + direction, right * 0.5f); // Left line of head
        Gizmos.DrawRay(pos + direction, left * 0.5f);  // Righ line of head
    }

    public virtual void OnDrawGizmos()
    {
        Gizmos.color = color;
        Gizmos.DrawCube(transform.position, new Vector3(0.5f, 0.5f, 0.5f));
        for (int i = 0; i < outs.Count; ++i)
        {
            Vector3 direction = outs[i].waypoint.transform.position - transform.position;
            ForGizmo(transform.position + direction.normalized, direction - direction.normalized * 1.5f, Color.green, 2f);
        }

        if (color.Equals(Color.green) || color.Equals(Color.white))
            color = Color.white;
    }

    public virtual void OnDrawGizmosSelected()
    {
        Gizmos.color = Color.yellow;
        Gizmos.DrawCube(transform.position, new Vector3(0.5f, 0.5f, 0.5f));
        for (int i = 0; i < outs.Count; ++i)
        {
            Vector3 direction = outs[i].waypoint.transform.position - transform.position;
            ForGizmo(transform.position + direction.normalized, direction - direction.normalized * 1.5f, Color.white, 3f);
        }
    }

    [ExecuteInEditMode]
    private void OnDestroy()
    {
        if (parent == null) return;
        for (int i = 0; i < outs.Count; ++i) Undo.RegisterCompleteObjectUndo(outs[i].waypoint, "destroyed");
        for (int i = 0; i < ins.Count; ++i) Undo.RegisterCompleteObjectUndo(ins[i], "destroyed");
        Undo.RegisterCompleteObjectUndo(this.getParent(), "destroyed");
        for (int i = outs.Count - 1; i >= 0; --i) this.unlinkFrom(outs[i].waypoint);
        for (int i = ins.Count - 1; i >= 0; --i) ins[i].unlinkFrom(this);
        Undo.RegisterCompleteObjectUndo(this, "destroyed");
        this.getParent().waypoints.Remove(this);
    }

}

[System.Serializable]
public class WaypointPercent
{
    [Range(0, 100)]

    public int probability = 0;
    [ReadOnly]

    public Waypoint waypoint;

    public WaypointPercent(Waypoint waypoint)
    {
        this.waypoint = waypoint;
    }

}

[System.Serializable]
public class WaypointDistance
{
    public Waypoint Point;
    public float Distance;

   public WaypointDistance(Waypoint point, float distance)
    {
        Point = point;
        Distance = distance;
    }
}

Every car has this function, it is on Update method.

  private void SetNodes()
    {
        _previousNode = _currentNode;
        _currentNode = _nextNode;
        //Debug.Log(_currentNode.outs.Count);       
        if ((_currentNode.outs.Count == 0) && (_distance < 1))
        {
            Debug.Log("Destrory");
            Destroy();

        }
        else if (_currentNode.outs.Count != 0)
        {
            _nextNode = _currentNode.getNextWaypoint();
            if (_nextNode.outs.Count != 0)
                _nextNextNode = _nextNode.getNextWaypoint();
        }

    }

I don’t have time to really dig into this, but it looks like ECS would be worth a look for a possible solution.

Might be a great option especially given that you may want to scale up this even further?

Philosophically, ask yourself if you have processes that you need to run every single moment, or do you want them to run only occasionally, or when needed?

For example, when you have a process in Update, it will run every frame. But maybe you only need to run the process 10 times a second? Or twice per second? Or … only when a certain event occurs (other than a clock tick)?

1 Like

You have two routes to make it more performant:

  • go ECS (it will have its own problems at the moment)
  • reduce the Unity callbacks (use only one Update and distribute the update calls yourself, and so on) → reduce the OOP you’re using by handling things in batches, not individually, plus what @hopeful said, create tasks, don’t update everything all the time

And try to find out what causes your high CPU load and start with those (use the profilers)

1 Like

That means i have to rewrite the entire code? My project is huge and i have no more time

Use the profiler to figure out what’s eating all your time. If I were to guess, I’d say that it’s 4000 calls to OnDrawGizmos every frame, but I’m not sure about that. The profiler will show you what’s going on.

Finally, don’t follow the ECS advice, it’s an experimental API, not production ready, and for sure not easy to use (yet). Instead, figure out old-fashioned ways of optimizing this. 4000 nodes shouldn’t be a problem, but each node should probably not have a script attached to it, nor really have a GameObject.

2 Likes

Is first time when i hear about Profiler, i am trying to figure ouy how it works.

here.

1 Like

I used gameobject to create the routes.
After i created them, should i save all nodes in a static array and disable all gameobjects?

No.

https://www.youtube.com/results?search_query=unity3d+how+to+profile

BTW, judging the screenshot you provided, you have a ton of active animations. You probably will need to LOD them.
Try to establish a LODs and turn off the animations when things are farther away (You can’t see them from the perspective) and see how the profiler numbers change. Then rinse and repeat.

But to be sure you will need to investigate properly. See the tutorials above.

I found first problem.
When i want to calculate the shortest car, for my car, I have low fps for 3.2 seconds.

This is my code:

IEnumerator CalculatePath(Waypoint start, Waypoint end)
    {
        // The final path
        Path = new List<Waypoint>();

        // The list of unvisited nodes
        List<Waypoint> unvisited = new List<Waypoint>();

        // Previous nodes in optimal path from source
        Dictionary<Waypoint, Waypoint> previous = new Dictionary<Waypoint, Waypoint>();

        // The calculated distances, set all to Infinity at start, except the start Node
        Dictionary<Waypoint, float> distances = new Dictionary<Waypoint, float>();

        for (int i = 0; i < Graph.Count; i++)
        {
            //if (_yield.YieldNow)
            //    yield return null;

            Waypoint node = new Waypoint();
            node = Graph[i];
            unvisited.Add(node);

            // Setting the node distance to Infinity
            distances.Add(node, float.MaxValue);
        }

        // Set the starting Node distance to zero
        distances[start] = 0f;

        while (unvisited.Count != 0)
        {
            //if (_yield.YieldNow)
                yield return null;
           
            // Ordering the unvisited list by distance, smallest distance at start and largest at end
            unvisited = unvisited.OrderBy(node => distances[node]).ToList();

            // Getting the Node with smallest distance
            Waypoint current = unvisited[0];

            // Remove the current node from unvisisted list
            unvisited.Remove(current);

            // When the current node is equal to the end node, then we can break and return the path
            if (current == end)
            {
                // Construct the shortest path
                while (previous.ContainsKey(current))
                {
                    //if (_yield.YieldNow)
                    //    yield return null;
                    // Insert the node onto the final result
                    Path.Insert(0, current);

                    // Traverse from start to end
                    current = previous[current];
                }

                // Insert the source onto the final result
                Path.Insert(0, current);
                break;
            }

            // Looping through the Node connections (neighbors) and where the connection (neighbor) is available at unvisited list
            for (int i = 0; i < current.nodes.Count; i++)
            {
                //if (_yield.YieldNow)
                //    yield return null;
                Waypoint neighbor = current.nodes[i].waypoint;

                // Getting the distance between the current node and the connection (neighbor)
                float length = current.nodes[i].Distance;

                // The distance from start node to this connection (neighbor) of current node
                float fullLength = distances[current] + length;

                // A shorter path to the connection (neighbor) has been found
                if (fullLength < distances[neighbor])
                {
                    distances[neighbor] = fullLength;
                    previous[neighbor] = current;
                }
            }
        }
        _calculating = false;
    }

All of the time is spent in LogStringToConsole, aka. Debug.Log.

Debug.Log is really slow. It has to rewind the entire stack to generate a stack trace, generate a string from that, and push it to the console. If you have Debug.Log calls in loops that run thousands of time, it will absolutely destroy your framerate.

Take out all of the Debug.Log calls and try again.

3 Likes

not only that your code is not multithreaded where it can be. fyi multithreading is possible in unity it just doesnt allow any other threads to handle unity objects. but calculations that has nothing to do with unity objects can be done

I tried what you say for finding path, but it’s not working :(.
Here is my post: [SOLVED] Path Finding with Thread. - Unity Engine - Unity Discussions

When you have fixed the biggest perfomance problems look into allocation. You allocate alot in that CalculatePath method. A healthy game code should allocate zero in its main game loop