組合語言中的迴圈
迴圈就是把同一件事做很多次:把清單裡每個數字加總、畫出一列裡的每個像素。高階語言給你 for 和 while 這些關鍵字,但機器沒有這種詞。組合語言中的迴圈是用你已經有的兩個更簡單的零件手工搭起來的:一個標記重複區塊頂端的標籤,以及底部一道只要還沒做完就跳回那個標籤的條件分支。它就這樣一圈一圈轉,直到條件不成立為止。
走一遍一個跑十次的倒數迴圈。先把 10 放進一個計數暫存器。在頂端放個標籤,比如 loop:。做本體的工作。把計數器減一。然後是一道條件分支:若計數器仍大於零,就分支回 loop;否則就落穿到接下來的程式碼。每一趟都檢查條件,再決定重複或離開。對陣列做的 for 迴圈也是同樣的想法,只是改成遞增一個索引、再和一個界限比較;本體裡的陣列索引會用索引算出位址(基底加上索引乘以元素大小)。
迴圈之所以重要,是因為程式大部分時間都花在裡面,所以調校效能時第一個要看的就是它。把迴圈看成組語,會讓真正的成本變得可見:每一趟跑幾道指令、迴圈變數是住在暫存器裡還是每次都從記憶體重新載入、以及回邊分支的行為如何。熱迴圈底部的分支幾乎總是被採用,分支預測器很快就學會了,所以迴圈通常預測得很好,只有最後離開那一次例外。
把基底在 s0、共 n 個字組的陣列加總到 a0: li a0, 0 # sum = 0 li t0, 0 # i = 0 loop: bge t0, a1, done # 若 i >= n 則離開 slli t1, t0, 2 # t1 = i * 4(一個字組 4 位元組) add t1, s0, t1 # 第 i 個元素的位址 lw t2, 0(t1) # 載入元素 add a0, a0, t2 # sum += element addi t0, t0, 1 # i++ j loop done:
一個 for 迴圈:頂端一個標籤、一道離開分支、本體、遞增,再加一道跳回頂端的跳躍。
陣列索引必須考慮元素大小:索引 i 處的 4 位元組字組位於 base + i*4,而非 base + i。忘了把索引按比例放大,是經典的「差幾個位元組」錯誤。