Warning: file_get_contents(/data/phpspider/zhask/data//catemap/3/arrays/12.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
Arrays 为什么在包含240个或更多元素的阵列上循环时会对性能产生很大影响?_Arrays_Performance_Rust_Llvm Codegen - Fatal编程技术网

Arrays 为什么在包含240个或更多元素的阵列上循环时会对性能产生很大影响?

Arrays 为什么在包含240个或更多元素的阵列上循环时会对性能产生很大影响?,arrays,performance,rust,llvm-codegen,Arrays,Performance,Rust,Llvm Codegen,在Rust中的数组上运行求和循环时,我注意到当容量>=240时,性能会大幅下降容量=239大约快80倍 Rust是否对“短”数组进行了特殊的编译优化 编译时使用rustc-C opt level=3 使用std::time::Instant; 常数容量:usize=240; 循环中的常数:usize=500000; fn main(){ 设mut arr=[0;容量]; 对于容量为0.的i{ arr[i]=i; } 设mut和=0; 让现在=瞬间::现在(); 对于0..in_循环中的u{ 设m

在Rust中的数组上运行求和循环时,我注意到当
容量
>=240时,性能会大幅下降<代码>容量=239大约快80倍

Rust是否对“短”数组进行了特殊的编译优化

编译时使用
rustc-C opt level=3

使用std::time::Instant;
常数容量:usize=240;
循环中的常数:usize=500000;
fn main(){
设mut arr=[0;容量];
对于容量为0.的i{
arr[i]=i;
}
设mut和=0;
让现在=瞬间::现在();
对于0..in_循环中的u{
设muts=0;
对于0..arr.len()中的i{
s+=arr[i];
}
总和+=s;
}
println!((“sum:{}时间:{:?}”,sum,now.appeased());
}

摘要:低于240,LLVM完全展开内部循环,让它注意到它可以优化重复循环,打破基准



您发现了一个神奇的阈值,超过该阈值,LLVM将停止执行某些优化。阈值为8字节*240=1920字节(您的数组是一个由
usize
s组成的数组,因此假定x86-64 CPU,长度乘以8字节)。在这个基准测试中,一个特定的优化(仅针对长度239执行)导致了巨大的速度差。但让我们慢慢开始:

(此答案中的所有代码都是用
-C opt level=3
编译的)

pub fn foo()->usize{
设arr=[0;240];
设muts=0;
对于0..arr.len()中的i{
s+=arr[i];
}
s
}
这段简单的代码将大致生成预期的程序集:一个添加元素的循环。但是,如果将
240
更改为
239
,则发出的程序集差别很大。以下是总成的一小部分:

movdqa xmm1,xmmwordptr[rsp+32]
movdqa xmm0,xmmword ptr[rsp+48]
paddq xmm1,xmmword ptr[rsp]
paddq xmm0,xmmword ptr[rsp+16]
paddq xmm1,xmmword ptr[rsp+64]
; 这里省略了更多的东西。。。
paddq xmm0,xmmword ptr[rsp+1840]
paddq xmm1,xmmword ptr[rsp+1856]
paddq xmm0,xmmword ptr[rsp+1872]
paddq xmm0,xmm1
pshufd xmm1,xmm0,78
paddq xmm1,xmm0
这就是所谓的循环展开:LLVM将循环体粘贴一段时间,以避免执行所有那些“循环管理指令”,即递增循环变量,检查循环是否已结束,以及跳转到循环的开始

如果您想知道:
paddq
和类似的指令都是SIMD指令,允许并行求和多个值。此外,两个16字节SIMD寄存器(
xmm0
xmm1
)并行使用,因此CPU的指令级并行性基本上可以同时执行其中两条指令。毕竟,它们是相互独立的。最后,将两个寄存器相加,然后水平相加得到标量结果

现代主流x86 CPU(不是低功耗的Atom)在L1d缓存中运行时,每个时钟可以执行2个向量加载,并且
paddq
吞吐量也至少为每个时钟2个,在大多数CPU上具有1个周期的延迟。另请参阅和了解多个累加器以隐藏延迟(对于点产品,FP FMA的延迟)和吞吐量瓶颈

LLVM在未完全展开时会展开一些小循环,并且仍然使用多个累加器。因此,通常情况下,前端带宽和后端延迟瓶颈对于LLVM生成的循环来说并不是什么大问题,即使没有完全展开


但循环展开不会导致性能差异系数为80至少不能单独展开循环。让我们来看看实际的基准测试代码,它将一个循环放在另一个循环中:

const容量:usize=239;
循环中的常数:usize=500000;
pub fn foo()->usize{
设mut arr=[0;容量];
对于容量为0.的i{
arr[i]=i;
}
设mut和=0;
对于0..in_循环中的u{
设muts=0;
对于0..arr.len()中的i{
s+=arr[i];
}
总和+=s;
}
总和
}
()

CAPACITY=240
的程序集看起来正常:两个嵌套循环。(在函数的开头,有相当多的代码只是用于初始化,我们将忽略这些代码。)但是,对于239,它看起来非常不同!我们看到初始化循环和内部循环被展开:到目前为止,这是意料之中的

重要的区别在于,对于239,LLVM能够计算出内环的结果不依赖于外环因此,LLVM发出的代码基本上首先只执行内部循环(计算总和),然后通过将
sum
相加几次来模拟外部循环

首先,我们看到与上面几乎相同的程序集(表示内部循环的程序集)。之后我们看到了这一点(我对程序集进行了注释;带有
*
的注释尤其重要):

;在函数的开头,“rbx”被设置为0
movq-rax,xmm1;存储在'rax'中的SIMD求和结果`
加上rax,711;从循环展开中添加缺少的术语
mov ecx,500000;*初始化循环变量外循环
.LBB0_1:
添加rbx、rax;*rbx+=rax
添加rcx,-1;*减量循环变量
jne.LBB0_1;*如果循环变量!=0跳转到LBB0\u 1
mov-rax,rbx;将rbx(总和)移回rax
; 省略了两条不重要的指令
ret;返回值存储在'rax'中`
正如您在这里看到的,内部循环的结果被获取,与外部循环运行的频率一样加起来,然后返回。LLVM只能使用pe
example::bar:
        movabs  rax, 14340000000
        ret