没有GC的无限方法参数

Per*_*eak 9 c# memory-management variadic-functions unity-game-engine

我正在尝试创建一个函数,可以接收无限量的参数,而无需创建GC.

我知道这可以通过params关键字完成,但它会创建GC.还要明白你可以将数组传递给函数但我想知道是否可以传递无限的方法参数而不创建GC并且不创建数组或列表并将其传递给List.

这是param代码的示例:

void Update()
{
    GameObject player1 = GameObject.Find("Player1");
    GameObject player2 = GameObject.Find("Player2");

    GameObject enemy1 = GameObject.Find("Enemy1");
    GameObject enemy2 = GameObject.Find("Enemy2");
    GameObject enemy3 = GameObject.Find("Enemy3");

    Vector3 newPos = new Vector3(0, 0, 0);
    moveObjects(newPos, 3f, player1, player2, enemy1, enemy2, enemy3);
}

void moveObjects(Vector3 newPos, float duration, params GameObject[] objs)
{
    for (int i = 0; i < objs.Length; i++)
    {
        //StartCoroutine(moveToNewPos(objs[i].transform, newPos, duration));
    }
}
Run Code Online (Sandbox Code Playgroud)

即使StartCoroutine注释掉函数执行,它也会分配80个字节.起初,我认为这是发生的,因为我使用了foreach循环,然后我将其更改为for循环,但它仍然创建GC然后我意识到这params GameObject[]是导致它.请参阅下面的Profiler,了解更多关于此的可视信息:

在此输入图像描述

那么,如何在不生成GC的情况下创建一个无限制参数的方法呢?

请忽略GameObject.Find函数中使用的函数的使用Update.这只是用于获取运行时期间所需对象的引用的示例.我有一个脚本实现来处理,但没有关联这个问题的内容.

Pro*_*mer 8

是的,可以创建一个具有无限参数的函数,而不会导致内存分配.

您可以使用未记录的__arglist关键字并params在其中包含我们的无限制.

改变你moveObjects(newPos, 3f, player1, player2, enemy1, enemy2, enemy3)moveObjects(newPos, 3f, __arglist(player1, player2, enemy1, enemy2, enemy3)).

moveObjects功能中,替换params GameObject[] objs__arglist.将__arglistin ArgIterator然后循环放在它上面直到ArgIterator.GetRemainingCount不再大于0.

要从循环中的参数获取每个值,请使用ArgIterator.GetNextArg获取TypedReference,然后TypedReference.ToObjectobject其转换为GameObject在示例中的参数中传递的Object类型.

整体变化:

void Update()
{
    GameObject player1 = GameObject.Find("Player1");
    GameObject player2 = GameObject.Find("Player2");

    GameObject enemy1 = GameObject.Find("Enemy1");
    GameObject enemy2 = GameObject.Find("Enemy2");
    GameObject enemy3 = GameObject.Find("Enemy3");

    Vector3 newPos = new Vector3(0, 0, 0);
    moveObjects(newPos, 3f, __arglist(player1, player2, enemy1, enemy2, enemy3));
}

void moveObjects(Vector3 newPos, float duration, __arglist)
{
    //Put the arguments in ArgIterator
    ArgIterator argIte = new ArgIterator(__arglist);

    //Iterate through the arguments in ArgIterator
    while (argIte.GetRemainingCount() > 0)
    {
        TypedReference typedReference = argIte.GetNextArg();
        object tempObj = TypedReference.ToObject(typedReference);

        GameObject obj = (GameObject)tempObj;
        //StartCoroutine(moveToNewPos(obj.transform, newPos, duration));
    }
}
Run Code Online (Sandbox Code Playgroud)

虽然这可以解决您的问题,但值得注意的是,这是一个未记录的功能,这意味着它可能有一天会停止工作.如果你关心它,那么应该使用一个数组.

编辑:

John Skeet提到了某些平台可能存在的不兼容问题.我再次进行了测试,它适用于我测试过的所有设备.我在Windows和Android上都进行过测试,它可以在Windows和Android上运行.我也希望它也适用于iOS.太懒了切换到Mac然后摆弄Xcode来测试但是应该没有问题.

请注意,必须使用.NET> = 4.6才能使其正常工作

要做到这一点:

1.转到播放器设置,将脚本运行时版本更改为"实验(.Net 4.6等效)"

2.将 Api兼容级别更改为.NET 4.6.

3 .Change 脚本后端到单声道而不是IL2CPP.不支持IL2CPP,因为Unity没有在其上实现此功能.

  • 请注意,这适用于Unity3d.如果它真的适用于所有Unity环境,包括AOT编译环境,我会感到非常惊讶.它可能* - 但我肯定比使用这样的功能更加谨慎. (2认同)
  • @JonSkeet我会,但我想再次测试,以确保Unity现在使用新的编译器.我会在测试后更新答案. (2认同)

Jon*_*eet 8

如果您使用params该方式指定的参数,然后是的,这始终创建一个数组对象.

如果你想避免这种情况,如果你不需要担心递归或线程安全,你可以保持一个List<GameObject>并多次使用相同的列表.例如:

private readonly List<GameObject> objectListCache = new List<GameObject>();

private void Update()
{
    cachedObjectList.Clear();
    cachedObjectList.Add(GameObject.Find("Player1"));
    cachedObjectList.Add(GameObject.Find("Player2"));
    cachedObjectList.Add(GameObject.Find("Enemy1"));
    cachedObjectList.Add(GameObject.Find("Enemy2"));
    cachedObjectList.Add(GameObject.Find("Enemy3"));

    Vector3 newPos = new Vector3(0, 0, 0);
    moveObjects(newPos, 3f, cachedObjectList);
    cachedObjectList.Clear();
}

void MoveObjects(Vector3 newPos, float duration, List<GameObject> objs)
{
    foreach (GameObject obj in objs)
    {
        // ...
    }
}
Run Code Online (Sandbox Code Playgroud)

清除时List<T>,将内部缓冲区的所有元素设置为null,但不会丢弃缓冲区 - 因此可以在Update没有任何分配的情况下再次用于下一次调用.