我做了一些看起来很聪明的事情。为了更好地处理错误,我编写了如下 DB 操作
fn handle_delete(&self, id: &Id) -> Result<(), SqliteError> {
db_op(
|| DbOperationInfo::new("filerequests", OperationType::Delete),
|| {
self.conn
.execute("DELETE FROM filerequests WHERE id = ?", [id.to_string()])?;
Ok(())
},
)
}
我的想法是,我有我的 SqliteError 枚举,它包含一个相当小的结构,其中包含有关尝试的操作及其rusqlite::error::Error
原因的一些信息。
该db_op
函数的作用是接受两个闭包,第一个闭包生成包含操作信息的小结构,第二个闭包执行实际操作。如果(且仅当)发生错误,则执行第一个闭包生成操作信息,然后添加导致错误的代码以创建SqliteError
。
因此操作信息的创建是懒惰的。
但是我在这里真的有收获吗?还是创建闭包的成本同样高昂?或者,也许编译器会优化闭包创建并只执行一次?或者也许它会同样优化结构创建(内容有点静态)?
在此寻求一些指导:这种优化(可能)真的有帮助吗?
divan
您可以使用(或 Rust 中的任何其他基准测试工具,如)对这种情况进行基准测试criterion
。您需要先最小化代码以隔离要测试的实际代码。然后在正确的位置添加一堆
black_box
以避免测试代码被编译掉。这是我试图最小化和基准化您的情况。请注意,我不知道 中到底发生了什么
DbOperationInfo::new()
,所以我只是String
在其中进行了分配,以演示某种基于堆的初始化。如果DbOperationInfo
不执行堆分配,结果可能不太明显。结果如下:
那么这意味着什么?
DbOperationInfo::new()
大约需要 28 纳秒。这意味着单个堆分配就足以使其值得用闭包包装。
为了看到区别,我从中删除了堆分配
DbOperationInfo::new()
:现在你是否将其包装在闭包中已经不重要了:
这告诉我,只要你有一个堆分配或某种计算,就把它包装在一个闭包中。
如果你的对象很小并且可以静态分配,那就没关系了。理论上,在这种情况下删除闭包可能会稍微快一点,但编译器可能会优化闭包以使其相同。