Performance 我应该通过引用或值传递一个大数组吗?

Performance 我应该通过引用或值传递一个大数组吗?,performance,pass-by-reference,rust,pass-by-value,Performance,Pass By Reference,Rust,Pass By Value,我正在用铁锈制作一个游戏机模拟器。我创建了一个readByteFromMemory()函数,该函数以8KB数组作为参数,该数组表示游戏机的内部内存,该内存为8KB。我在这里考虑两种选择: fn readByteFromMemory(internalRAM: [u8;0x2000]) -> u8 或 我能找到的唯一引用此主题的内容是这篇文章:其中指出: But don’t reach for that pointer until you must! Make sure that the

我正在用铁锈制作一个游戏机模拟器。我创建了一个readByteFromMemory()函数,该函数以8KB数组作为参数,该数组表示游戏机的内部内存,该内存为8KB。我在这里考虑两种选择:

fn readByteFromMemory(internalRAM: [u8;0x2000]) -> u8

我能找到的唯一引用此主题的内容是这篇文章:其中指出:

But don’t reach for that pointer until you must! Make sure that the 
struct is large enough by performing some tests before you add in the 
complexity of pointers.
我对这两个选项都进行了基准测试,似乎无论优化器是打开还是关闭,传递指针的速度都要快得多(正如预期的那样)

尽管有趣的是,如果关闭优化器,pass-by值会稍微快一点,如下所示:

➜  rustTest$  rustc --test -O passByTest.rs
➜  rustTest$  ./passByTest --bench         

running 2 tests
test ptrBench   ... bench:         1 ns/iter (+/- 0)
test valueBench ... bench:       221 ns/iter (+/- 2)

test result: ok. 0 passed; 0 failed; 0 ignored; 2 measured

➜  rustTest$  rustc --test  passByTest.rs 
➜  rustTest$  ./passByTest --bench       

running 2 tests
test ptrBench   ... bench:        13 ns/iter (+/- 3)
test valueBench ... bench:       152 ns/iter (+/- 1)

我的问题是:在通过引用传递比通过值传递更有意义的情况下,以字节为单位的阈值是多少?

我不能直接回答您的问题,因为与其他许多问题一样,这取决于代码所在的位置、周围的代码在做什么、访问模式是什么等等。如果您关心性能,则必须对其进行分析

也就是说

  • Rust中通过值传递的“大”值实际上通过引用传递,作为优化。说到“大”,我相信它比几个指针更重要。同样,这是一种优化,因此您需要依靠编译器在这里做出合理的选择。在大多数情况下,它应该正确选择

    还要记住,您自己不能“通过值进行逻辑传递,实际上是通过引用进行传递”(这是编译器正在做的);这意味着要使用
    &move
    引用,这在该语言中还不存在

    我相信Steve在那里的评论应该被更自由地解释为:“不要自己重写编译器,除非评测告诉您这样做。”

  • 那么这两者之间有什么区别吗?对如果您传递一个
    [u8;0x2000]
    ,则调用者必须在传递该值之前复制该值,前提是您希望以后能够再次使用该值。这可能就是性能差异的来源

    请注意,如果将值移动到函数中,编译器将不会进行复制。这包括所有不可复制的类型(如
    Box
    String
    Vec
    ),并且应该包括编译器知道您不会再次使用的可复制类型

适当的选择是
&[u8;0x2000]
,因为:

  • 您只需读取该值(即,如果要修改它,则需要一个
    &mut[u8;0x2000]
  • 您不希望获得值的所有权(在这种情况下,您将通过值传递)

再次强调:不要开始玩“这是一个很大的值,因此我将使用pass-by-reference”的游戏,因为编译器已经在这样做了,您很难手动将其正确无误。

关于第二点:是的,理论上,编译器应该能够“像被移动一样”运行当函数调用后未使用可复制值时。但是,与其他优化一样,这取决于LLVM是否意识到这是合法的,并且我不确定rustc当前是否正确地传达了这一点(我认为
LLVM.life.end
放置得过于保守有问题)。我还想补充一点,适当的选择可能是甚至
&[u8]
,而不是
&[u8;0x2000]
。但编译器难道不应该知道,既然数组是不可变传递的,那么数组应该“逻辑上按值传递,实际上按引用传递”吗?这里似乎不需要完整副本,除非我遗漏了一些东西。@VladimirMatveev为什么&[u8]更合适?我指定了大小,因为内部RAM数组总是8KB长。这只是一个类型安全问题,除非Rust与C类似,C忽略了参数中的数组大小?使用&[u8]是否更有效?@DanB91:编译器只知道
[u8;0x2000]
是按值逻辑传递的。这意味着您可能会修改它。因此它必须先进行复制。
&[u8;0x2000]
明确告诉它您肯定不会修改传递的内容,因此它不需要进行复制。
➜  rustTest$  rustc --test -O passByTest.rs
➜  rustTest$  ./passByTest --bench         

running 2 tests
test ptrBench   ... bench:         1 ns/iter (+/- 0)
test valueBench ... bench:       221 ns/iter (+/- 2)

test result: ok. 0 passed; 0 failed; 0 ignored; 2 measured

➜  rustTest$  rustc --test  passByTest.rs 
➜  rustTest$  ./passByTest --bench       

running 2 tests
test ptrBench   ... bench:        13 ns/iter (+/- 3)
test valueBench ... bench:       152 ns/iter (+/- 1)