我目前正在try 优化我为一个24x24矩阵三角化程序编写的一些MIPS汇编程序.我目前的目标是利用延迟分支和手动循环展开来try 减少循环.Note: I am using 32-bit single precision for all the matrix arithmetic.

算法的一部分涉及我试图展开的以下循环(N始终为24)

...
    float inv = 1/A[k][k]
    for (j = k + 1; j < N; j++) {
        /* divide by pivot element */
        A[k][j] = A[k][j] * inv;
    }
...

我想要

...
    float inv = 1/A[k][k]
    for (j = k + 1; j < N; j +=2) {
        /* divide by pivot element */
        A[k][j]     = A[k][j]     * inv;
        A[k][j + 1] = A[k][j + 1] * inv;
    }
...

但它产生了错误的结果,我不知道为什么.有趣的是,使用循环展开的版本正确地生成了矩阵的第一行,但其余的不正确.没有循环展开的版本可以正确地对矩阵进行三角化.

以下是我的try .

...

# No loop unrolling
loop_2:
    move    $a3, $t2          # column number b = j (getelem A[k][j])
    jal     getelem           # Addr of A[k][j] in $v0 and val in $f0
    addiu   $t2, $t2, 1       ## j += 2
    mul.s   $f0, $f0, $f2     # Perform A[k][j] * inv
    bltu    $t2, 24, loop_2   # if j < N, jump to loop_2
    swc1    $f0, 0($v0)       ## Perform A[k][j] := A[k][j] * inv

    # The matrix triangulates without problem with this original code.

...
...

# One loop unrolling
loop_2:
    move    $a3, $t2         # column number b = j (getelem A[k][j])
    jal     getelem          # Addr of A[k][j] in $v0 and val in $f0
    addiu   $t2, $t2, 2      ## j += 2
    lwc1    $f1, 4($v0)      # $f1 <- A[k][j + 1]
    mul.s   $f0, $f0, $f2    # Perform A[k][j] * inv
    mul.s   $f1, $f1, $f2    # Perform A[k][j+1] * inv
    swc1    $f0, 0($v0)      # Perform A[k][j] := A[k][j] * inv
    bltu    $t2, 24, loop_2  # if j < N, jump to loop_2
    swc1    $f1, 4($v0)      ## Perform A[k][j + 1] := A[k][j + 1] * inv

    # The first row in the resulting matrix is correct, but the remaining ones not when using this once unrolled loop code.

...

推荐答案

展开的C循环条件是错误的.

j < N; j +=2 can start the loop body with j = N-1,
accessing the array at A[k][N-1] (fine) and A[k][N] (not fine).

One common method is 100,或一般j < N-(unroll-1).但是对于无符号N,在开始循环之前还必须单独判断N >= unroll,因为N-1可能会换行为一个巨大的无符号值.

对于C编译器来说,保持j < limit通常是好的,而对于j + 1 < N,这是他们必须计算的另一件事.并且还可以阻止编译器证明循环对于无符号计数(比如size_t)不是无限的,因为这被定义为环绕,所以N=UINT_MAX可能会导致条件始终为真,具体取决于起始点.(例如,j=UINT_MAX-2表示UINT_MAX-1 < UINT_MAXj+=2表示0 < UINT_MAX,同样正确.)因此,这与使用j <= limit表示未签名计数器的问题类似.编译器很想知道循环何时可能是无限的.对于一些人来说,如果行程计数在第一次迭代之前无法计算,它将禁用自动矢量化.


如果j是从0开始的,如果N保证是展开因子的倍数,那么你就可以避开一个松散的条件.但正如内特指出的,这里是不同的.


efficiency of your MIPS asm

通常,循环展开的重点是性能.在循环中对helper函数的非内联调用有点达不到目的.

jal getelem我想是不是用一个指针和两个整数做了一些乘法之类的事情来重新建立索引?请注意,您正在沿着一行中的连续内存进行扫描,因此您可以只增加一个指针.

计算一个要比较的结束指针,这样你的MIPS循环看起来就像

 # some checking outside the loop, maybe with a bxx to the end of it.
 looptop:                  # do{

    lwc1   $f2, 0($t0)
    lwc1   $f3, 4($t0)
    addiu  $t0, $t0, 4*2      # p+=2     advance by 8 bytes, 2 floats
    ...
    swc1   something, 0($t0)
    swc1   something, 4($t0)
    bne    $t0, $t1        # }while(p!=endp)

   # maybe another condition to check if you should run one last iteration.

MIPS bltu只是一条伪指令(sltu/bnez);这就是为什么最好计算一个精确的结束指针,这样就可以使用一条机器指令作为循环分支.

是的,这可能意味着将迭代计数取整为2的倍数,以确保正确性.或者进行一次标量迭代,将up四舍五入到2的倍数.e、 g.x++/x&=-2;

使用软件管道,例如,进行加载和拆分,但还没有存储,如果奇数,您可能会让取整让循环重做该元素.(如果分支预测失误的成本高于FP乘法和冗余存储.)我还没有完全考虑过这一点,但这与SIMD先做一个未对齐的向量,然后做一个可能部分重叠的对齐向量的 idea 类似.(例如,SIMD矢量化类似于展开,但随后会回滚到一条包含4个元素的指令中.)

C++相关问答推荐

为什么PLT表中没有push指令?

了解一些CLIPS原语数据类型

C中是否有语法可以直接初始化一个常量文本常量数组的 struct 成员?

如何将字符串argv[]赋给C中的整型数组?

减法运算结果的平方的最快方法?

当我更改编译优化时,相同的C代码以不同的方式运行

struct -未知大小

理解C版宏(看起来像未声明的变量?)

For循环中的变量行为不符合预期.[C17]

如何在C中使数组变量的值为常量?

GCC错误,共享内存未定义引用?

赋值两侧的后置增量,字符指针

C中的空指针是什么(_N)?

使用mmap为N整数分配内存

分支预测和UB(未定义的行为)

无法理解 fgets 输出

std::malloc/calloc/realloc/free 与纯 C 的 malloc/calloc/realloc/free 有什么不同

将数组返回到链表

在 C 中的 scanf() 格式说明符中使用宏获取字符串长度

strlen 可以是[[未排序]]吗?