我正在尝试将一些旧的C代码重写为Rust - 我是新手 . 我遇到的一个反复出现的问题是C代码有很多这样的循环:
for (i = startIndex; i < asize; i++)
{
if (firstEdge < 0 && condLeft(i))
{
firstEdge = i;
}
RightIndex = asize-1-i;
if (firstEdgeRight < 0 && condRight(RightIndex))
{
firstEdgeRight = RightIndex;
}
// Found both edges
if (firstEdge >= 0 && firstEdgeRight >= 0) {
break;
}
}
你会如何以高效的方式将其转化为Rust?我的问题是,虽然我可能得到我想要的功能,但我不确定如何获得(大致)相同的速度 .
这部分代码是我们代码中的主要瓶颈(至少这部分代码),并且在翻译时它希望保留以下属性 .
-
循环应该尽快破坏,因为
asize
可能非常大 . -
firstEdge
和firstEdgeRight
大致同时发现 . 因此,只有一个循环而不是两个循环是一件好事 - 为了避免再次从头开始搜索(即使我认为这个解决方案杀死了预取器(但我甚至有一个预取器)) .
虽然性能很重要,但可读性当然更为重要:)
EDIT 好的,这是我可能的Rust实现( cond_right()
和 cond_left()
被遗漏了) . 我想到的是:
-
这是否是其他人如果必须从头开始实施的话呢?
-
我真的需要制作
first_edge
和first_edge_right
可变吗?它们在我的实现中,但我感觉不对,因为它们只被分配一次 .
let mut first_edge = -1;
let mut first_edge_right = -1;
// Find first edge
let start_index = 300; // or something
let asize = 10000000;
for i in start_index..asize {
if first_edge < 0 && cond_left(i) {
first_edge = i;
}
let right_index = asize - i -1;
if first_edge_right < 0 && cond_right(right_index) {
first_edge_right = right_index;
}
if (first_edge >= 0 && first_edge_right >= 0) {
break;
}
}
3 回答
你需要做好选择的准备:
哪个对你来说更重要?这是我编写代码的方式,假设我已经理解了你的要求:
我们从左到右迭代一次,从右到左迭代一次 . 我的直觉告诉我,这将为代码提供简单的分支预测,以线性方式访问内存,这两者都应该是可优化的 .
但是,变量名称
asize
让我暂停 . 它当然听起来像是"array size"的缩写 . 如果是这种情况,那么我会100%建议使用切片而不是数组索引 . 为什么?因为数组访问(foo[0]
)通常具有边界检查的开销 . 我会用切片写一些东西:但是,对于您的问题,只有一个可能的真实答案:
使用分析器
使用分析器
使用分析器
使用分析器
只知道您的实际数据,条件的实际实现,您正在运行的其余代码等,您才能真正了解某些事情的速度 .
这对我来说不直观,所以我希望看到支持声明的分析结果 .
cond_left
和cond_right
对于回答性能问题非常重要 . 例如,用迭代器帮助替换索引吗?不知道cond_left
做什么就不会告诉 .另一个问题是
first_edge
和first_edge_right
是可变的 . 提议的RFC allowing loops to return a value可能是解决这个问题的一种优雅方式 . 现在你可以使用闭包来模拟循环返回:(In Playground) .
用
None
替换-1
可能会使变量变大 . 见Can I use the "null pointer optimization" for my own non-pointer types? .将此循环拆分为两个循环,一个获取
first_edge
,另一个检查剩余范围以获取first_edge_right
似乎是正确的事情,但CPU分支预测可能会最小化影响 .由于同时保持双方的关注至关重要,我认为没有一种简单的方法可以避免变异变量 .
可以提高可读性的一件事是使用option而不是负数 . 否则代码很好 .
(你可能做的另一件事是当索引在中间相遇时打破循环,如果这意味着你的问题没有解决方案,但那不是Rust特有的 . )