在Solana程序中管理内存

本文详细探讨了Solana程序中的内存管理,特别是如何避免堆栈溢出的问题。通过对堆栈、堆和账户空间的基本概念进行讲解,提供了多种优化解决方案,包括将代码分割成函数、使用Box以转移内存到堆中,以及自定义堆分配器以合理利用内存。

看起来 solana-program 1.18 有一些新的破坏性更改,这使得栈溢出的情况更频繁。下面是关于 Solana 程序中内存工作的简要回顾,以及如何通过正确的程序内存管理来减少此类错误的可能性。

程序内存概述

Solana 运行时有三种内存类型:栈、堆和账户空间。

  • 栈用于局部变量存储,存储大多数简单数据类型。栈内存为 4KiB,但通常每个函数有自己分配的 4KiB。
  • 堆用于更动态的存储。Vecs 和 Strings 存储在这里,适用于更大的数据。程序有 32KiB 的堆空间,但这个分配是整个程序共享的。
  • 账户空间是对 Solana 账户的直接映射,并在调用程序时持久存在。如果栈和堆是易失性 RAM,账户空间就是将数据写回的硬盘。此内存可以通过零拷贝 serde 库直接使用,但这不在本指南的范围内。

需要注意的事项

Rust 编译器相当智能,因此如果你尝试在程序中分配过多的栈空间,编译时会发出警告。

如果你忽略这一点并违反栈大小限制,你将收到运行时错误。

这意味着你的指令超出了 4KiB 的栈空间,因为尝试分配了过多的空间。这通常发生在使用大量局部变量的大型复杂指令处理器中。

我该如何修复?

将代码拆分为函数

如果你的指令处理器是一个单一的、较长的函数,并声明了许多变量,解决方案可能只是将部分代码分开到自己的部分。

Solana 运行时中的每个函数调用都有自己的栈帧,这意味着它有自己 4KiB 的栈分配可以使用。这意味着如果你在一个大型函数中超出了栈大小,将部分代码提取到子函数中将为其提供一个新的 4KiB 内存栈进行使用。可以执行此操作的次数有限,添加子函数会造成轻微的性能损失,但对大多数用例来说,不太可能达到这两种情况。

使用 Box

对于 Anchor 程序,最简单的解决方案通常是简单地 Box 一个账户,如下所示。

当 Anchor 账户包含一个类型时,它会在指令开始时尝试反序列化该类型。默认情况下,该反序列化的账户存储在栈上,对于大型账户,这会快速占用所有栈空间。通过将该账户包装为 Box,你告知编译器希望该反序列化的账户存活在堆上。如上所述,有 32KiB 的堆空间可供使用,大多数情况下未被充分利用。将账户数据改为存储在这里,可以快速且简单地防止栈溢出。

自定义堆分配器

使用堆的缺点是整个程序只有 32KiB 的可用空间,且与栈不同,一旦用完就没有了。那么如果我告诉你有一种方法可以在使用完堆空间后恢复它呢?

当 Solana 运行时最初开发时,预期 Solana 程序都很简单、短小、直截了当。因此,默认的堆分配器——管理程序的堆空间——期望不需要超过 32KiB 的数据,因此在程序完成后不会释放堆空间。

如果你在阅读这篇文章,显然你知道这种预期并没有实现。幸运的是,有一种方法可以编写更好的堆分配器,它知道如何在完成后进行清理。Solana Labs 甚至在 SPL 中提供了一个示例( https://github.com/solana-labs/solana-program-library/tree/master/examples/rust/custom-heap)。通过使用自定义分配器,32KiB 的堆空间不再是一次性使用的内存,可以在先前的变量被释放后继续重用该空间。

希望上面提到的某些解决方案可以帮助你解决任何正在遇到的 Solana 程序内存问题。如果你有任何额外的问题,请随时在 Twitter 上联系我!

  • 原文链接: medium.com/metaplex/mana...
  • 登链社区 AI 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
点赞 0
收藏 0
分享
本文参与登链社区写作激励计划 ,好文好收益,欢迎正在阅读的你也加入。

0 条评论

请先 登录 后评论
blockiosaurus
blockiosaurus
江湖只有他的大名,没有他的介绍。