如何使用 Span 和 stackalloc 创建临时小列表

2024-06-24

我正在阅读一些用 C 编写的代码的描述,这些代码由于在堆栈上而不是堆上分配临时数组以在非常热的循环中使用而提高了速度。 (它被描述为类似于 SBO 优化)。有问题的对象类似于List<T>因为它只是一个数组,上面有一些基本的便利功能。它分配一小部分内存来使用,如果列表扩展超过数组的大小,它会在堆上分配一个新数组,复制数据并更新指针。

我想在 C# 中做同样的事情,但我不确定如何完成它,因为我想将其保留在safe上下文,所以我不能使用指针来更新数据引用(如果它已扩展),并且Span<int>没有隐式转换为int[]。具体来说:

  • stackalloc内存在方法退出时释放,所以我不确定是否有更简单的方法来使用这样的结构,而不是给它一个 Span 字段并在使用它的方法中创建后分配它。
  • 如何在不更改面向公众的接口的情况下巧妙地在使用不同类型(Span 和 int[])的支持字段之间切换?

我设法想出了一个解决方案,不确定它是否是最好的实现,但它似乎有效。我还有几个选择。

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 稍快(并且容易得多),所以请记住进行分析!

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

如何使用 Span 和 stackalloc 创建临时小列表 的相关文章

  • 求 a 范围内的 pow(a^b)modN

    对于给定的b and N以及一系列a say 0 n 我需要找到ans 0 n 1 where ans i 没有a s为此pow a b modN i 我在这里搜索的是可能的重复pow a b modN对于一系列a 以减少计算时间 例子 i
  • 在 2 个 .c 文件之间共享函数

    dir1有dir2 file1 c和file1 h dir2 有 file2 c 现在 如果我想在 file2 c 中访问 file1 c 中定义的函数 我需要在 file1 h 中声明它并在 file2 c 中包含 file1 h 这是一
  • 运行时两个注册之间的简单注入器基于动态上下文的注入

    我有一个使用 Simple Injector 进行命令处理程序注册的中介应用程序 并且注入和处理程序均已设置并完美运行 class DoWashingCommandHandler IRequestHandler
  • 如何从 std::vector 中删除元素而不调整其大小

    迭代器擦除 迭代器位置 迭代器擦除 首先是迭代器 迭代器最后 擦除元素 从向量中删除 容器可以是单个元素 位置 或一系列元素 第一个 最后一个 这有效地减少了向量 大小除以元素数量 删除 调用每个元素的 之前的析构函数 and remove
  • 在宏中使用 # [重复]

    这个问题在这里已经有答案了 请解释一下代码 include
  • C# Visual Studio 动态代码片段

    我正在开发一个 WinForms 项目 每天都会执行一些重复性的任务 所以我认为创建代码片段 https msdn microsoft com en us library ms165394 v vs 110 aspx会帮助我 但它仅适用于固
  • 如何处理作为参数传递到方法中的 Lambda 表达式 - C# .NET 3.5

    我对 Lambda 表达式的了解有点不稳定 虽然我可以编写使用 Lambda 表达式 又名 LINQ 的代码 但我正在尝试编写自己的方法 该方法采用一些 Lambda 表达式类型的参数 背景 我正在尝试编写一个方法 该方法从任何其他对象类型
  • 如何将 QSerialPort 模块添加到 CMake 中?

    我想将 QSerialPort 模块添加到 CMake 中 根据我的理解 我需要将QT 串口添加到 pro中 我只想使用 CMake 所以我尝试编译简单的 CMake 文件 但有错误 QtCore 正在工作 qDebug 可以毫无问题地显示
  • 命令中带空格的 Windows C 系统调用

    我无法使用名称和参数中的空格进行系统调用 例如 system c program files something example exe c my files example txt 我尝试过各种我知道的方法来逃避 但没有任何效果 我努力了
  • OpenMP 循环数组访问中的错误共享

    我想利用 OpenMP 来并行执行我的任务 我需要将数组的所有元素减去相同的数量并将结果写入另一个向量中 两个数组都是动态分配的malloc第一个填充了文件中的值 每个元素都有类型uint64 t pragma omp parallel f
  • 实体框架中的导航属性是什么

    我是实体框架的新手 当Visual Studio创建模型图时我们主要可以看到Entities Propertie和Navigation Properties这两个东西 那么这些Navigation Properties是什么 如何使用它们
  • 为什么 ASP.Net MVC Range 属性采用类型?

    我只是想知道为什么范围验证属性可以采用类型和两个字符串作为参数 这是为了根据枚举或类似的东西验证字符串吗 另外 我想做的是找到一种简单的方法来验证必须出现在枚举中的 3 个字符的字符串 有什么建议吗 谢谢 亚历克斯 我确实发现你提到的 Ra
  • 是否可以在 Eclipse 中为除 Java 之外的 Eclipse 编写插件?

    谁能帮我用c 写一个eclipse插件 weekens 和 celavek 感谢您提供的信息 我正在研究 JNI 并将尝试实现它 celavek 我们必须做什么样的主控 控制 在C 和java接口中处理是否风险更大 我的要求是在 Java
  • PC 上 XNA 中的信箱和缩放

    有没有一种方法可以让我基本上以 1080p 或 720p 作为默认分辨率来开发 XNA 游戏 然后根据设置的分辨率将游戏中的所有内容缩放到适当的大小 而不必在每个 Sprite 中设置缩放因子Draw 方法 我的想法是 我可以基于 1080
  • 使用反射检测属性的访问修饰符类型

    我编写了一些代码来使用反射查看属性 我已经使用反射从类中检索了属性列表 但是我需要查明该财产是公共的还是受保护的 例如 public string Name get set protected int Age get set Propert
  • 预览MouseMove 与 MouseMove

    我有相当多的 XAML 经验 但最近我注意到我的大多数同事都使用预览鼠标移动代替鼠标移动事件 我一直用鼠标移动它对我很有帮助 但我忍不住问我什么时候应该使用预览鼠标移动什么时候鼠标移动 有什么区别 各自有什么优点和缺点等等 PreviewM
  • 具有可导出私钥的证书的“错误密钥”例外

    我正在尝试使用非对称加密来加密然后解密文件 我已经使用 makecert 创建了一个测试证书并将其安装到我的个人本地计算机存储中 将来我必须在多个服务器上安装此证书 这就是为什么我使用 pe 标志创建它 即使用可导出的私钥 证书已成功创建并
  • 从不同的线程访问对象

    我有一个服务器类 它基本上等待来自客户端的连接 在该类中 我创建了一个 NetworkStream 对象 以便能够从客户端接收字节 由于 NetworkStream Read 方法不是异步的 这意味着它将等到从客户端读取字节才能继续执行类似
  • 如何从与 C# lambda 集成(而非代理集成)的 Amazon API 网关获取正确的 http 状态代码?

    我正在使用 C lambda 与 API 网关集成 我希望 API 网关返回正确的错误代码 例如 400 404 500 等 API网关模块tf文件 provider aws version lt 2 70 0 region var aws
  • Json.net 将数字属性序列化为字符串

    我正在使用 JsonConvert SerializeObject 序列化模型对象 服务器期望所有字段都是字符串 我的模型对象具有数字属性和字符串属性 我无法向模型对象添加属性 有没有办法将所有属性值序列化为字符串 我必须只支持序列化 而不

随机推荐