【发布时间】:2014-10-10 15:39:33
【问题描述】:
学习 Rust(耶!)并且我试图了解某些迭代器模式所需的预期惯用编程,同时获得最佳性能。注意:不是 Rust 的 Iterator 特征,只是我编写的一个方法,它接受一个闭包并将其应用于我从磁盘/内存中提取的一些数据。
我很高兴看到 Rust(+LLVM?)采用了我为稀疏矩阵条目编写的迭代器,以及用于执行稀疏矩阵向量乘法的闭包,写为
iterator.map_edges({ |x, y| dst[y] += src[x] });
并在生成的代码中内联闭包的主体。它进展得相当快。 :D
如果我创建两个这样的迭代器,或者第二次使用第一个(不是正确性问题)每个实例都会减慢很多(在这种情况下大约是 2 倍),大概是因为优化器不再选择进行专门化,因为多个调用站点,您最终会为每个元素执行一次函数调用。
我试图了解是否有惯用模式可以在不牺牲性能的情况下保持上述愉快体验(至少我喜欢它)。我的选择似乎是(不满足这个约束):
- 接受不可靠的性能(慢 2 倍不是致命的,但也没有奖品)。
- 要求用户提供面向批处理的闭包,以便对小批量数据的迭代器进行操作。这暴露了迭代器的一些内部结构(数据被很好地压缩,用户需要知道如何解包,或者迭代器需要在内存中暂存一个解包的批次)。
- 在实现假设的
EdgeMapClosure特征的类型中使map_edges泛型,并要求用户为他们想要内联的每个闭包实现这样的类型。未经测试,但我猜这会向 LLVM 公开不同的方法,每个方法都可以很好地内联。缺点是用户必须编写自己的闭包(打包相关状态等)。 - 可怕的 hack,比如创建不同的方法
map_edges0,map_edges1, ...。或者添加一个通用参数,程序员可以使用它来使方法不同,但否则会被忽略。
非解决方案包括“只使用for pair in iterator.iter() { /* */ }”;这是数据/任务并行平台的准备工作,我希望能够捕获/移动这些闭包到工作线程,而不是捕获主线程的执行。也许我应该使用的模式是编写上面的代码,将 it 放入 lambda/closure 中,然后将其发送出去?
在一个完美的世界里,最好有一个模式,它会导致源文件中每次出现map_edges,从而在二进制文件中产生不同的专门方法,而不会迫使整个项目以某种可怕的程度进行优化.我正在摆脱与托管语言和 JIT 的不愉快关系,其中泛型将是(我知道的)实现这种情况的唯一方法,但 Rust 和 LLVM 看起来足够神奇,我认为可能有一个好方法。 Rust 的迭代器如何处理这个来内联它们的闭包体?或者他们不(他们应该!)?
【问题讨论】:
-
不确定,但您可以尝试使用
#[inline(always)]标记您的方法。 -
作为一个更新,#[inline(always)] 恢复了性能,但可能是因为“错误的原因”:由于该方法内联到 main 中,因此闭包随后也可以内联。但是,我不希望将迭代器内联到 main 中(而是编写将闭包作为参数的库代码)。尽管如此,它仍然有帮助,我会看看我是否可以利用它。谢谢!
-
另外,通用方法,写作
map_edges<T>( ..., extra: T),效果很好,但确实令人不安。我需要向谁付费才能支持#[monomorphise_for_constant_arguments]属性? :D
标签: rust