學(xué)習(xí)了關(guān)于復(fù)古游戲機(jī)模擬器的實(shí)現(xiàn)方法后溉瓶,我就有了一個思考万矾,為什么我見到的大部分模擬器都是單線程實(shí)現(xiàn)的(這里單線程指的是對CPU伤疙、MMU银酗、中斷、DMA徒像、定時器等硬件的模擬都塞在一個線程里面)(也有可能是我孤陋寡聞黍特,我只研究過比較古老的游戲機(jī)模擬器,沒有研究像3DS和Switch這種較新的模擬器)锯蛀?
雖然大部分復(fù)古游戲機(jī)的性能都很弱灭衷,現(xiàn)在的個人電腦的CPU和旗艦手機(jī)的SOC的單核性能來模擬這些游戲機(jī)綽綽有余,但是對于低成本的嵌入式SOC旁涤,情況就不一樣了翔曲,某些嵌入式SOC雖然單核性能弱,但也是多核架構(gòu)的劈愚,如果能充分利用多核的性能部默,那也是能夠流暢運(yùn)行的(理論上)。
更何況造虎,受到工藝的限制傅蹂,目前摩爾定律所描述的單核主頻的提升已經(jīng)舉步維艱了,將來模擬性能更強(qiáng)的多核游戲機(jī)算凿,模擬器的性能會更加受限份蝴。
那么多線程實(shí)現(xiàn)模擬器的挑戰(zhàn)在哪里?
我搜索過很多資料氓轰,但沒有一個確切的答案婚夫。有說多線程同步狀態(tài)太麻煩的,有說多線程性能不好的署鸡,但我覺得這些都不是本質(zhì)的原因案糙。
無論是單核的GB限嫌、GBA還是雙核的NDS,它們的硬件可以說都是并行地運(yùn)行的时捌,在硬件層面上對于內(nèi)存的讀寫操作本質(zhì)上就是沒有并發(fā)安全一說怒医,數(shù)據(jù)競爭問題那是軟件需要解決的問題。按這個道理來說奢讨,多線程模擬多個硬件似乎問題不大稚叹?
經(jīng)過我一段時間以來,閑來無事就稍微思考一下的習(xí)慣拿诸,我認(rèn)為是以下原因扒袖,以及解決辦法。
首先亩码,很多復(fù)古游戲機(jī)模擬器都使用的解釋執(zhí)行季率,類似rboy、mGBA等描沟,melonds支持JIT蚀同,但也是后面才加的。解釋運(yùn)行的好處是跨平臺很方便啊掏,畢竟使用代碼模擬opcode蠢络。但這里會有一個問題,比如rboy里面一段LD BC, d16
的操作:
let v = self.fetchword();
self.reg.setbc(v);
這里模擬一個簡單的指令就需要兩個步驟迟蜜,更別說這些步驟可能編譯出來更多的真實(shí)機(jī)器的匯編指令刹孔。我們要想模擬復(fù)古游戲機(jī)的單個指令的原子性,就必須保證解釋執(zhí)行這個指令的原子性娜睛。
要怎么保證解釋執(zhí)行指令的原子性髓霞?加鎖?用channel的方式畦戒?
要知道就算是古老的Gameboy方库,他的CPU主頻只有4194304 Hz,但通過加鎖的方式來保證原子性障斋,那一秒鐘就會有4194304次加解鎖操作纵潦,要是用channel的方式就會有4194304次內(nèi)存復(fù)制的操作,可以實(shí)現(xiàn)(我試過)垃环,但是性能會非常差邀层,掉幀掉出天際了。
那還有別的辦法嗎遂庄?
我突然想到一個思路是放棄解釋執(zhí)行寥院,改成用JIT,但前提是JIT能夠?qū)⒂螒驒C(jī)的指令一比一翻譯成目標(biāo)機(jī)器的指令涛目,這樣就可以保證模擬指令的原子性了秸谢。但是JIT有個缺點(diǎn)凛澎,就是跨平臺性很差,需要針對每種真實(shí)機(jī)器的CPU指令集都做一遍翻譯估蹄。