本文详细探讨了Solana程序中的内存管理,特别是如何避免堆栈溢出的问题。通过对堆栈、堆和账户空间的基本概念进行讲解,提供了多种优化解决方案,包括将代码分割成函数、使用Box以转移内存到堆中,以及自定义堆分配器以合理利用内存。
看起来 solana-program 1.18 有一些新的破坏性更改,这使得栈溢出的情况更频繁。下面是关于 Solana 程序中内存工作的简要回顾,以及如何通过正确的程序内存管理来减少此类错误的可能性。
Solana 运行时有三种内存类型:栈、堆和账户空间。
Rust 编译器相当智能,因此如果你尝试在程序中分配过多的栈空间,编译时会发出警告。
如果你忽略这一点并违反栈大小限制,你将收到运行时错误。
这意味着你的指令超出了 4KiB 的栈空间,因为尝试分配了过多的空间。这通常发生在使用大量局部变量的大型复杂指令处理器中。
如果你的指令处理器是一个单一的、较长的函数,并声明了许多变量,解决方案可能只是将部分代码分开到自己的部分。
Solana 运行时中的每个函数调用都有自己的栈帧,这意味着它有自己 4KiB 的栈分配可以使用。这意味着如果你在一个大型函数中超出了栈大小,将部分代码提取到子函数中将为其提供一个新的 4KiB 内存栈进行使用。可以执行此操作的次数有限,添加子函数会造成轻微的性能损失,但对大多数用例来说,不太可能达到这两种情况。
对于 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 助手,为大家转译优秀英文文章,如有翻译不通的地方,还请包涵~
如果觉得我的文章对您有用,请随意打赏。你的支持将鼓励我继续创作!