【问题标题】:Storing a large object in memory or only a pointer to it in C#?将大对象存储在内存中还是仅在 C# 中存储指向它的指针?
【发布时间】:2017-04-29 14:41:59
【问题描述】:
struct Tile
{
    GameObject go;
    ... //more fields

    public Tile(GameObject g, Object s)
    {
        go = g;
        ...
    }
}
Tile[,] tilemap = new Tile[1000,1000]

Tile 结构由一个 GameObject(一个 Unity Engine 游戏内对象,已经在场景/内存中)和其他字段组成。考虑到我要存储一百万个,它可能会占用大量内存。

我对 C# 中的指针没有太多经验。

将指向游戏对象的指针存储在结构中是否明智?

struct Tile
{
    GameObject* go;
    ...
}

【问题讨论】:

  • 如果你在设计大的类,使用class而不是struct,并使用new创建类的对象,这样对象就创建在大堆上而不是小堆上堆栈。

标签: c# arrays pointers unity3d memory-management


【解决方案1】:

在 C# 中,引用已经是一个指针(在底层)。它不是某个对象的副本。在您的第一个代码中,sn-p“go”实际上是一个指向实际对象的指针。你在担心一些不真实的事情。

ref(例如“go”)和不安全指针之间的区别之一是 ref 可以移动,而 GC 可以并且将(当它感觉到时)移动对象(并自动更改 refs)。

所以这里没有问题,只是一种不正确的看法。

【讨论】:

  • 也许没什么。 @Hugh 是正确的,引用是一个指针,@Graeme 给出的答案是不正确的,暗示复制结构会导致 GameObject 的新副本。但是,我会质疑使用struct 而不是将Tile 声明为class 的决定。正如 Eric Lippert 在 stackoverflow.com/a/9315211/1134217 中指出的那样,结构的真正用例很少而且相差甚远,因此可能会因此而影响性能。
  • 正如@DanielHume 所指出的,为了清楚起见,我已经更新了我的答案。我并不是说要制作 GameObject 的副本,而是要制作 struct 元素的副本,只是措辞不是很好。
【解决方案2】:

您不能将类用作指针。也许这在最新的 C# 中有所改变,但我不确定。那根本就行不通。任何引用类型都将使用指针。

intfloatbool等简单类型都可以作为指针使用。

即使在Tile 结构体中声明了GameObject go 变量,也不行。

不建议在结构中声明引用类型,尤其是在游戏应用程序中,因为这会使垃圾收集器不必要地扫描结构中要销毁的对象,从而减慢速度。当您拥有数百万个内部带有引用对象的结构时,情况会更糟。


我的建议是使用 integer 而不是 GameObject *。该整数应该使用 GameObject 的 instanceID 而不是 GameObject 引用或指针进行初始化。

然后您可以使用DictionaryinstanceID(int) 映射到GameObject。当您想要访问 GameObject 时,请提供存储在 Tile 结构中的 intanceID。这将加快一切。

示例:

struct Tile
{
    int goID;

    public Tile(int goID)
    {
        this.goID = goID;
    }
}

用法:

Dictionary<int, GameObject> idToObj = new Dictionary<int, GameObject>();

Tile[] tiles = new Tile[1000];
GameObject obj = new GameObject("obj");

//Create new Instance with Instance ID
tiles[0] = new Tile(obj.GetInstanceID());

//Add to Dictionary
idToObj.Add(obj.GetInstanceID(), obj);

最后,值得测试一下,看看哪个更快。使用在 struct 中声明的 GameObject 进行测试,然后使用上面使用 intDictionary 的方法进行测试。

【讨论】:

  • 使用字典提高内存使用率?嗯? Int32 占用相同或仅一半的参考空间...因此在理想情况下使用 Int 作为字典的键(忽略字典开销,仅键 + 值)将占用两倍的空间只是参考...(谢谢评论如何启用unsafe)
  • @AlexeiLevenkov 我知道有字典开销。您只需在初始化一次期间将对象存储到字典中。游戏开始时只有一次。那个开销没有了。您现在拥有的是一个字典,它占用更多空间,但比在结构中存储数百万个类引用更快更好。当 GC 运行时,它必须扫描数百万个结构以找到其中的类引用。也许我错了....从Tile结构类中,您可以访问以goID为键的GameObject。
  • 不取决于引用计数的存储位置吗?我原以为这将在实际引用的对象上,并在创建或销毁引用时更新,而不是遍历所有现有变量以查看其中是否有任何引用?
  • @Graeme none of .Net implementation 我知道对 GC 使用传统的引用计数(在en.wikipedia.org/wiki/… 的意义上)。他们使用某种形式的“标记和扫描” - en.wikipedia.org/wiki/… (MSDN - msdn.microsoft.com/en-us/library/ee787088.aspx, Unity3d - docs.unity3d.com/Manual/…)。所以不,根本没有为引用计数分配空间。
  • @AlexeiLevenkov 修改了我的答案以添加它,甚至要求 OP 对其进行测试以查看哪个更快。如果有问题,您可以编辑答案。
【解决方案3】:

据我了解,GameObject 已经是一个引用类型,所以它是对对象的引用,而不是实际的对象本身。

所以简单存储

struct Tile {
  GameObject go;
  ...
}

就足够了。

但是,如果您要传递您的结构;该结构将按值传递,因此整个结构的副本将传递给任何方法,除非您特别指定按引用传递(使用“Ref”关键字)。

【讨论】:

  • 我很确定复制结构仍然只会复制引用,而不是包含的对象。
  • @DanielHume 确实如此,但是由于您存储的只是引用,因此您需要复制的只是引用,因此您不想复制整个对象,因为这可能非常大且成本高昂。 GameObject 是一个类,所以定义为它的类型的变量都是引用而不是实际的对象。
  • @DanielHume 如果您将结构本身的引用称为引用,那么不,结构是文字类型,因此除非使用 Ref,否则按值传递。测试一下,创建一个带有 int 的结构,打印它的值,将它传递给一个方法并增加值,然后再次打印该值,你会看到它在原始结构中没有增加,只有方法内的副本
  • 所以我们同意。为了清楚起见,您可能想改写最后一段,“但是,它将按值传递”这句话似乎暗示 GameObject 将与所有其他结构字段一起按值传递。跨度>
  • @DanielHume 啊哈!我已经改写了,其中一个“它在我的脑海中非常有意义”的时刻:) 好地方。
猜你喜欢
  • 1970-01-01
  • 2014-12-19
  • 1970-01-01
  • 1970-01-01
  • 2014-04-21
  • 1970-01-01
  • 1970-01-01
  • 2013-07-28
  • 2013-08-15
相关资源
最近更新 更多