【问题标题】:Unity AddComponent Very Inefficient and Slowunity AddComponent 非常低效和缓慢
【发布时间】:2020-06-07 00:11:33
【问题描述】:

我有一个包含近 120,000 颗星星的数据集和一个包含 215 颗星星的测试数据集(取自主数据集),我正在编写一个程序来读取数据集并在 3D 地球模型周围绘制星星。

  1. 读取数据集并将信息存储在多个列表中(在单例脚本中)
  2. 然后模拟脚本读取列表(销毁单例以提高程序效率):
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. 每颗星都是用这种方法绘制的:
    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;
    }

使用只有 215 颗星的较小测试数据集,我能够将 Star 类附加到每个实例化的星。

但是对于拥有约 120k 星的大型完整数据集,Unity 编辑器只会在按下播放按钮后挂起。当我注释掉这一行时:thisStar.AddComponent&lt;Star&gt;().newStar(...); 程序按预期工作,每个星星都绘制在场景中,唯一的问题是 Star 类没有附加到每个新实例化的星星上。

这意味着 AddComponent 函数不能很好地与我的代码配合使用,尤其是在使用大型数据集时。

有没有更有效的方法将 Star 类附加到每个实例化的 Star GameObject?

另外,在我用完单例后销毁它有什么不同吗?我使用 IEnumerator 实例化有什么不同吗?

编辑:这是 Star 类:

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);
    }
}

编辑:我将 listStars() 设为协程,将 placeStars() 设为普通方法

    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++;  
    }

【问题讨论】:

  • 在这里使用协程没有任何附加价值,因为其中的所有代码都会同时运行,然后它会停顿一帧并继续前进。所以你可以使用普通的 void 方法。如果您的程序趋于变慢,也许您可​​以考虑只加载相关的星星,例如可见的星星或接近的星星。随着时间的推移,您可以使用协同程序来创建它们,因此不是在一次运行中创建它们,而是创建前 200 个,然后 yield return,下一个 200,依此类推。这将需要几秒钟,但您可以同时使用该应用程序。
  • 所以我会首先运行并创建 200 个在一定半径内的星星,那些未创建的星星放在一个集合中。然后接下来的 200 以此类推,直到初始为空。完成后,使用相同的方法,但使用 far 集合。您的方法看起来像 IEnumerator CreateStars(IEnumerable initial, IEnumerable nextBatch, float radius); initial 是要运行的集合, nextBatch 是放置远项目的集合。每次,nextBatch 都成为初始值。运行这个直到 initial 为空。
  • FindObjectOfType&lt;&gt;() 也很重,消除它。这看起来像是 ECS 的完美用例,我会按照 Andrew 的建议进行研究。

标签: c# class unity3d instantiation large-data


【解决方案1】:

如果您认为 AddComponent() 的计算量太大,最好仔细查看 Start 组件,或者更好地运行 Unity 的分析器工具,该工具可以准确找出罪魁祸首。

你的星也必须是一个组件吗?也许您可以通过简单地利用 OO 技术来解决这个问题。

【讨论】:

  • 不幸的是,所有 Unity 都挂了,我认为分析器不起作用。除非探查器不与 Unity 挂起。请展开 Start 组件。
  • 另外,我正在运行 Unity 2019.2.12f1。您认为更新版本会提高性能吗?
  • @SidS 将您的数据集缩小以进行测试,然后运行分析器,只需外推
  • 请详细介绍 OO 技术。我认为为每个星添加一个类是面向对象的。
【解决方案2】:

避免在performance critical code 中使用AddComponent

调用GameObject.AddComponent 的成本很高,尤其是在performance critical context 内部。每次添加组件时,必须发生以下情况:

  1. 在脚本缓存中按名称查找组件的脚本。如果尚未缓存,这也可能会导致分配。
  2. 为 MonoBehaviour 分配内存。
  3. 通知其他附加组件已添加新组件。附加的组件可以在已知的情况下执行操作 添加了组件。这里执行的工作量取决于 附加组件的数量和类型。例如。刚体需要 知道是否添加了对撞机。
  4. 运行新组件的唤醒方法。此检查将突出显示在性能关键上下文中对 AddComponent 的调用。 它还将调用方法标记为昂贵,以及 调用方法也会收到一个性能指标 突出显示。

【讨论】:

  • 你能建议另一种方法让我将一个类附加到一个新实例化的游戏对象吗?
  • Star组件有什么作用?
  • 查看原始问题编辑以获取更多详细信息,但简而言之,Star 类只获取每个星星的详细信息。详细信息,例如不同的 ID、幅度、位置等...
  • @Sids ,根据设计,如果它在游戏中不是工具,为什么你只是用你想要的所有东西制作一个预制件并实例化预制件?
  • @VectorX,我的完整数据集中有大约 120,000 颗星星,为每颗星星制作预制件是不可能的。
【解决方案3】:

您需要知道这对于 Monobehaviour/Component OOP 模型来说不是一个好的用例。为此,您可能需要考虑切换到 preview ECS tech stack 。 不久前,我测试了类似的案例,发现从 csv 文件中实例化 100k 个实体是可行的。在这种情况下,我的 HDD I/O 是瓶颈,而不是 CPU/内存(与 Monobehaviours 发生的情况相反)。更重要的是 - 通过将原始数据预缓存为内存段serialized to binary files,可以优化加载时间。 但我赢得了糖衣——它比 Monobehaviours 更高级的编程,而且这个技术堆栈目前处于不稳定的预发布状态。

【讨论】:

  • 我从未使用过 ECS,如果您能将我链接到指南或发送解释 ESC 用法的 sn-p 代码,那就太好了。
  • 一般的想法是为每个(结构)数据类型在大数组中布局所有数据,并尽可能远离托管(类/对象)。 ECS 以使程序员的过程复杂化/混淆为代价来帮助解决这个问题。
  • 但是您可以采取非常相似的方法,而无需所有这些。首先 - 不要将 GameObjects 用于需要成千上万次复制的东西,永远。如果您需要绘制 >1e3 颗星,则创建一个矩阵数组并使用Graphics.DrawMeshInstanced 每帧渲染所有这些(!),完成 - 甚至需要 0 个实例化。
  • 这种思路意味着将您的 placeStars() 方法重新设计为面向数组的方法,例如 THIS
  • HERE 是如何渲染它们的示例。
【解决方案4】:

有没有更有效的方法将 Star 类附加到每个实例化的 Star GameObject?

是的。您的 Star 预制件应该已经有组件 Star 随附的。然后你可以将它实例化为var thisStar = (Star)Instantiate(prefabStar, transform.position + getVectors(Convert.ToDecimal(rightAscension), Convert.ToDecimal(declination)), Quaternion.identity);,避免 添加组件步骤。

另外,在我用完单例后销毁它有什么不同吗?

没有。它不消耗任何处理,您只会释放一些内存。

我使用 IEnumerator 实例化有什么不同吗?

仅当您想在多个帧上实例化对象时,如 我已经在下面展示了。

一些你可以改进的地方

  1. 在多个帧上实例化星星:为此,您必须将 listStars 方法转换为协程,使返回类型为 IEnumerator 并使用 yield return placeStars(primaryID, properName, HIPID, HDID, HRID, GLID, BFID, rightAscension, declination, Mag, CI, scale)。您可能希望在同一帧上实例化多个星星,或者可能需要一些时间!
  2. 您的 Star Start 方法在每一个 Star 实例化时变得越来越昂贵,最好将它作为参数传递给 placeStars 方法,在循环启动之前只获取一次。
  3. 您不需要在每次迭代时都从PlayerPrefs 获取dynamicSize。在循环开始之前存储它。单例实例也是如此。在循环开始之前缓存它并使用缓存的引用。

伪代码示例:

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);
}

【讨论】:

  • 非常感谢,使用 IEnumerator 和 theStars 方法,现在 Unity 不再挂起。我还可以看到星星被一一实例化。快速注意,放置星星的 for 循环,当每帧的星星增加时,相同的星星被实例化。有没有办法同时实例化多个游戏对象?
  • 这很奇怪,如果可能的话,在这里发布您的代码。可能您正在返回 placeStars 例程。这将使每帧只实例化一颗星。将placeStars 转换为普通方法会更有意义,返回 void。
  • 当使用上面的代码时,循环外循环内名为 i 的变量显然不能在该范围内声明。
  • 抱歉,内部循环应该使用另一个变量名,比如 j。我会为你更新代码。
  • 谢谢,但是如果我将 starsPerFrame 设置为 2 之类的值,那不就是实例化 2 个相同的星星吗?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-10-24
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-11-11
相关资源
最近更新 更多