第十七屆“二十一世紀的計算大會”
圖靈獎得主Leslie Lamport主題演講
大家好!今天我想和大家分享的是用數學的眼光看計算機系統。由於時間有限,我就從最簡單的系統——時鐘講起,這是最簡單的數字化系統。事實上,我不是講解真正意義上的時鐘,而是一位元的時鐘。在工程師眼裡,一位元時鐘是這個樣子的:它是一個物理系統,用電壓來表徵,是時間的函式。
相信只要學過物理或具備基本的科學常識,你就能看懂這樣的圖表。它的行為用電壓來表徵,是時間的函式。現在,我們把它轉變成數字化系統。
從理想化的角度來看待時鐘的行為,無視這個模糊而複雜的時間和電壓圖,我們假設只有兩個數值0和1。時鐘能夠從一個狀態瞬間切換到另一個狀態。這樣的系統就稱為數字化系統。接著我們把連續的電壓曲線與時間去掉,大家不需要關注時間推進的速度是否正確,只需要關心從0到1,從1到0,週而復始的過程。也就是說我們不需要關心時鐘具體怎麼走,只是把時鐘系統表述為一個序列或是一種行為,兩種狀態構成的一個序列,而這個狀態只是將數值分配到各個變數。
在我們這個例子裡,只有一個變數,簡稱為v。在這裡,有一個具體方法來指定各個狀態序列。首先指定一個初始狀態,然後是狀態切換 —— 如何從一個狀態轉換到另一個狀態則可以用多種方式表述,如程式設計符號。
指定初始狀態,指定v的值為0,然後狀態切換。這可以用方程式來表述,也可以用自動機的方式來表述,比方說這樣,用小圓圈表示各個狀態,用箭頭表示各個狀態之間的切換方向,用有趣的圖片表示初始狀態。這就是人類發明的各種怪異的計算機語言。
我是數學家出身,所以我想採用一種我更擅長的方法——數學來說明。指定初始狀態,所指定的不過是一個狀態集。狀態切換就是兩種狀態之間的關係。集合和關係是數學的核心。那我們如何表示這個狀態集合呢?我會用一個謂詞,或是一個公式來描述狀態集合。狀態是為變數賦值,而狀態的謂詞則是包含這些變數的公式。例如,這是一個表述初始狀態的公式。非常簡單。這是兩個狀態之間的關係,這個也可以表述為一個謂詞,要注意這是基於一對狀態,而非一個狀態。所以,我用一種方法,你們也可以用很多不同的方法,本質上是同一種基本方法,即用一個公式既包含舊狀態,又包含新狀態。我會用primed的變數和unprimed的變數。unprimed的變數表述初始狀態下的變數值。Primed的變數表述第二個狀態下的變數值。
我們來舉一個例子。數值v和新狀態中v prime等於舊狀態中v的值加1對2取餘,這就是數學的表示方式。所以我很明白地描述了一個簡單的系統,不用語言,不用圖片,什麼都沒有,只用了兩個簡單的公式。還有什麼能比這更簡潔?
用一個公式來說明,要實現這一點,就要引入一個時序邏輯(Temporal Logic)。時序邏輯公式不是關於狀態或狀態對的謂詞,而是行為的謂詞。記住,行為只是狀態的序列。狀態只是對變數的賦值。這很好理解。所以公式類似v prime等於v加1對2取餘,記住這個公式是狀態對的謂詞。所以,這個時序公式為真的前提是當且僅當行為對前兩個狀態為真。作為一種特殊情況,v=0中沒有primed的變數。所以這只是第一個狀態的謂詞。所以當且僅當第一個宣告中v=0為真時,它才為真。
接下來,我要介紹一個單一時序,公式,一個時序運算子,建構函式運算子。當且僅當建構函式F對某種行為和它的所有字尾為真時,建構函式F才為真。所以例如建構函式v=0有什麼含義?當且僅當v=0在對行為的每一個字尾為真時,建構函式v=0才對行為為真。不過v=0對行為為真究竟是什麼意思?它的意思是,當且僅當在行為的初始狀態下為真時v=0才成立。所以當且僅當v=0對行為的每一種字尾初始狀態均為真時,v=0才成立。行為的每一個狀態都是某個字尾的初始狀態,字尾的行為在該狀態下開始。所以當且僅當v=0對行為的每一個狀態都為真時,v=0才成立。所以,這裡的原理都很簡單。所以,我要做的就是把這兩個公式利用這個建構函式運算子,把它們轉換成一個單一的公式。那麼,這代表什麼呢?這個公式對於這個行為為真。這是兩個公式的結合。當且僅當兩個公式都成立時才成立。所以,第一個公式v=0表示在行為的第一個狀態中v=0。其次下一部分,建構函式,表示對於行為中的所有狀態組合v prime等於v加1對2取餘成立。所以,這與我先前用表述的完全一致。只用一個公式。
我們現在研究一下更復雜的情況,複雜度提高一倍,一個兩位元的時鐘。我們先從一位元時鐘開始,用電壓v和t來表示。我們現在加一個低階位。就變成了這樣。
不過這還是一個數字化系統,這樣我們就可以從實際的物理行為進行抽象,從理想化的角度認為各個狀態是離散的。這樣,就有兩個變數,分別是高階位數v和低階位數w,或者兩個變數非0即1。這樣我們就得到了系統行為,v、w均從0開始,然後低階位開始變化,再變為0,然後高階位開始變化,以此類推。
但如果是一個二位元時鐘,假設忽略第一個低階位,那麼就變成一位元時鐘了。也就是說,如果把時鐘上的第二個指標遮掉,那麼這就是一個由小時和分鐘組成的時鐘。好,兩位時鐘的行為是這樣的。如果忽略w,那麼就是一個一位元時鐘。所以這就應該是一位元時鐘的行為。
不過這裡有些不對勁。注意我們有兩個狀態v=0,有兩個狀態v=1等等,但這些步驟是不允許的,因為每一個步驟都要滿足v prime等於v加1對2取餘。為了解決這裡出現的問題,我們就要讓公式允許“啞步”,也就是說不會改變v的值的步驟。因為歸根結底,狀態描述的是系統,只要狀態不變,什麼都不會變。也就是說,如果你面前有這麼一個一位元時鐘,你根本不知道是否有低階位發生變化。所以我的做法是改變規則,允許另一種情況。每一步要麼滿足v prime等於v加1對2取餘,要麼滿足v prime等於v。所以我經常會這麼做,引入一個符號,這個小方括號帶有下標,意思是“或下標未發生變化“。那麼問題就來了,這一規則允許一種需要若干步驟才能完成的行為,但什麼都不幹,只是頓步。
這代表一個已經停止的時鐘,也就是時鐘的高階v指標停掉了,雖然時鐘可能發生這樣的事,但我們並不想讓時鐘停下來,我們就要加一個公式,永遠禁止頓步。把這些寫下來,我用兩個公式描述這個時鐘。第一個公式描述的是穩定部分,即時鐘的有限行為。然後另一個公式描述活躍部分,即描述行為的無限字尾。換一種說法,時鐘的穩定部分表示允許發生的情況,而活躍部分表示的是必須發生的情況。對於一位元時鐘,我其實可以把公式寫成這樣。如果你們還記得剛才寫下的那個公式,想一下那個建構函式表示什麼含義,仔細思考,你們就會發現,需要無限的步驟才能使v變化,也就是說需要演算無限長的時間。實話告訴大家,我大概得花上15分鐘時間才把這些活躍度屬性的一般形式寫出來。時間關係,我們這裡就不管活躍度了,穩定性是我們首先考慮的,也是大家一般首先擔心的。我們首先需要確保系統不會犯錯,不會產生錯誤的解答,然後再去考慮給出某種答案。為了方便之後使用,我把這個公式命名為時鐘公式。
現在,來描述另一個更有趣的系統。事實上,這是一個很重要的硬體訊號程式設計系統,稱為兩步握手系統。大家知道,這樣一個系統可能位於電腦中的多個位置。各個程式之間利用兩根一位線路通訊,我將其分別稱為p和c。
這裡有兩個程式,我將其分別稱為嘀(Tick)和嗒(Tock)。初始謂詞表示,p和c均等於0,我們稱其為I。下一個狀態的關係是兩個公式的分離。
有兩種可能的步驟型別,需要完成一個嘀步驟或嗒步驟。嘀步驟在p=c時執行,與p形成互補,可從0變到1,或從1變到0,就像一位元時鐘一樣。同時使c保持不變c的新值c prime等於原值。嗒與嘀類似,不過嗒步驟在p等於c prime變數時也可能發生。所以在嗒步驟中初始狀態下p與c不同,c加上1對2取餘。p保持不變,將其記作N。這樣,握手協議就可以用這個公式描述,即初始謂詞建構函式,下一個狀態是子p和c的謂詞。記住,子p和c的含義是p prime和c prime 等於p和c,對一個表示式取質數,就表示對錶達式中的所有變數取質數。所以就相當於說p prime c prime 等於p c只有在p和c都各自相等時這一對變數才相等。這就相當於說,p prime 等於p,c prime 等於c 換句話說,這是一個頓步。這也就是說,每一步都是一個N步,或者使p和c保持不變,這樣就有了兩步握手協議。如果你們能想通,你們可以看到只有一個行為,沒有頓步,看起來大致是這個樣子。
好,現在數學公式有了。這個表示式有一點很好,就是可以進行各種神奇的演算,可以演算出很多東西。我要做的就是定義這個表示式。我將其稱為v ̅,定義為p加c對2取餘。v ̅是這些變數的函式,因此在每一個狀態下v ̅都有數值。
可以想見會發生什麼。事實上兩步握手對v ̅造成的變化與一位元時鐘對v造成的變化完全一樣。我們可以證明這一點,這也是數學的奇妙之處——你總是能夠證明。不過,我們首先要從數學上說明其中的具體含義。現在來定義(Clk) ̅,公式指定了(Clk) ̅,除了v ̅替換為v。數學公式美妙的地方就在於一種非常重要的運算——替換,無論是程式語言還是自動機之類的都無法進行替換運算,但數學可以。那麼我們把帶v ̅的(Clk) ̅替換為p,把v ̅的定義替換成v。所以兩步握手系統,使v ̅發生的變化,與一位元時鐘使v發生的變化一樣,只有在行為滿足這個(Clk) ̅公式時這一點才成立。所以兩步握手系統使v ̅發生變化,與一位元時鐘使v變化的方式一樣,表明每一位都滿足公式Hsk,滿足(Clk) ̅公式。或換一種說法,在數學裡Hsk公式表示(Clk) ̅。所以,這個文字表述有著簡單明確的數學含義。
我可以花上10分鐘來寫出這個定理的完美證明,簡單而嚴謹的證明不是我們的目標。大家也知道,工程師主要是設計系統的,嚴謹、簡單的證明不是你們的目標。不過這些證明表明你們把事情做對了。如果能夠給出證明,握手協議代表時鐘協議應該不成問題。如果握手協議不能代表時鐘協議,那就有問題了。那麼這個公式代表什麼含義?兩步握手在替換情形下成為了一位元時鐘?其實換一種說法,你們實現或最佳化了一個語句。替換是一個基本的數學概念,在你說某某成為了某某時,這是實現的基礎。從基礎角度說,有一個定理跟這個定理很像,Hsk代表(Clk) ̅。在程式語言中沒有替換的概念。你們可以試試看在賦值語句中替換x,讓x獲得一個值,這完全行不通。但你可以在自動機下替換,或在一些古怪的計算機語言下進行替換。
有一種語言就可以實現替換。這種語言叫做TLA+,原因很有意思。在TLA+中寫握手協議,就是這個樣子的。跟我之前寫的基本差不多。當然了,也並非完全一樣,因為在TLA+裡寫了取餘,而沒法寫p=c=0。這是一個縮寫,代表兩個公式p=0和c=0。宣告變數,p和c為變數。這將匯入整數模運算,如加和百分比運算,嵌入到這一語言中。這些運算在標準模組中定義,獲得例示,並匯入到幾乎每一個規範中。這有點像樣板檔案。這是這個兩步握手協議的TLA+規範。這是一個精美的印刷版本,但其實是你們實際打出來的。你麼可以看到,差別不是很大。這就是全部了。如果你問型別宣告在哪裡?事實上沒有型別宣告。你們在數學課上有看到過型別宣告嗎?在某種意義上,型別是一種普通、簡單的數學,型別的正確性其實是一個很容易證明的定理。型別的正確性表示p和c始終為0或1,而這就是定義。握手協議代表p始終是0、1集合的一個元素。c是0、1集合的一個元素。還有一個小程式定理來證明,或讓我們的工具來檢驗小定理。
時間有限,我只能給大家演示一個簡化的樣例,而TLA+是一門真正的語言,而不是簡化的案例。它擁有工業級強度的工具。有一個模型檢驗功能。無窮盡地對系統的小例項進行檢驗。如果你們試過,你們就知道這不會很有意思。我有這個千處理器系統,我可以用它建立三處理器系統,但這對發現bug非常有效,因為在這三個處理器系統中可以檢測所有可能的行為。另外還帶有證據檢驗功能。證明真實系統的正確性總是耗資巨大,在實踐中不具備可行性。比如現在環繞彗星的航天器,就是歐洲宇航局的羅塞塔太空飛船,是一個模擬,可以顯示彗星表面的登陸車。Virtuoso實時作業系統控制羅塞塔上的多個儀器裝置。這個東西就是用TLA+設計的。
這些都是Virtuoso開發團隊負責人說的。之前我對此一無所知。這個專案已經進行了10年左右,而我在幾個月前才聽說。我給Eric Verhulst寫了一封信,詢問體驗如何。他是這麼答覆的。他說TLA+的抽象功能大大提高了架構的簡潔度。我很高興地聽他說,對比多年的C語言程式設計獲得的一手結果,程式碼規模是一個先前系統版本程式碼規模的十分之一。這很大程度上要歸功於他們採用了TLA+進行系統設計和思考。
TLA+在亞馬遜網站網頁服務的系統設計中經常用到,幾年前還在14個真實的系統中使用,這些系統是亞馬遜網路服務的組成部分。在《ACM通訊》雜誌4月刊上有一篇文章描述了他們使用TLA+時的體驗。英特爾的四個設計小組也使用過TLA+。我在英特爾的朋友已經離職,所以我不知道他們現在在做些什麼。微軟也用過TLA+。微軟工程師Dave Langworthy是這樣說的:利用高中學的簡單數學就可以找到程式中的缺陷,而在一個實際執行的伺服器上,除錯幾乎是不可能的。如果幾年前發現了這些錯誤,我們有充分的時間來修復。我們利用的東西都是在高中最晚大學就能學到的簡單方法,算術、集合、函式、一階邏輯等等,再加上prime和建構函式,這門語言中唯二小學數學裡沒有的東西。如你們所見,非常簡單。所以,典型的系統規範,儘管一般要比兩步握手大一點,不過形式是一樣的——初始謂詞,建構函式,下一狀態關係,變數集合下標以及活躍度公式,大約都是十個這樣的變數。不過,變數可能是一組記錄,對數的函式,或相當複雜的東西;初始謂詞只是一些非常簡單的數學;規範的主要部分是下一狀態關係。我的意思是,不僅僅兩面握手協議的6行程式是這樣,一個典型工業規範中的一千行程式也是這樣。
不過,這仍然是簡單的數學,prime,加上簡單的算術和一些集合、符號,諸如此類的時態邏輯可能有大約15個時態邏輯。工程師往往根本不管活躍度,因為他們覺得穩定性部分是最有可能發生錯誤的。他們更有可能構建一個顯示錯誤數字的時鐘,而不是一個停走的時鐘。所以需要在這方面採取措施,這也是規範的主要方面,事實上這些都是很簡單的數學。
Dave Langworthy說他在高中就學過這些簡單的數學,你也有可能在大學裡學到,總之都是很基礎的東西,不像Mike說的那麼複雜,真的是很簡單的數學。我想引用Dave之前寫的第一句話,這些話都是Dave寫的,可能還有一些其它的表述沒有列出。這些是他們在某些背景下寫的,TLA+教會我如何思考。前亞馬遜員工Chris Newcombe,他是《ACM通訊》中那篇論文的作者之一,他說TLA+改變了他的思維方式。Brandon Batson,我在英特爾的第一個朋友說學習用TLA+寫規範的難點在於如何學會抽象思考,有了經驗後,工程師就能學會了,他們還新增、改進了各自設計的系統。
學會抽象思考其實不是TLA+教會他們抽象思考,但電腦科學家和工程師都認為是語言的神奇力量,相信只要有了正確的語言,全世界的問題都能得到解決。所以,他們看到TLA+時,看到TLA+為他們帶來了了不起的結果時,他們都認為這是因為TLA+是一門很讚的語言。但之所以能取得好的結果,並不是因為TLA+本身,TLA+本身並沒有什麼特別了不起的地方,而是因為他們學會了如何使用數學。真正讓他們學會思考的是數學的思維方式,而不是程式語言。程式語言可不會教會你抽象思考。還記得麼,我們已經看到過多年使用C語言導致的洗腦效應。程式語言不會教會你抽象思考,也不是所有程式語言都像C語言那樣摧殘大腦。我不是讓大家拋棄程式語言。大家也知道,程式語言很複雜。它們的複雜是因為它們要解決複雜的問題。你寫的程式規模遠超一千行。你寫的程式可以在模型檢驗器中高效地執行,但數學無法高效執行。無論多麼設計得多麼精妙,沒有哪種程式語言能夠像小學數學這樣讓你覺得如此美妙而強大。也沒有哪種語言能像小學數學這樣簡單而又有富有表達能力。
所以從數學的意義上來看,小學數學比任何程式語言都更有表達能力。計算機工程師面臨的根本問題是複雜度。優秀的工程師能夠讓系統變得最簡。用來描述計算機系統的數學也很簡單。程式語言很複雜,這種複雜是有原因的。你們想看的話,我可以舉出各種各樣的複雜案例。就像物件這種東西,你仔細研究一下,會發現確實複雜。就這一點來說,有些人認為程式設計方法很複雜,但是程式語言很簡單。人們為了解釋程式語言,就賦予它們語義,他們給程式語言賦予數學中的語義,但從沒有人嘗試過給數學賦予程式語言中的語義。這麼做沒多大意義,因為程式語言就是複雜的,無法實現簡化,你無法用複雜的語言進行思考。
要實現簡化,在實施前需要進行抽象思考。這就意味著在寫任何程式碼前都要用數學方式思考。TLA+教會你如何用數學方式思考,讓你用數學編寫規範。很少有工程師願意嘗試。他們經過多年的“洗腦”,喜歡用自己的那套計算機理念,多年的計算機教育讓他們認為C語言是極其簡單的語言,而數學是非常複雜的東西。這真是本末倒置。
大學裡需要教授數學思維。我想我知道學生們應該學什麼,其實要學的也很簡單,只要看一下我說的,把一個系統看成狀態機,就像兩步握手協議初始謂詞,以及下一狀態關係在描述抽象狀態機。不過,是讓學生學會在初始謂詞和下一狀態關係的背景下用數學的方式描述狀態機。因為只要看一下各種編寫自動機程式語言的方式,如圖靈機等,無論是什麼狀態機都可以用初始謂詞和下一狀態關係簡單地描述。將實施看作替換,因為你正在做的是實現某種東西,比如一個列表或是一個由指標組成的陣列,這一切實際上都是在實施替換。這與一位元時鐘中的那個一位元位的實施方式完全一樣,是由握手協議的兩個位元位進行實施。你所實施的是更為簡單的數學概念,就像一個列表或者序列,其中包含更復雜的物件組成,而這些物件又可以透過數學的方式進行描述,如陣列或指標。只要掌握了這些,大家就能用數學的方式來思考你所構建的系統了。
點此下載演講PPT:http://vdisk.weibo.com/s/z7VKRh2itrzah
原文連結:http://mp.weixin.qq.com/s/aYMYc7lnHhwQST2AdeS_jw