我设法想出了一个解决方案,不确定它是否是最好的实现,但它似乎有效。我还有几个选择。
Note:仅当您有一个需要创建临时数组并被调用的函数时,这对于提高速度很有用very频繁地。切换到堆分配对象的能力只是当缓冲区溢出时的后备措施。
选项 1 - 使用Span https://learn.microsoft.com/en-us/dotnet/api/system.span-1?view=netcore-3.1#properties and stackalloc
如果您正在构建 .NET Core 2.1 或更高版本、.NET Standard 2.1 或更高版本,或者可以使用 NuGet 来使用系统内存包 https://www.nuget.org/packages/System.Memory/,解决办法其实很简单。
使用类来代替类ref struct
(这是必要的Span<T>
字段,并且都不能将方法保留在声明它们的位置。如果您需要一个长期存在的类,那么没有理由尝试在堆栈上分配,因为您只需将其移动到堆中即可。)
public ref struct SmallList
{
private Span<int> data;
private int count;
//...
}
然后添加所有列表功能。Add()
, Remove()
等。在“添加”或任何可能扩展列表的函数中,添加一个检查以确保不会超出范围。
if (count == data.Length)
{
int[] newArray = new int[data.Length * 2]; //double the capacity
Array.Copy(data.ToArray(), 0, new_array, 0, cap);
data = new_array; //Implicit cast! Easy peasy!
}
Span<T>
可用于处理堆栈分配的内存,但它也可以指向堆分配的内存。因此,如果您不能保证您的列表始终足够小以适合堆栈,上面的代码片段为您提供了一个很好的后备方案,该后备方案不应频繁发生而导致明显的问题。如果是,要么增加初始堆栈分配大小(在合理范围内,不要溢出!),要么使用其他解决方案,例如数组池。
使用该结构只需要额外的一行和一个构造函数,该构造函数需要一个跨度来分配给data
场地。不确定是否有一种方法可以一次性完成所有操作,但这很简单:
Span<int> span = stackalloc int[32];
SmallList list = new SmallList(span);
如果您需要在嵌套函数中使用它(这是我的问题的一部分),您只需将其作为参数传递,而不是让嵌套函数返回列表。
void DoStuff(SmallList results) { /* do stuff */ }
DoStuff(list);
//use results...
选项 2:数组池
System.Memory 包还包括ArrayPool
类,它允许您存储一个小数组池,您的类/结构可以将其取出而无需打扰垃圾收集器。根据用例,这具有相当的速度。它还具有一个好处,即它适用于必须超越单一方法的类。如果您不会使用,自己编写也相当容易System.Memory
.
选项 3:指针
你可以用指针和其他东西来做类似的事情unsafe
代码,但问题是在技术上询问safe
代码。我只是喜欢我的清单详尽。
选项 4:不使用 System.Memory
如果像我一样,您正在使用 Unity / Mono,那么您不能使用 System.Memory 和相关功能,直到至少到2021年 https://github.com/dotnet/standard/issues/1330#issuecomment-510495179。这让你可以推出自己的解决方案。数组池的实现相当简单,并且可以避免垃圾分配。堆栈分配的数组有点复杂。
幸运的是,有人已经做到了 https://jacksondunstan.com/articles/5051,特别是考虑到 Unity。链接的页面相当长,但包括演示概念的示例代码和可以制作SmallBuffer
特定于您的具体用例的类。基本思想是创建一个包含各个变量的结构,您可以像数组一样对其进行索引。Update:我尝试了这两种解决方案,在我的例子中,数组池比 SmallBuffer 稍快(并且容易得多),所以请记住进行分析!