SidS
SidS

Reputation: 326

Unity AddComponent Very Inefficient and Slow

I have a dataset of almost 120,000 stars and a test dataset of 215 stars (taken from the main dataset) and I am making a program that reads the dataset and plots stars around a 3D Earth model.

  1. Dataset is read and info are stored in multiple lists (in a singleton script)
  2. Simulation script then reads the lists (Singleton is destroyed to make program more efficient):
void listStars()
{
    int i = 0;
    while (i < (StarDataBank.Instance.NumOfStars))
    {
        int primaryID = int.Parse(StarDataBank.Instance.StarIDID[i]);
        string properName = StarDataBank.Instance.StarName[i];
        string HIPID = StarDataBank.Instance.StarIDHIP[i];
        string HDID = StarDataBank.Instance.StarIDHD[i];
        string HRID = StarDataBank.Instance.StarIDHR[i];
        string GLID = StarDataBank.Instance.StarIDGL[i];
        string BFID = StarDataBank.Instance.StarIDBF[i];
        decimal rightAscension = Convert.ToDecimal(StarDataBank.Instance.StarRA[i]);
        decimal declination = Convert.ToDecimal(StarDataBank.Instance.StarDec[i]);
        decimal Mag;
        decimal CI;
        float scale = 0;
        int r = 0;
        int g = 0;
        int b = 0;

        Decimal.TryParse((StarDataBank.Instance.StarMag[i]), out Mag);
        Decimal.TryParse((StarDataBank.Instance.StarCI[i]), out CI);

        if (PlayerPrefs.GetInt("dynamicSize") == 1)
        {
            if (Mag < -1)
            {
                scale = 77.5f;
            }
            else
            {
                if (Mag > -1 && Mag <= 5)
                {
                    scale = 52.5f;
                }
                else
                {
                    if (Mag > 5 && Mag <= 10)
                    {
                        scale = 32.5f;
                    }
                    else
                    {
                        if (Mag > 10 && Mag <= 15)
                        {
                            scale = 17.5f;
                        }
                        else
                        {
                            if (Mag > 15 && Mag <= 20)
                            {
                                scale = 7.5f;
                            }
                            else
                            {
                                if (Mag > 20 && Mag <= 25)
                                {
                                    scale = 2.5f;
                                }
                            }
                        }
                    }
                }
            }
        } 
        else
        {
            scale = 20;
        }
        StartCoroutine(placeStars(primaryID, properName, HIPID, HDID, HRID, GLID, BFID, rightAscension, declination, Mag, CI, scale));
        i++;
    }
    DestroyImmediate(StarDataBank.Instance.gameObject);
}
  1. Each star is plotted with this method:
    IEnumerator placeStars(int primaryID, string properName, string HIPID, string HDID, string HRID, string GLID, string BFID, decimal rightAscension, decimal declination, decimal magnitude, decimal colourIndex, float scale)
    {
        var thisStar = (GameObject)Instantiate(prefabStar, transform.position + getVectors(Convert.ToDecimal(rightAscension), Convert.ToDecimal(declination)), Quaternion.identity);
        thisStar.name = (primaryID).ToString();
        thisStar.transform.parent = StarObject.transform;
        thisStar.transform.localScale = new Vector3(scale, scale, scale);
        thisStar.AddComponent<Star>().newStar(primaryID, properName, HIPID, HDID, HRID, GLID, BFID, rightAscension, declination, magnitude, colourIndex);
        yield return null;
    }

Using the smaller test dataset with only 215 stars, I am able to attach the Star class to each Instantiated star.

But with the large full dataset with ~120k stars, the Unity Editor just hangs after the play button is pressed. When I comment out this line: thisStar.AddComponent<Star>().newStar(...); The program works as intended, each star is plotted in the scene, the only thing is that the Star class isn't getting attached to each newly instantiated star.

This means that the AddComponent function is not playing well with my code, especially when the large dataset is being used.

Is there a more efficient way of attaching the Star class to each instantiated star GameObject?

Also, does it make a difference when I destroy the singleton after I've finished using it? And does it make a difference that I've used IEnumerator to instantiate?

EDIT: Here's the Star class:

public class Star : MonoBehaviour
{


    Simulation simulationInstance;


    public int primaryID;  // primary key                                      NEEDS TO BE SET
    public string properName;  // some stars have names                        NEEDS TO BE SET
    public string HIPID;   // ID of star from Hipparcos catalogue              NEEDS TO BE SET
    public string HDID;    // ID of star from Henry Draper catalogue           NEEDS TO BE SET
    public string HRID;    // ID of star from Harvard Revised catalogue        NEEDS TO BE SET
    public string GLID;    // ID of star from Gliese catalogue                 NEEDS TO BE SET
    public string BFID;    // ID of star from BayerFlamsteed catalogue         NEEDS TO BE SET
    public decimal rightAscension; // right ascension of star                  NEEDS TO BE SET
    public decimal declination;    // declination of star                      NEEDS TO BE SET
    public decimal magnitude;  // magnitude of the star                        NEEDS TO BE SET
    public decimal colourIndex;    // colour index of the star                 NEEDS TO BE SET
    public int scale;  // size of the sphere that will represent the star      AUTOMATICALLY SET
    public int red;    // red colour (0-255)                                   AUTOMATICALLY SET
    public int green;  // green colour (0-255)                                 AUTOMATICALLY SET  
    public int blue;   // blue colour (0-255)                                  AUTOMATICALLY SET
    public double x;   //                                                      AUTOMATICALLY SET
    public double y;   //                                                      AUTOMATICALLY SET
    public double z;   //                                                      AUTOMATICALLY SET

    void Start()
    {
        simulationInstance = FindObjectOfType<Simulation>();
    }

    public void newStar(int primaryID, string properName, string HIPID, string HDID, string HRID, string GLID, string BFID, decimal rightAscension, decimal declination, decimal magnitude, decimal colourIndex)
    {
        this.primaryID = primaryID;
        this.properName = properName;
        this.HIPID = HIPID;
        this.HDID = HDID;
        this.HRID = HRID;
        this.GLID = GLID;
        this.BFID = BFID;
        this.rightAscension = rightAscension;
        this.declination = declination;
        this.magnitude = magnitude;
        this.colourIndex = colourIndex;

    }

    public void tellStarInfoPanel()
    {
        simulationInstance.starInfoPanelManager(primaryID, properName, HIPID, HDID, HRID, GLID, BFID, rightAscension, declination, magnitude, colourIndex);
    }
}

EDIT: I have made listStars() a coroutine and placeStars() a normal method

    public void placeStars(int primaryID, string properName, string HIPID, string HDID, string HRID, string GLID, string BFID, decimal rightAscension, decimal declination, decimal magnitude, decimal colourIndex, float scale)
    {
        var thisStar = (GameObject)Instantiate(prefabStar, transform.position + getVectors(Convert.ToDecimal(rightAscension), Convert.ToDecimal(declination)), Quaternion.identity);
        thisStar.name = (primaryID).ToString();
        thisStar.transform.parent = StarObject.transform;
        thisStar.transform.localScale = new Vector3(scale, scale, scale);
        thisStar.GetComponent<Star>().newStar(primaryID, properName, HIPID, HDID, HRID, GLID, BFID, rightAscension, declination, magnitude, colourIndex);
        starsRendered++;  
    }

Upvotes: 1

Views: 4180

Answers (4)

mcrvaz
mcrvaz

Reputation: 659

Is there a more efficient way of attaching the Star class to each instantiated star GameObject?

Yes. Your Star prefab should already have the component Star attached. Then you can instantiate it as var thisStar = (Star)Instantiate(prefabStar, transform.position + getVectors(Convert.ToDecimal(rightAscension), Convert.ToDecimal(declination)), Quaternion.identity);, avoiding the AddComponent step.

Also, does it make a difference when I destroy the singleton after I've finished using it?

No. It's not consuming any processing, you will just free some memory.

And does it make a difference that I've used IEnumerator to instantiate?

Only if you want to instantiate the objects over multiple frames, as I've shown below.

Some things you can improve

  1. Instantiate the stars over multiple frames: For this you must turn your listStarsmethod into a Coroutine, making the return type an IEnumerator and using yield return placeStars(primaryID, properName, HIPID, HDID, HRID, GLID, BFID, rightAscension, declination, Mag, CI, scale). You might want to instantiate multiple stars on the same frame, or it might take some time!
  2. Your Star Start method becomes increasingly expensive at every star instantiated, it's better to pass it as an argument to the placeStars method, acquiring it only once before the loop stars.
  3. You don't need to get dynamicSize from PlayerPrefs at every iteration. Store it before the loop starts. The same goes for the singleton instance. Cache it before the loop starts and use the cached reference.

Pseudo code example:

IEnumerator listStars()
{
    var starDataBank = StarDataBank.Instance;
    var dynamicSize = PlayerPrefs.GetInt("dynamicSize");
    var simulationInstance = FindObjectOfType<Simulation>();
    var starsPerFrame = 10;
    for (int i = 0; i < starDataBank.NumOfStars; i++)
    {
        // do your stuff
        for (int j = 0; j < starsPerFrame; j++)
            placeStars(simulationInstance, primaryID, properName, HIPID, HDID, HRID, GLID, BFID, rightAscension, declination, Mag, CI, scale));
        yield return null;
    }
    DestroyImmediate(starDataBank.gameObject);
}

Upvotes: 1

Andrew Łukasik
Andrew Łukasik

Reputation: 1637

You need to know that this is not a good use case for Monobehaviour/Component OOP model. You may want consider switching to preview ECS tech stack for this. Not long time ago I tested similar case and found out that instancing 100k entities from csv file is doable. In this situation my HDD I/O was the bottleneck and not CPU/memory (opposite of what happens with Monobehaviours). What's more - loading time can be optimized by pre-caching your raw data as memory segments serialized to binary files. But I won sugar-coat it - it's much more advanced programming than Monobehaviours and this tech stack is in unstable pre-release state at the moment.

Upvotes: 0

Eric
Eric

Reputation: 1775

If you think AddComponent() is computationally too heavy, it would be wise to have a closer look at Start component or even better running a Unity's profiler tool which should exactly pinpoint the culprit.

Also do your Star has to be a Component? Maybe you can go around this problem by simply utilising OO techniques.

Upvotes: 0

Hamid Yusifli
Hamid Yusifli

Reputation: 10137

Avoid usage of AddComponent in performance critical code

Calling GameObject.AddComponent is expensive, especially inside a performance critical context. Each time a component is added, the following must happen:

  1. Finding the component's script in the script cache, by name. This might also incur allocations if it's not already cached.
  2. Allocating the memory for the MonoBehaviour.
  3. Notifying other attached components that a new component has been added. The attached components can perform actions when a known component is added. The amount of work performed here depends on the number and type of attached components. E.g. a rigid body needs to know if a collider is added.
  4. Running the new component's Awake method. This inspection will highlight calls to AddComponent inside a performance critical context. It will also mark the calling method as expensive, and any usages of the calling method will also receive a performance indicator highlight.

Upvotes: 0

Related Questions