Regex 點靠 544 條搜尋取代規則,砌出一部跑到 Doom 嘅虛擬電腦
3C 產品

Regex 點靠 544 條搜尋取代規則,砌出一部跑到 Doom 嘅虛擬電腦

圖片:via Tom's Hardware — https://www.tomshardware.com/software/programming/daring-coder-gets-doom-running-with-regular-expressions-at-180-seconds-per-frame-like-playing-correspondence-chess-with-a-shotgun-nearly-14-million-substitutions-to-render-a-frame-at-80-000-substitutions-per-second
TechLab 編輯部(譯)·

CPU、RAM 同畫面都塞入 96.6 MB 文字,再逐步改寫

真正做緊運算嘅係規則

「用 regex 跑 Doom」聽落好似寫咗一條巨大 pattern,match 到遊戲之後畫幅圖出嚟。實際結構精密好多:開發者 Artem Lytkin 準備咗 544 條固定、排好次序嘅搜尋取代規則,driver 由頭檢查,第一條成功 match 嘅規則每次只改寫一次,跟住再由頭行過。規則喺開始前已用 SHA-256 鎖定,運行途中唔會按 Doom 狀態偷偷加新邏輯。

呢種做法屬於 iterated string rewriting,概念接近經典 Markov algorithm。只要規則可以按狀態分支,又可以反覆改寫字串,理論上已經做到通用運算。Turing complete 只代表表達能力夠,冇承諾速度合理;doom-regex 有趣嘅位,係作者真係將抽象理論砌成一部跑得郁大型程式嘅虛擬電腦,仲花大量工夫證明結果冇出蠱惑。

doom-regex 一邊逐步畫出 Doom 場景,一邊顯示正在觸發嘅搜尋取代規則

圖片:doom-regex 項目頁

96.6 MB 文字就係整部機

虛擬 CPU 叫 RVM-1,成個 machine state 係一串 96.6 MB 純文字。開頭 header 擺 program counter、register、clock 同運算階段,後面再分成 lookup table、平面 RAM、程式、framebuffer、WAD、高位記憶體同 I/O 區。換句話講,某個 register 入面嘅數字、牆身下一點畫咩顏色,以至 Doom 關卡資料,都只係字串入面唔同位置嘅字符。

一條規則改寫 program counter,另一條就按新地址攞 instruction;load、store、加減數同輸入輸出亦係同一套路。加法用 512 項 full-adder lookup table,八次 lookahead 逐段處理 32-bit 數值,carry 就靠 capture group 傳落去。乘除法拆成多個 micro-phase,header 入面嘅 PH 欄位記住行到邊一步。呢啲結構其實好似用文字同 pattern 手砌 microcode。

RAM 點解唔使每次掃足 96 MB

最棘手嘅部分係 memory access。每次 load 都由頭掃過近百 MB 文字,速度肯定捱唔住。作者將地址每一位變成 regex conditional jump,砌成一棵 binary tree,再按地址計出要跨過幾多字符,直接跳到 RAM slot;instruction fetch 亦用相同方法按 program counter 搵 opcode。配合 PCRE2 JIT、dotall jump、平面記憶體區同避免重複複製長字串前綴,速度先由初版每秒 7 次改寫,升到作者錄得嘅每核心約 80,000 次。

Doom 點樣變成呢串文字

作者冇逐條 regex 重寫 Doom。項目先用方便移植嘅 doomgeneric,再經修改版 8cc 將 C code 編成 ELVM IR,跟住轉成 RVM-1 明白嘅 instruction。544 條規則扮演固定 CPU,編譯後嘅 Doom 就係 CPU 要執行嘅程式資料;程式大咗主要會令 state 字串變長,規則數量唔使跟住增加。ELVM 本身正正係用簡化 IR,將 C 程式送去各種古怪語言或運算模型,呢次加個 regex CPU backend 亦相當順理成章。

呢條編譯鏈亦解釋咗點解項目有意思。好多「某某裝置跑到 Doom」其實靠串流畫面、藏起 emulator,或者只播預先生成內容。doom-regex 個 driver 當然仍然靠正常電腦同 PCRE2 JIT 搵 match,但改變 machine state 嘅算術、跳轉同記憶體操作都寫喺固定規則內;刪走 ruleset,剩低嘅 96.6 MB 文字就唔識自己行。

每幀三分鐘,數字要睇清測試條件

作者公開嘅 E1M1 timedemo 第 60 幀,要 13,994,067 次 substitution。按項目用 PCRE2 JIT 製作片段時錄得嘅速度,每個 CPU core 每秒大約做到 80,000 次改寫,計出嚟約需 175 秒,亦即接近官方所講三分鐘一幀。製作 100 幀片段就用咗約 12.5 億次改寫,production jobs 分散到五部電腦處理。呢啲數字受 CPU、PCRE2 build、JIT 同工作分配影響,唔應該當成所有電腦都會有嘅固定 benchmark;不過每一步都要等上一個 state 完成,單一執行流程始終冇得並行。

項目亦有兩層核對。作者話 Python reference emulator 會執行同一套 instruction set,每一次 substitution 之後都逐 byte 比較完整 state;畫好之後,再將 framebuffer 嘅 SHA-256 同 native build 對照。公開測試連續核對 100 幀,結果全部一致。呢套驗證比「個畫面睇落似 Doom」扎實得多,亦令項目由趣味 demo 變成一個幾完整嘅 computation model 實作。

呢個實驗證明咗咩

Regex 喺呢度冇突然變成實用 game engine,PCRE2 JIT 同現代 CPU 亦幫手做咗大量高速 pattern matching。不過項目清楚展示:CPU 本質上可以理解成一套按狀態套用、逐步改寫資料嘅規則,register、RAM 同 framebuffer 只係大家同意點樣解讀嘅編碼。對開發者嚟講,最值得睇係作者點樣處理 state layout、random access、compiler backend 同 lockstep verification;Doom 主要負責提供一個夠複雜、人人一眼認得嘅測試程式。


參考來源

本文根據原文及公開資料整理;資料有出入時,以原文及官方資料為準。

分享:WhatsAppThreadsTelegramFacebook