【深圳 IO 攻略】番外篇:上電時設(shè)置符號以提高運行效率

本文首發(fā)于 B 站《深圳 IO》文集(https://www.bilibili.com/read/readlist/rl569860)。原創(chuàng)不易,轉(zhuǎn)載請注明出處。
上一個番外篇中,我們提到了“終止條件前置法”去掉了簡單循環(huán)中的 jmp 指令,大幅提高了循環(huán)的運行效率。但是,此方法有一些局限性:首先,進入準備工作前一定要滿足循環(huán)終止條件;其次,循環(huán)結(jié)束后不能有難以消化的收尾工作,要么沒有收尾工作,要么確保第一個周期額外執(zhí)行的收尾工作沒有副作用,不能畫蛇添足。這次我要提的是另外一種優(yōu)化循環(huán)的方法:上電設(shè)置符號法。這種優(yōu)化方法不能減少代碼行數(shù),但普適性更強。它不要求進入準備工作前一定要滿足循環(huán)終止條件,同時對于有收尾工作的循環(huán),也能很好地優(yōu)化。
我們首先說明一下帶收尾工作的循環(huán)結(jié)構(gòu):
如果我們在準備工作前,加一條上電執(zhí)行的,一定成立的判斷語句的話,代碼就會變成下面這個樣子:
上電時激活 + 號指令,執(zhí)行準備工作。進入循環(huán)后,只要不滿足循環(huán)終止條件,芯片就一直處于 - 號激活的狀態(tài),就一直只執(zhí)行循環(huán)體相關(guān)代碼。直到滿足循環(huán)終止條件后,+ 號指令激活,執(zhí)行收尾工作,然后再跳回第 2 行執(zhí)行下一次任務(wù)的準備工作。
和“終止條件前置法”一樣,優(yōu)化前循環(huán)體執(zhí)行 n 次就要執(zhí)行 n-1 條 jmp 指令,優(yōu)化后多了一條必然成立的判斷,同時去掉了 jmp 指令,共計能省下 n-2 格電??紤]到幾乎沒有只執(zhí)行一次的循環(huán),那么 n-2 以非負數(shù)居多。不增加成本、不增加代碼行數(shù),但是電量卻降下來了。優(yōu)化后的方案是完爆優(yōu)化前的方案的。
示例 1:第 22 關(guān)《加密貨幣存儲終端》
原版攻略:【深圳 IO 攻略】第 22 關(guān):加密貨幣存儲終端
上一個番外篇里,我說過,第 22 關(guān)的循環(huán)無法使用“終止條件前置法”優(yōu)化,因為循環(huán)結(jié)束后有收尾工作,且收尾工作在第一個周期額外執(zhí)行時會產(chǎn)生副作用。但是這一關(guān)的循環(huán)是可以用本期番外篇里提到的“上電設(shè)置符號法”來優(yōu)化的。


注意右邊芯片的這個循環(huán),由于第 5 行寫的是 - jmp 2,因此很明顯,第 2~5 行是循環(huán)體。然后,循環(huán)體上方的部分(第 1 行)是準備工作,循環(huán)體下方的部分(第 6~7 行)是收尾工作。我們按照上面所說的優(yōu)化模板對該芯片做“上電設(shè)置符號法”的優(yōu)化,將芯片改寫成如下代碼:

①準備工作前添加 @ teq 0 0;②準備工作和收尾工作全部帶上 + 號;③循環(huán)體去掉 jmp。三步曲一氣呵成。這里要注意一下,終止條件由原先的 tcp acc 7 改成了 teq acc 7,因為要確保終止時【激活 + 號指令】,而不是像之前一樣【不激活 - 號指令】就行。
點擊左下角的【模擬】,稍等片刻,便會彈出結(jié)算界面:

成本和代碼行數(shù)沒有增加,電量卻活生生地省下來了。這就叫完爆。
順便說一下 459 電量是怎么做到的。這是因為修改了左邊芯片的代碼。左邊芯片的代碼改成了這個樣子:

官方謎題里的所有非阻塞 x 口輸入,第一秒一定是無數(shù)據(jù)(-999),所以將睡眠指令移動到第一行以節(jié)省電量。teq x1 0, + jmp 7 這兩行代碼合并成了一行 tcp x1 0,這樣每當(dāng)從 DX-300 讀到 0 時,就關(guān)閉一切 + - 號指令,直接跳到第 7 行,完全不需要手寫?jmp 指令跳轉(zhuǎn)。從第 7 行開始一直到最后是一個三態(tài)判定。只是,這里如果用 tcp 做三態(tài)判定的話需要多寫兩條 jmp 指令,會有效率浪費。因為讀到 -999 時是不執(zhí)行任何操作的,我們關(guān)心的只有【大于?-1】和【等于 -1】這兩態(tài)。從讀卡器中讀到了大于 -1 的數(shù)字時,將一位卡號寫入 RAM(mov x0 dat, tgt dat -1, + mov dat x2);從讀卡器中讀到了 -1 時(- tlt dat -1, 既不大于 -1 也不小于 -1 則等于 -1),將已存金額發(fā)給右側(cè)的芯片,然后清空 acc(- mov acc x3, - sub acc)。

由于左邊的芯片去掉了 3 條 jmp 指令,533 電量再次驟降到 459 電量。
示例 2:第 28 關(guān)《防劇透耳機》
原版攻略:【深圳 IO 攻略】第 28 關(guān):防劇透耳機
第二版【哈希表】方案因為不涉及循環(huán),所以不是我們的優(yōu)化目標。我們這次需要優(yōu)化的是第一版【線性查找】方案。

第 12 行的代碼是 jmp 3,那么很明顯第 3~12 行是循環(huán)體,在此之前的是準備工作,在此之后的是收尾工作。那么還是同樣的三步曲,優(yōu)化后的代碼如下:

原先的 tcp x3 0 是“循環(huán)繼續(xù)條件”,ROM 地址大于 0 時跳回去繼續(xù)比對,直到 ROM 地址歸零時循環(huán)結(jié)束。那么,按照本篇里的優(yōu)化要求,“循環(huán)繼續(xù)條件”(ROM 地址大于 0)要變更成“循環(huán)終止條件”(ROM 地址等于 0),所以優(yōu)化后,原先的 tcp x3 0 變成了 teq x3 0。
另外還要注意一下,大循環(huán)體里還套了一層小的靜音循環(huán)(第 8~10 行,上一版方案里是第 7~9 行),優(yōu)化后 jmp 的行號也要跟著一起變(由 jmp 7 變成了 jmp 8)。

不鳴則已,一鳴驚人。就這么簡簡單單地優(yōu)化一下,1.4K 電量減少到了 1.1K,減少了 300 格電,已經(jīng)和早期未微操的哈希表效率相當(dāng)了。
示例 3:阿瓦隆城第 1 關(guān)《冷庫機器人》
原版攻略:【深圳 IO 攻略】阿瓦隆城第 1 關(guān):冷庫機器人
注意一下 2 號數(shù)據(jù)庫管理員芯片:

第 3~5 行是循環(huán)體,在此之前的是準備工作,在此之后的是收尾工作。三步曲走起,優(yōu)化成如下的樣子:


好吧,同樣的優(yōu)化方案在這道題里只省下了 1 格電量。原因是冷庫最多只有 6 格,所以隨便找找就能找到空位了,平均只少執(zhí)行了兩次 jmp 指令。不過聊勝于無,能省一格電也不錯。
無法優(yōu)化的情形
如果準備工作/收尾工作里有判斷或循環(huán),導(dǎo)致這兩部分的代碼不能完全帶上 + 號覆蓋的話,就無法使用“上電設(shè)置符號法”來優(yōu)化。因為這和我們的前提相悖。本優(yōu)化方案要求準備工作和收尾工作的代碼都必須帶上 + 號前綴。
舉例,第 26 關(guān)《電子門鎖》:


第 2~10 行構(gòu)成了循環(huán),在此之前的是準備工作,在此之后的是收尾工作。但是收尾工作卻出現(xiàn)了分叉:僅當(dāng) acc 不為 11 時才向 p1 發(fā)送 6 秒的 100 信號(teq acc 11, - gen p1 6 0)。然后分叉又合并了,不論 acc 的值為多少,都要將 RAM 地址和 acc 自己清零(mov 0 x3, sub acc)。mov 0 x3 這條指令倒是可以放在判斷的前面,但是 sub acc 這條指令萬萬不可前置,因為這樣會丟失 acc 的信息,導(dǎo)致接下來的 teq acc 11 這條判斷返回恒假。因此,本題的收尾工作必然是“出現(xiàn)分叉后再合并”。然而,本優(yōu)化方案要求準備工作和收尾工作必須全部帶上 + 號,這就意味著,一旦在這些地方出現(xiàn)了分叉,就不能將分叉再合并回來。本方案的算法邏輯和本優(yōu)化方案的要求相悖。
再舉例,第 29 關(guān)《變色鞋》:

注意一下右邊的芯片,第 3~12 行構(gòu)成了循環(huán),在此之前的是準備工作,在此之后的是收尾工作。準備工作沒有分叉,可以全部帶上 + 號,但是收尾工作卻是一個循環(huán)清零的工作。正是這個收尾時的循環(huán),導(dǎo)致了本優(yōu)化方案的落空。因為,收尾工作全部帶上 + 號時,你也只能用 + 號開頭的 jmp 在收尾階段執(zhí)行循環(huán)。而一旦結(jié)束循環(huán)后,整個芯片的狀態(tài)會變成 - 號激活,就無法繼續(xù)執(zhí)行同樣是帶 + 號前綴的準備工作了。