< 返回版块

eweca-d 发表于 2022-12-09 22:20

最近想写一个自己用的库来计算带状存储矩阵。主要需求就是1.带状矩阵A * 向量x;2.向量x * 向量y;3.迭代法求解线性方程组Ax=y。这过程中发现了一些疑惑特来求教。(PS:如果有带状存储矩阵可以用的库那其实更好了)

我首先先尝试写向量点积的方法来积累一些经验,看了一些用C来举例的文章,觉得矩阵乘法可能应该按,直接循环->交换循环顺序->SIMD向量化->循环展开->blocking,的顺序来优化。但是我发现手写SIMD256根本不如或几乎和编译器优化的一样。而SIMD向量化之后再循环展开更慢了,一重循环再展开倒是比直接循环快,几乎接近nalgebra了。

这把我搞郁闷了。难道rust写矩阵乘法,居然是直接循环更快么,更离谱的是,貌似nalgebra也是直接写循环

这是因为rust的循环优化太强了么?

测试代码如下:

use std::arch::x86_64::{
    __m128, _mm_setr_ps, _mm_mul_ps,
    _mm_hadd_ps, _mm_setzero_ps, __m256, _mm256_setzero_ps, _mm256_setr_ps, _mm256_mul_ps,
    _mm256_hadd_ps, _mm256_load_ps, _mm_load_ps, _mm256_fmadd_ps, _mm256_add_ps, _mm_fmadd_ps
};
use rand::{thread_rng, Rng};
use nalgebra as na;

fn main() {
    let mut x = vec![0.0f32; 10000000];
    let mut y = vec![0.0f32; 10000000];
    thread_rng().fill(&mut x[..]);
    thread_rng().fill(&mut y[..]);

    // time dot_product
    let start = std::time::Instant::now();
    let sum = dot_product(&x, &y);
    let elapsed = start.elapsed();
    println!("dot_product: {} in {:?}", sum, elapsed);

    // time unrolling_dot_product
    let start = std::time::Instant::now();
    let sum = unrolling_dot_product(&x, &y);
    let elapsed = start.elapsed();
    println!("unrolling_dot_product: {} in {:?}", sum, elapsed);
    
    // time simd_dot_product
    let start = std::time::Instant::now();
    let simd_sum = simd_dot_product(&x, &y);
    let simd_elapsed = start.elapsed();
    println!("simd_dot_product: {} in {:?}", simd_sum, simd_elapsed);

    // time simd256_dot_product
    let start = std::time::Instant::now();
    let simd_sum = simd256_dot_product(&x, &y);
    let simd_elapsed = start.elapsed();
    println!("simd256_dot_product: {} in {:?}", simd_sum, simd_elapsed);

    // time simd256_unrolling_dot_product
    let start = std::time::Instant::now();
    let simd_sum = simd256_unrolling_dot_product(&x, &y);
    let simd_elapsed = start.elapsed();
    println!("simd256_unrolling_dot_product: {} in {:?}", simd_sum, simd_elapsed);

    // time na_dot_product
    let vx = na::DVector::from_vec(x);
    let vy = na::DVector::from_vec(y);
    let start = std::time::Instant::now();
    let na_sum = vx.dot(&vy);
    let na_elapsed = start.elapsed();
    println!("na_dot_product: {} in {:?}", na_sum, na_elapsed);
}

// 简单点积
fn dot_product(x: &Vec<f32>, y: &Vec<f32>) -> f32 {
    assert!(x.len() == y.len());
    let mut sum = 0.0f32;
    for i in 0..x.len() {
        sum += x[i] * y[i];
    }
    sum
}

// 循环展开点积
fn unrolling_dot_product(x: &Vec<f32>, y: &Vec<f32>) -> f32 {
    assert!(x.len() == y.len());

    let mut sum = 0.0f32;

    let mut i = 0;
    let mut acc0 = 0.0f32;
    let mut acc1 = 0.0f32;
    let mut acc2 = 0.0f32;
    let mut acc3 = 0.0f32;
    let mut acc4 = 0.0f32;
    let mut acc5 = 0.0f32;
    let mut acc6 = 0.0f32;
    let mut acc7 = 0.0f32;

    while x.len() - i >= 8 {
        acc0 += unsafe { x.get_unchecked(i) * y.get_unchecked(i) };
        acc1 += unsafe { x.get_unchecked(i + 1) * y.get_unchecked(i + 1) };
        acc2 += unsafe { x.get_unchecked(i + 2) * y.get_unchecked(i + 2) };
        acc3 += unsafe { x.get_unchecked(i + 3) * y.get_unchecked(i + 3) };
        acc4 += unsafe { x.get_unchecked(i + 4) * y.get_unchecked(i + 4) };
        acc5 += unsafe { x.get_unchecked(i + 5) * y.get_unchecked(i + 5) };
        acc6 += unsafe { x.get_unchecked(i + 6) * y.get_unchecked(i + 6) };
        acc7 += unsafe { x.get_unchecked(i + 7) * y.get_unchecked(i + 7) };
        i += 8;
    }

    sum += acc0 + acc4;
    sum += acc1 + acc5;
    sum += acc2 + acc6;
    sum += acc3 + acc7;

    sum
}

// SIMD128点积
fn simd_dot_product(x: &Vec<f32>, y: &Vec<f32>) -> f32 {
    assert!(x.len() == y.len());
    let mut sum = 0.0f32;

    unsafe {
        let mut sum4 = _mm_setzero_ps();
        for i in 0..x.len() / 4 {
            let x4 = _mm_load_ps(&x[i * 4]);
            let y4 = _mm_load_ps(&y[i * 4]);
            sum4 = _mm_fmadd_ps(x4, y4, sum4);
        }
        let sum4: [f32; 4] = std::mem::transmute(sum4);
        sum += sum4[0] + sum4[1] + sum4[2] + sum4[3];
    }

    for i in (x.len() / 4) * 4..x.len() {
        sum += x[i] * y[i];
    }

    sum
}

// SIMD256点积
fn simd256_dot_product(x: &Vec<f32>, y: &Vec<f32>) -> f32 {
    assert!(x.len() == y.len());
    let mut sum = 0.0f32;

    unsafe {
        let mut sum8 = _mm256_setzero_ps();
        for i in 0..x.len() / 8 {
            let x8 = _mm256_load_ps(x.get_unchecked(i * 8));
            let y8 = _mm256_load_ps(y.get_unchecked(i * 8));
            sum8 = _mm256_fmadd_ps(x8, y8, sum8);
        }
        let sum8: [f32; 8] = std::mem::transmute(sum8);
        sum += sum8[0] + sum8[1] + sum8[2] + sum8[3] + sum8[4] + sum8[5] + sum8[6] + sum8[7];
    }

    for i in (x.len() / 8) * 8..x.len() {
        sum += x[i] * y[i];
    }

    sum
}

// SIMD256循环展开点积
fn simd256_unrolling_dot_product(x: &Vec<f32>, y: &Vec<f32>) -> f32 {
    assert!(x.len() == y.len());
    let mut sum = 0.0f32;

    unsafe {
        let mut sum8_0 = _mm256_setzero_ps();
        let mut sum8_1 = _mm256_setzero_ps();
        let mut sum8_2 = _mm256_setzero_ps();
        let mut sum8_3 = _mm256_setzero_ps();
        let mut sum8_4 = _mm256_setzero_ps();
        let mut sum8_5 = _mm256_setzero_ps();
        let mut sum8_6 = _mm256_setzero_ps();
        let mut sum8_7 = _mm256_setzero_ps();

        let mut i = 0;
        while x.len() - i >= 64  {
            let x8_0 = _mm256_load_ps(&x[i]);
            let y8_0 = _mm256_load_ps(&y[i]);
            sum8_0 = _mm256_fmadd_ps(x8_0, y8_0, sum8_0);

            let x8_1 = _mm256_load_ps(&x[i + 8]);
            let y8_1 = _mm256_load_ps(&y[i + 8]);
            sum8_1 = _mm256_fmadd_ps(x8_1, y8_1, sum8_1);

            let x8_2 = _mm256_load_ps(&x[i + 16]);
            let y8_2 = _mm256_load_ps(&y[i + 16]);
            sum8_2 = _mm256_fmadd_ps(x8_2, y8_2, sum8_2);
            
            let x8_3 = _mm256_load_ps(&x[i + 24]);
            let y8_3 = _mm256_load_ps(&y[i + 24]);
            sum8_3 = _mm256_fmadd_ps(x8_3, y8_3, sum8_3);

            let x8_4 = _mm256_load_ps(&x[i + 32]);
            let y8_4 = _mm256_load_ps(&y[i + 32]);
            sum8_4 = _mm256_fmadd_ps(x8_4, y8_4, sum8_4);

            let x8_5 = _mm256_load_ps(&x[i + 40]);
            let y8_5 = _mm256_load_ps(&y[i + 40]);
            sum8_5 = _mm256_fmadd_ps(x8_5, y8_5, sum8_5);

            let x8_6 = _mm256_load_ps(&x[i + 48]);
            let y8_6 = _mm256_load_ps(&y[i + 48]);
            sum8_6 = _mm256_fmadd_ps(x8_6, y8_6, sum8_6);

            let x8_7 = _mm256_load_ps(&x[i + 56]);
            let y8_7 = _mm256_load_ps(&y[i + 56]);
            sum8_7 = _mm256_fmadd_ps(x8_7, y8_7, sum8_7);

            i += 64;
        }

        sum8_0 = _mm256_add_ps(sum8_0, sum8_4);
        sum8_1 = _mm256_add_ps(sum8_1, sum8_5);
        sum8_2 = _mm256_add_ps(sum8_2, sum8_6);
        sum8_3 = _mm256_add_ps(sum8_3, sum8_7);

        sum8_0 = _mm256_add_ps(sum8_0, sum8_2);
        sum8_1 = _mm256_add_ps(sum8_1, sum8_3);

        sum8_0 = _mm256_add_ps(sum8_0, sum8_1);

        let sum8_0: [f32; 8] = std::mem::transmute(sum8_0);
        sum += sum8_0[0] + sum8_0[1] + sum8_0[2] + sum8_0[3] + sum8_0[4] + sum8_0[5] + sum8_0[6] + sum8_0[7];
    }

    for i in (x.len() / 64) * 64..x.len() {
        sum += x[i] * y[i];
    }
    sum
}

评论区

写评论
wangbyby 2022-12-11 14:59

应该是并行度,你可以调出来一个unroll的最优值.然后测试性能时候,应该多跑几次取平均值.

--
👇
eweca-d: 大佬,感觉是不是直接循环+unroll有什么奇怪的优化我没发现吧。

--
👇
wangbyby: 还有一件事, 你需要查看下CPU型号以及对应的指令集扩展. 比如我的笔记本是i5-7300HQ, 指令集扩展是Intel® SSE4.1, Intel® SSE4.2, Intel® AVX2,但没查到具体寄存器个数. 运行结果是 unrolling_dot_product: 24575894 in 42.7105ms simd256_dot_product: 24575892 in 126.9367ms

然后查看汇编

  • vmovaps也就是_mm256_load_ps和_mm256_store_ps有45处
  • movaps(去除vmovaps)有480-45=435处,而movaps对应_mm_load_ps和_mm_store_ps

即一个用的是avx指令集,一个是SSE指令集. 我不太了解intel给这两个指令集分配的寄存器情况, 所以我猜测性能差距来自于寄存器和计算单元数量的差异.

作者 eweca-d 2022-12-11 10:24

另外,还有unalign的,unrolling还有movups有24处,simd256有movups有4处。

--
👇
eweca-d: 我的是台式,amd tr 2950x,扩展基本全有,包括SSE4.1, SSE4.2, SSE 4A,AVX,AVX2,FMA3。寄存器有几个也没看到。我也不太懂汇编,按你这个方法查了下:unrolling那个,vmovaps没有,movaps有26处;simd256那个,vmovaps有6处,movaps有42-6=36处。

作者 eweca-d 2022-12-11 10:09

大佬,感觉是不是直接循环+unroll有什么奇怪的优化我没发现吧。

--
👇
wangbyby: 还有一件事, 你需要查看下CPU型号以及对应的指令集扩展. 比如我的笔记本是i5-7300HQ, 指令集扩展是Intel® SSE4.1, Intel® SSE4.2, Intel® AVX2,但没查到具体寄存器个数. 运行结果是 unrolling_dot_product: 24575894 in 42.7105ms simd256_dot_product: 24575892 in 126.9367ms

然后查看汇编

  • vmovaps也就是_mm256_load_ps和_mm256_store_ps有45处
  • movaps(去除vmovaps)有480-45=435处,而movaps对应_mm_load_ps和_mm_store_ps

即一个用的是avx指令集,一个是SSE指令集. 我不太了解intel给这两个指令集分配的寄存器情况, 所以我猜测性能差距来自于寄存器和计算单元数量的差异.

作者 eweca-d 2022-12-11 10:08

我的是台式,amd tr 2950x,扩展基本全有,包括SSE4.1, SSE4.2, SSE 4A,AVX,AVX2,FMA3。寄存器有几个也没看到。我也不太懂汇编,按你这个方法查了下:unrolling那个,vmovaps没有,movaps有26处;simd256那个,vmovaps有6处,movaps有42-6=36处。

--
👇
wangbyby: 还有一件事, 你需要查看下CPU型号以及对应的指令集扩展. 比如我的笔记本是i5-7300HQ, 指令集扩展是Intel® SSE4.1, Intel® SSE4.2, Intel® AVX2,但没查到具体寄存器个数. 运行结果是 unrolling_dot_product: 24575894 in 42.7105ms simd256_dot_product: 24575892 in 126.9367ms

然后查看汇编

  • vmovaps也就是_mm256_load_ps和_mm256_store_ps有45处
  • movaps(去除vmovaps)有480-45=435处,而movaps对应_mm_load_ps和_mm_store_ps

即一个用的是avx指令集,一个是SSE指令集. 我不太了解intel给这两个指令集分配的寄存器情况, 所以我猜测性能差距来自于寄存器和计算单元数量的差异.

wangbyby 2022-12-11 01:16

还有一件事, 你需要查看下CPU型号以及对应的指令集扩展. 比如我的笔记本是i5-7300HQ, 指令集扩展是Intel® SSE4.1, Intel® SSE4.2, Intel® AVX2,但没查到具体寄存器个数. 运行结果是 unrolling_dot_product: 24575894 in 42.7105ms simd256_dot_product: 24575892 in 126.9367ms

然后查看汇编

  • vmovaps也就是_mm256_load_ps和_mm256_store_ps有45处
  • movaps(去除vmovaps)有480-45=435处,而movaps对应_mm_load_ps和_mm_store_ps

即一个用的是avx指令集,一个是SSE指令集. 我不太了解intel给这两个指令集分配的寄存器情况, 所以我猜测性能差距来自于寄存器和计算单元数量的差异.

--
👇
eweca-d: 感谢大佬,其实我看了IR之后隐隐约约感觉到确实不太对啊。貌似unroll8的时候只是调用了simd的add和mul?那按道理直接调用simd258至少一样快吧,甚至更快(我合并了mul和add到fma指令集的fmadd)。

--
👇
wangbyby: 看起来是编译器自动做了循环向量化(可以通过查看x86汇编验证) 不过为啥unroll8和simd258有差别我还得看下

作者 eweca-d 2022-12-10 20:01

感谢大佬的回复,嗯嗯,已经改成&[T]。不过那个sum8貌似影响不大,事实上我后来尝试过某个StackOverflow的技巧:

unsafe fn fn_sum8(x: __m256) -> f32 {
    let hi_quad = _mm256_extractf128_ps(x, 1);
    let lo_quad = _mm256_castps256_ps128(x);
    let sum_quad = _mm_add_ps(lo_quad, hi_quad);
    let lo_dual = sum_quad;
    let hi_dual = _mm_movehl_ps(sum_quad, sum_quad);
    let sum_dual = _mm_add_ps(lo_dual, hi_dual);
    let lo = sum_dual;
    let hi = _mm_shuffle_ps(sum_dual, sum_dual, 0x1);
    let sum = _mm_add_ss(lo, hi);
    _mm_cvtss_f32(sum)
}

感觉跟最后累加区别也不大,写的时候也是估计全程没转换回来,等到最后才累加这么一次。整个x和y的向量的长度很大很大,所以最后影响就不太大的样子。

--
👇
Neutron3529: 首先给一个建议:

所有函数签名,比如(x: &Vec<f32>, y: &Vec<f32>),里面的&Vec<T>都可以改成&[T]

另外,simd256_dot_product里面的

        let sum8: [f32; 8] = std::mem::transmute(sum8);
        sum += sum8[0] + sum8[1] + sum8[2] + sum8[3] + sum8[4] + sum8[5] + sum8[6] + sum8[7];

可能有点慢,你或许应该let mut sum8: [f32; 8] = [0f32;8];,然后用sum8存累加结果

Neutron3529 2022-12-10 18:42

首先给一个建议:

所有函数签名,比如(x: &Vec<f32>, y: &Vec<f32>),里面的&Vec<T>都可以改成&[T]

另外,simd256_dot_product里面的

        let sum8: [f32; 8] = std::mem::transmute(sum8);
        sum += sum8[0] + sum8[1] + sum8[2] + sum8[3] + sum8[4] + sum8[5] + sum8[6] + sum8[7];

可能有点慢,你或许应该let mut sum8: [f32; 8] = [0f32;8];,然后用sum8存累加结果

作者 eweca-d 2022-12-10 16:47

感谢大佬,其实我看了IR之后隐隐约约感觉到确实不太对啊。貌似unroll8的时候只是调用了simd的add和mul?那按道理直接调用simd258至少一样快吧,甚至更快(我合并了mul和add到fma指令集的fmadd)。

--
👇
wangbyby: 看起来是编译器自动做了循环向量化(可以通过查看x86汇编验证) 不过为啥unroll8和simd258有差别我还得看下

wangbyby 2022-12-10 16:07

看起来是编译器自动做了循环向量化(可以通过查看x86汇编验证) 不过为啥unroll8和simd258有差别我还得看下

👇
eweca-d: 忘记分行了,重来下:

dot_product: 2471604.8 in 7.6143ms

unrolling_dot_product: 2499994.3 in 4.4126ms

simd_dot_product: 2498251.8 in 12.8602ms

simd256_dot_product: 2499994.3 in 7.727ms

simd256_unrolling_dot_product: 2500656.8 in 7.7704ms

na_dot_product: 2499994.3 in 4.3216ms

--
👇
wangbyby: 1. 可否把时间贴出来 2. 编译选项是否为release 3. 查看编译出来的汇编/LLVMIR了吗? 建议为每个函数加上noinline

作者 eweca-d 2022-12-10 12:39

忘记分行了,重来下:

dot_product: 2471604.8 in 7.6143ms

unrolling_dot_product: 2499994.3 in 4.4126ms

simd_dot_product: 2498251.8 in 12.8602ms

simd256_dot_product: 2499994.3 in 7.727ms

simd256_unrolling_dot_product: 2500656.8 in 7.7704ms

na_dot_product: 2499994.3 in 4.3216ms

--
👇
wangbyby: 1. 可否把时间贴出来 2. 编译选项是否为release 3. 查看编译出来的汇编/LLVMIR了吗? 建议为每个函数加上noinline

作者 eweca-d 2022-12-10 12:38

大佬,你要的运行时间,release下大概率如下:

dot_product: 2471604.8 in 7.6143ms unrolling_dot_product: 2499994.3 in 4.4126ms simd_dot_product: 2498251.8 in 12.8602ms simd256_dot_product: 2499994.3 in 7.727ms simd256_unrolling_dot_product: 2500656.8 in 7.7704ms na_dot_product: 2499994.3 in 4.3216ms

加上noinline是指#[inline(never)]这个么,加了感觉没明显差别啊。

IR我着实不懂。稍微贴一点我觉得可能重要的把。

循环展开(展开8个):

  %i.010.i = phi i64 [ 0, %bb4.lr.ph.i ], [ %62, %bb4.i ]
  %27 = phi <8 x float> [ zeroinitializer, %bb4.lr.ph.i ], [ %61, %bb4.i ]
  %28 = getelementptr inbounds float, ptr %0, i64 %i.010.i
  %29 = getelementptr inbounds float, ptr %2, i64 %i.010.i
  %30 = load <8 x float>, ptr %28, align 4
  %31 = load <8 x float>, ptr %29, align 4
  %32 = fmul <8 x float> %30, %31
  %33 = fadd <8 x float> %27, %32
  %34 = add nuw nsw i64 %i.010.i, 8
  %35 = getelementptr inbounds float, ptr %0, i64 %34
  %36 = getelementptr inbounds float, ptr %2, i64 %34
  %37 = load <8 x float>, ptr %35, align 4
  %38 = load <8 x float>, ptr %36, align 4
  %39 = fmul <8 x float> %37, %38
  %40 = fadd <8 x float> %33, %39

直接循环:

%sum.07.i = phi float [ 0.000000e+00, %"_ZN81_$LT$alloc..vec..Vec$LT$T$C$A$GT$$u20$as$u20$core..ops..index..Index$LT$I$GT$$GT$5index17h0481ae6c4168febaE.exit.lr.ph.i" ], [ %46, %"_ZN81_$LT$alloc..vec..Vec$LT$T$C$A$GT$$u20$as$u20$core..ops..index..Index$LT$I$GT$$GT$5index17h0481ae6c4168febaE.exit6.i" ]
  %iter.sroa.0.06.i = phi i64 [ 0, %"_ZN81_$LT$alloc..vec..Vec$LT$T$C$A$GT$$u20$as$u20$core..ops..index..Index$LT$I$GT$$GT$5index17h0481ae6c4168febaE.exit.lr.ph.i" ], [ %44, %"_ZN81_$LT$alloc..vec..Vec$LT$T$C$A$GT$$u20$as$u20$core..ops..index..Index$LT$I$GT$$GT$5index17h0481ae6c4168febaE.exit6.i" ]
  %27 = getelementptr inbounds [0 x float], ptr %0, i64 0, i64 %iter.sroa.0.06.i
  %_21.i = load float, ptr %27, align 4
  %28 = add nuw nsw i64 %iter.sroa.0.06.i, 1
  %29 = getelementptr inbounds [0 x float], ptr %2, i64 0, i64 %iter.sroa.0.06.i
  %_25.i = load float, ptr %29, align 4
  %_20.i = fmul float %_21.i, %_25.i
  %30 = fadd float %sum.07.i, %_20.i
  %31 = getelementptr inbounds [0 x float], ptr %0, i64 0, i64 %28
  %_21.i.1 = load float, ptr %31, align 4
  %32 = add nuw nsw i64 %iter.sroa.0.06.i, 2
  %33 = getelementptr inbounds [0 x float], ptr %2, i64 0, i64 %28

手动SIMD256:

  %0 = alloca <8 x float>, align 32
  %1 = alloca <8 x float>, align 32
  %2 = alloca <8 x float>, align 32
  %3 = alloca <8 x float>, align 32
  %sum8.i = alloca <8 x float>, align 32

  ....

  %iter.sroa.0.011.i = phi i64 [ 0, %bb5.lr.ph.i ], [ %31, %bb5.i ]
  %_38910.i = phi <8 x float> [ %sum8.promoted.i, %bb5.lr.ph.i ], [ %_38.i, %bb5.i ]
  %31 = add nuw nsw i64 %iter.sroa.0.011.i, 1
  %index.i = shl i64 %iter.sroa.0.011.i, 3
  %32 = getelementptr inbounds float, ptr %4, i64 %index.i

  ...
  call fastcc void @_ZN4core9core_arch3x863fma15_mm256_fmadd_ps17hef67d92f4731afa4E(ptr noalias nocapture noundef nonnull dereferenceable(32) %3, ptr noalias nocapture noundef nonnull dereferenceable(32) %2, ptr noalias nocapture noundef nonnull dereferenceable(32) %1, ptr noalias nocapture noundef nonnull dereferenceable(32) %0)
  %_38.i = load <8 x float>, ptr %3, align 32
  call void @llvm.lifetime.end.p0(i64 32, ptr nonnull %3)
  %exitcond.not.i = icmp eq i64 %31, 1250000
  br i1 %exitcond.not.i, label %bb12, label %bb5.i

bb12: 
  %sum81.sroa.0.0.vec.extract.i = extractelement <8 x float> %_38.i, i64 0
  %sum81.sroa.0.4.vec.extract.i = extractelement <8 x float> %_38.i, i64 1
  %_50.i = fadd float %sum81.sroa.0.0.vec.extract.i, %sum81.sroa.0.4.vec.extract.i
  %sum81.sroa.0.8.vec.extract.i = extractelement <8 x float> %_38.i, i64 2
  %_49.i = fadd float %sum81.sroa.0.8.vec.extract.i, %_50.i
  %sum81.sroa.0.12.vec.extract.i = extractelement <8 x float> %_38.i, i64 3
  %_48.i = fadd float %sum81.sroa.0.12.vec.extract.i, %_49.i
  %sum81.sroa.0.16.vec.extract.i = extractelement <8 x float> %_38.i, i64 4
  %_47.i = fadd float %sum81.sroa.0.16.vec.extract.i, %_48.i
  %sum81.sroa.0.20.vec.extract.i = extractelement <8 x float> %_38.i, i64 5
  %_46.i = fadd float %sum81.sroa.0.20.vec.extract.i, %_47.i
  %sum81.sroa.0.24.vec.extract.i = extractelement <8 x float> %_38.i, i64 6
  %_45.i = fadd float %sum81.sroa.0.24.vec.extract.i, %_46.i
  %sum81.sroa.0.28.vec.extract.i = extractelement <8 x float> %_38.i, i64 7
  %_44.i = fadd float %sum81.sroa.0.28.vec.extract.i, %_45.i
  %34 = fadd float %_44.i, 0.000000e+00

--
👇
wangbyby: 1. 可否把时间贴出来 2. 编译选项是否为release 3. 查看编译出来的汇编/LLVMIR了吗? 建议为每个函数加上noinline

wangbyby 2022-12-10 09:36
  1. 可否把时间贴出来
  2. 编译选项是否为release
  3. 查看编译出来的汇编/LLVMIR了吗? 建议为每个函数加上noinline
1 共 12 条评论, 1 页