Warning: file_get_contents(/data/phpspider/zhask/data//catemap/7/rust/4.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Loops 删除Rust循环中的边界检查,以达到最佳编译器输出_Loops_Rust_Bounds Check Elimination - Fatal编程技术网

Loops 删除Rust循环中的边界检查,以达到最佳编译器输出

Loops 删除Rust循环中的边界检查,以达到最佳编译器输出,loops,rust,bounds-check-elimination,Loops,Rust,Bounds Check Elimination,在试图确定是否可以/应该使用Rust而不是默认的C/C++时,我正在研究各种边缘情况,主要是考虑到这个问题:在0.1%的情况下,我是否总能获得与gcc一样好的编译器输出(具有适当的优化标志)?答案很可能是否定的,但让我们看看 上有一个相当特殊的示例,它研究无分支排序算法的子例程的编译器输出 以下是基准C代码: #包括 #包括 int32_t*foo(int32_t*元素、int32_t*缓冲区、int32_t枢轴) { 大小缓冲区索引=0; 对于(尺寸i=0;i=N); 让元素=&元素[…N];

在试图确定是否可以/应该使用Rust而不是默认的C/C++时,我正在研究各种边缘情况,主要是考虑到这个问题:在0.1%的情况下,我是否总能获得与gcc一样好的编译器输出(具有适当的优化标志)?答案很可能是否定的,但让我们看看

上有一个相当特殊的示例,它研究无分支排序算法的子例程的编译器输出

以下是基准C代码:

#包括
#包括
int32_t*foo(int32_t*元素、int32_t*缓冲区、int32_t枢轴)
{
大小缓冲区索引=0;
对于(尺寸i=0;i<64;++i){
缓冲区[buffer_index]=(int32_t)i;
缓冲区索引+=(大小t)(元素[i]<枢轴);
}
}
下面是编译器输出的示例

第一次尝试生锈的情况如下所示:

pub fn foo0(elements: &Vec<i32>, mut buffer: [i32; 64], pivot: i32) -> () {
    let mut buffer_index: usize = 0;
    for i in 0..buffer.len() {
        buffer[buffer_index] = i as i32;
        buffer_index += (elements[i] < pivot) as usize; 
    }
}
pub fn foo0(元素:&Vec,mut buffer:[i32;64],pivot:i32)->(){
让mut buffer_index:usize=0;
对于0..buffer.len()中的i{
buffer[buffer_index]=i作为i32;
缓冲区索引+=(元素[i]
有相当多的边界检查正在进行,请参阅

下一次尝试将消除第一次边界检查:

pub unsafe fn foo1(elements: &Vec<i32>, mut buffer: [i32; 64], pivot: i32) -> () {
    let mut buffer_index: usize = 0;
    for i in 0..buffer.len() {
        unsafe {
            buffer[buffer_index] = i as i32;
            buffer_index += (elements.get_unchecked(i) < &pivot) as usize; 
        }
    }
}
pub-unsafe-fn-foo1(元素:&Vec,mut-buffer:[i32;64],pivot:i32)->(){
让mut buffer_index:usize=0;
对于0..buffer.len()中的i{
不安全{
buffer[buffer_index]=i作为i32;
缓冲区索引+=(elements.get_unchecked(i)<&pivot)作为usize;
}
}
}
这是一个更好的一点(见相同的godbolt链接如上)

最后,让我们尝试完全删除边界检查:

use std::ptr;

pub unsafe fn foo2(elements: &Vec<i32>, mut buffer: [i32; 64], pivot: i32) -> () {
    let mut buffer_index: usize = 0;
    unsafe {
        for i in 0..buffer.len() {
            ptr::replace(&mut buffer[buffer_index], i as i32);
            buffer_index += (elements.get_unchecked(i) < &pivot) as usize; 
        }
    }
}
使用std::ptr;
发布不安全的fn foo2(元素:&Vec,mut buffer:[i32;64],pivot:i32)->(){
让mut buffer_index:usize=0;
不安全{
对于0..buffer.len()中的i{
ptr::replace(&mut buffer[buffer_index],i为i32);
缓冲区索引+=(elements.get_unchecked(i)<&pivot)作为usize;
}
}
}
这将产生与
foo1
相同的输出,因此
ptr::replace
仍然执行边界检查。在这里,那些
不安全的
操作肯定超出了我的深度。这引出了我的两个问题:

  • 如何消除边界检查
  • 像这样分析边缘情况有意义吗?或者,如果将整个算法而不是其中的一小部分呈现出来,Rust编译器会看穿这一切吗
关于最后一点,我很好奇,总的来说,锈迹是否可以被切割到与C一样接近金属的程度。经验丰富的铁锈程序员可能会在这条调查路线上畏缩,但这是

  • 如何消除边界检查
数组通过对切片的deref强制,也具有相同的性能

pub-unsafe-fn-foo(元素:&Vec,mut-buffer:[i32;64],pivot:i32){
让mut buffer_index:usize=0;
对于0..buffer.len()中的i{
不安全{
*buffer.get_unchecked_mut(buffer_index)=i为i32;
缓冲区索引+=(elements.get_unchecked(i)<&pivot)作为usize;
}
}
}
这可能会产生与使用Clang编译等效C代码所获得的机器代码相同的机器代码

  • 像这样分析边缘案例有意义吗?或者,如果将整个算法而不是其中的一小部分呈现出来,Rust编译器会看穿这一切吗
像往常一样,即使您已经在代码的这一部分中发现了瓶颈,也要对这些情况中的任何一种进行基准测试。否则,这是一个过早的优化,总有一天会后悔的。尤其是在生锈的情况下,编写
不安全的
代码的决定不应掉以轻心。可以肯定地说,在许多情况下,仅移除边界检查的努力和风险就超过了预期的性能好处

关于最后一点,我很好奇,总的来说,锈迹是否可以被切割到与C一样接近金属的程度

不,你不想这样做有两个主要原因:

<> LI>尽管锈的抽象性强,但不支付你不使用的原则仍然是很有针对性的,与C++类似。看见在边界检查的情况下,这仅仅是语言设计决策的结果,即当编译器无法确保这样的访问是内存安全的时候,总是执行空间检查
  • 无论如何。它可能看起来像文字,接近金属,直到它真的不是
  • 另见:


      • 您可以使用老式的指针算法来实现这一点

        常数N:usize=64; 发布fn foo2(元素:&Vec,mut buffer:[i32;N],pivot:i32)->(){ 断言!(elements.len()>=N); 让元素=&元素[…N]; 让mut buff_ptr=buffer.as_mut_ptr(); 对于elements.iter().enumerate()中的(i,&elem){ 不安全{ //安全性:我们严格地将ptr提高了不到N倍 *buff_ptr=i作为i32; 如果元素<枢轴{ buff_ptr=buff_ptr.add(1); } } } }
    此版本编译为:

    example::foo2:
            push    rax
            cmp     qword ptr [rdi + 16], 64
            jb      .LBB7_4
            mov     r9, qword ptr [rdi]
            lea     r8, [r9 + 256]
            xor     edi, edi
    
            // Loop goes here
    .LBB7_2:
            mov     ecx, dword ptr [r9 + 4*rdi]
            mov     dword ptr [rsi], edi
            lea     rax, [rsi + 4]
            cmp     ecx, edx
            cmovge  rax, rsi
            mov     ecx, dword ptr [r9 + 4*rdi + 4]
            lea     esi, [rdi + 1]
            mov     dword ptr [rax], esi
            lea     rsi, [rax + 4]
            cmp     ecx, edx
            cmovge  rsi, rax
            mov     eax, dword ptr [r9 + 4*rdi + 8]
            lea     ecx, [rdi + 2]
            mov     dword ptr [rsi], ecx
            lea     rcx, [rsi + 4]
            cmp     eax, edx
            cmovge  rcx, rsi
            mov     r10d, dword ptr [r9 + 4*rdi + 12]
            lea     esi, [rdi + 3]
            lea     rax, [r9 + 4*rdi + 16]
            add     rdi, 4
            mov     dword ptr [rcx], esi
            lea     rsi, [rcx + 4]
            cmp     r10d, edx
            cmovge  rsi, rcx
            // Conditional branch to the loop beginning
            cmp     rax, r8
            jne     .LBB7_2
            pop     rax
            ret
    .LBB7_4:
            call    std::panicking::begin_panic
            ud2
    
    如您所见,循环是展开的,单个分支是循环迭代跳跃

    然而,我感到惊讶的是,这个函数并没有因为没有效果而被消除:它应该被编译成简单的noop。很可能,在内联之后会这样做

    另外,我想说,将参数更改为&mut不会更改代码:

    example::foo2:
            push    rax
            cmp     qword ptr [rdi + 16], 64
            jb      .LBB7_4
            mov     r9, qword ptr [rdi]
            lea     r8, [r9 + 256]
            xor     edi, edi
    .LBB7_2:
            mov     ecx, dword ptr [r9 + 4*rdi]
            mov     dword ptr [rsi], edi
            lea     rax, [rsi + 4]
            cmp     ecx, edx
            cmovge  rax, rsi
            mov     ecx, dword ptr [r9 + 4*rdi + 4]
            lea     esi, [rdi + 1]
            mov     dword ptr [rax], esi
            lea     rsi, [rax + 4]
            cmp     ecx, edx
            cmovge  rsi, rax
            mov     eax, dword ptr [r9 + 4*rdi + 8]
            lea     ecx, [rdi + 2]
            mov     dword ptr [rsi], ecx
            lea     rcx, [rsi + 4]
            cmp     eax, edx
            cmovge  rcx, rsi
            mov     r10d, dword ptr [r9 + 4*rdi + 12]
            lea     esi, [rdi + 3]
            lea     rax, [r9 + 4*rdi + 16]
            add     rdi, 4
            mov     dword ptr [rcx], esi
            lea     rsi, [rcx + 4]
            cmp     r10d, edx
            cmovge  rsi, rcx
            cmp     rax, r8
            jne     .LBB7_2
            pop     rax
            ret
    .LBB7_4:
            call    std::panicking::begin_panic
            ud2
    

    因此,不幸的是,rustc可能会发出函数接受缓冲区参数作为LLVM IR中的指针的消息。

    我当然不想暗示,或者让任何读过这篇文章的人暗示,Rust比lang X慢/快/好/坏
    example::foo2:
            push    rax
            cmp     qword ptr [rdi + 16], 64
            jb      .LBB7_4
            mov     r9, qword ptr [rdi]
            lea     r8, [r9 + 256]
            xor     edi, edi
    .LBB7_2:
            mov     ecx, dword ptr [r9 + 4*rdi]
            mov     dword ptr [rsi], edi
            lea     rax, [rsi + 4]
            cmp     ecx, edx
            cmovge  rax, rsi
            mov     ecx, dword ptr [r9 + 4*rdi + 4]
            lea     esi, [rdi + 1]
            mov     dword ptr [rax], esi
            lea     rsi, [rax + 4]
            cmp     ecx, edx
            cmovge  rsi, rax
            mov     eax, dword ptr [r9 + 4*rdi + 8]
            lea     ecx, [rdi + 2]
            mov     dword ptr [rsi], ecx
            lea     rcx, [rsi + 4]
            cmp     eax, edx
            cmovge  rcx, rsi
            mov     r10d, dword ptr [r9 + 4*rdi + 12]
            lea     esi, [rdi + 3]
            lea     rax, [r9 + 4*rdi + 16]
            add     rdi, 4
            mov     dword ptr [rcx], esi
            lea     rsi, [rcx + 4]
            cmp     r10d, edx
            cmovge  rsi, rcx
            cmp     rax, r8
            jne     .LBB7_2
            pop     rax
            ret
    .LBB7_4:
            call    std::panicking::begin_panic
            ud2