跳转至

动态分配问题

原文:https://docs.elrond.com/developers/best-practices/the-dynamic-allocation-problem

### 避免内存分配

警告

智能合约必须避免动态分配。由于动态分配带来的性能损失,Elrond虚拟机配置了硬限制,并将阻止试图过多分配的合约。

这里有一些简单的指导方针,你可以用来确保你的合约有效执行。通过遵循它们,当你的合约被调用时,你可能会注意到燃气消耗的大量减少。编译后的 WASM 二进制文件也有可能变得更小,因此调用起来更快更便宜。

都是关于类型的

许多基本的 Rust 类型(如StringVec<T>)是在堆上动态分配的。简单地说,这意味着程序(在这种情况下,智能合约)不断向运行时环境(VM)请求越来越多的内存。对于小的集合来说,这没有多大关系,但是对于大的集合来说,这可能会变得很慢,VM 甚至可能会停止合约,并将执行标记为失败。

主要问题是,基本的 Rust 类型非常渴望动态内存分配:他们要求比实际需要更多的内存。对于普通的程序来说,这对于性能来说是非常好的,但是对于智能合约来说,每条指令都要耗费大量的资源,这对成本甚至运行时故障都有很大的影响。

另一种方法是使用托管类型,而不是通常的 Rust 类型。所有托管类型,如BigUintManagedBuffer等。将它们的所有内容存储在虚拟机的内存中,而不是压缩内存,因此它们有很大的性能优势。但是您不需要关心内容“在哪里”,因为托管类型会在 VM 的帮助下自动跟踪内容。

托管类型的工作方式是只在约定内存中存储一个handle,这是一个u32索引,而实际的有效负载驻留在保留的 VM 内存中。因此,每当你必须添加两个BigUint时,例如,你代码中的+操作将只传递三个句柄:结果、第一个操作数和第二个操作数。通过这种方式,很少有数据被传递,这反过来使得一切都更便宜。由于这些类型只存储一个句柄,它们的内存分配大小是固定的,所以可以在堆栈上分配,而不是在堆上分配。

警告

如果您需要更新旧代码以利用托管类型,请花时间了解您需要做出的更改。这种更新很重要,不能自动完成。

基锈类型 vs 托管类型

下面是由Elrond框架提供的非托管类型(基本信任类型)及其托管对应类型的表格:

非托管(使用安全) 非托管(在堆上分配) 管理
- - BigUint
&[u8] - &ManagedBuffer
- BoxedBytes ManagedBuffer
ArrayVec<u8, CAP>【1】 Vec<u8> ManagedBuffer
- String ManagedBuffer
- - TokenIdentifier
- MultiValueVec MultiValueEncoded / MultiValueManagedVec
ArrayVec<T, CAP>【1】 Vec<T> ManagedVec<T>
[T; N]【2】 Box<[T; N]> ManagedByteArray<N>
- Address ManagedAddress
- H256 ManagedByteArray<32>
- - EsdtTokenData
- - EsdtTokenPayment

在大多数情况下,托管类型可以作为基本 Rust 类型的替代。一个简单的例子,见 BigUint 操作

我们还建议在需要连续的有用内存区域时,直接在堆栈(作为局部变量)上分配 Rust 数组。此外,避免为此目的分配可变的全局缓冲区,这需要使用unsafe代码。

另外,考虑使用ArrayVec,它提供了Vec的功能,但是没有在堆上分配。相反,它需要直接在堆栈上分配一块内存,就像基本的 Rust 本地数组一样,但保留了Vec的灵活性。

警告

确保在考虑部署到 mainnet 之前,您逐步迁移到托管类型彻底测试您的代码

**##### 提示

您可以使用erdpy contract report命令来验证您的合约是否仍然需要动态分配。


1. 在堆栈上分配,因此它有固定的容量——它不能无限增长。您可以将其设置为您喜欢的大小,但请注意,添加超过此容量会导致死机。使用`try_push`而不是`push`进行更优雅的错误处理。[↩](#fnref1)t5】↩ 2. 传递数组时要小心,因为从函数返回时它们会被复制。这可能会在您的合约中添加许多昂贵的内存副本。 [↩](#fnref2)

**



回到顶部