的確,對於程式設計者而言,運用某個程式語言所寫下的程式,其運行的效率高低與否,當然是一件十分關鍵的事情。任誰都不希望自己所寫下的程式碼,光是在程式語言的先天效能條件上,就落後其他語言一大截。所以,究竟使用那一個語言比較高效,自然也成為論者必議之題。

一般的看法,會覺得組合語言(機器語言)的效能優於C語言、C又優於C++,C++優於Java這種以VM(虛擬機器)為基礎的程式語言;而以VM為基礎的程式語言,效能表現又優於直譯式的程式語言。這樣的順序,基本上反映了一般程式設計者的認知。而在實務的經驗上,似乎也不脫上述的認知。不過,就程式語言的效率這個議題來說,還有一些值得討論的地方。

抽象一點來說,程式語言是一種表述的方式,也決定了能運用於表述的基本元素。當我們在撰寫程式時,其實我們是在透過這些可用於表述的基本元素,來描述解決問題的方法。使用不同的語言,描述解決問題的方式就不同。當你使用邏輯式程式語言時,你可能是寫下一堆邏輯規則來做為程式碼;當你使用的是函數式程式語言、程序式程式語言、物件導向程式語言時,即使待解的問題相同,但是用來描述解決問題方法的方式都不一樣。

從最理論的觀點來看,我們不能單就程式語言來回答效率這件事。因為效率關係到太多的因子:底層的硬體架構、編譯器、程式庫、要解的問題領域,以及運用的方式(也就是寫程式的方式)。

底層的硬體架構
底層的硬體架構當然對程式語言影響很大。因為整個程式語言的表述方式,最終都必須對應回實際的硬體架構。

程序式的程式語言目前在效率的表現佔優勢,是因為目前的硬體運作方式就是程序式的。但如果試著把思考程式語言效能的層次提高到更抽象的層次上,就會明白,程序式的程式語言並不會總是最快的,因為也許有朝一日,人們會發明出其他的硬體架構,恰好適合其他類型的語言運行。

此外,即便目前的硬體架構的模式都很相像的情況下,其餘不同的特性也會影響到程式語言的效率。就好比對能處理向量型資料的硬體(也就是說,提供專門指令集的CPU)來說,處理矩陣型的資料就會格外地快。倘若語言本身就含有向量運算的表述方式,那麼這樣的語言在此種架構下,也會表現得比較快。

編譯器
編譯器隨著電腦架構愈來愈複雜,其重要性也愈來愈提高。

好比在沒有指令管線化(pipeline)的時代,也許手寫的組合語言碼,會跑得比編譯器產生出來的碼來得精簡。但是考慮到管路的最佳化時,如何避開影響管路效能的因子,就有可能不是單靠人力可以做好的。

又例如C或是C++語言,由於提供了所謂的「指標」,使得編譯器的alias detection難以進行。這對於支援向量化或平行化運算的語言(通常這也意味著硬體架構)來說,就無法判定是否能夠轉為向量化或平行化的指令,也失去了效能最佳化的機會。當硬體支援此類的指令,程式語言先天的特性反而轉變成為了限制,限制此語言所撰寫的程式,使其無法充份地發揮硬體真正的實力。

而這正是一個很典型的例子:當語言本身的表述方式適用於最後要運行的硬體架構後,效率就有可能會好。例如,當語言本身具備向量化的概念,而硬體也同時支援向量的計算,同時又利用這個語言來做類似矩陣的運算時,這個語言的效率就有可能會好。語言本身,我們可以說它沒有快和慢的分別,只有對應到具體的應用情境上,才會浮現快與慢的結果。

程式庫
另外,標準程式庫的效率也會影響我們對程式語言效率的感受。同樣都是Java的程式庫,不同的程式庫提供者寫的效能就是會不同。由於程式庫是時常被程式員使用到的,它的效能也會和整體程式所呈現出來的效能息息相關。愈是核心的程式庫,愈是需要具備更好的效能。

高效的程式語言寫出來的程式既短且清楚。為什麼這樣的程式語言執行效率會好呢?更進一步想,讓開發者寫的部份少了,而且單純了,其餘的心力就轉移到程式庫上頭,而程式庫時常都是專家進行過最佳化的。倘若每個程式庫都能夠由專家開發設計,並且提供足夠的程式庫,那麼寫出來的程式自然容易具備高效能。

要針對的領域
如果表述的形式愈單純,就像我們做向量的運算時,倘若可以一行指令就描述一個向量的運算,那麼,這個語言就有機會比用迴圈來一個一個處理向量中元素的語言來得快(而這種形式相較之下,就不那麼的單純)。當我們表述的形式愈單純時,就有可能代表這個語言很適合我們要處理的問題領域。

語言的表述方式,其實是在描述一個解題空間中的答案,表達了如何將問題空間的問題對應到解題空間中的答案。如果這中間不需要進行什麼額外的轉換,那麼效率就有可能會很高。當你要做的是矩陣的運算時,倘若語言本身就提供了矩陣運算的表述方式,那麼這效率自然更有可能,快過利用不支援矩陣運算的語言來處理矩陣運算。

寫程式的方式
選用某一特定的語言,不完全是由語言的效能來決定。很多時候,我們得在語言效率上的實際展現與生產力之間,必須進行取捨。而且,更重要的是,在整個開發的過程當中,系統最後的效率不僅僅由所選定的程式語言影響,系統架構及效能的最佳化技巧所造成的影響或許更深。

語言與系統架構的選擇是開發規畫階段的事。我們依據經驗來選擇一個可能夠快、但不是最快的答案。這個語言必須是在這個應用領域中生產力夠高的,而這個架構必須是足夠有效率且容易擴充,有能力抵擋變更造成的衝擊。

系統的最佳化是開發後期甚至是上線後的事。大部份的效能瓶頸都是位在極少數的關鍵位置上。對這些效能瓶頸所做的改善,遠比對非效能瓶頸所做的改善,效果來得重大太多了。在大多數的情況下,只要處理幾個效能瓶頸,就可以讓系統以合理的效能來運作。

很多人常盲目追求效能,於是在每行程式碼、每個演算方法上精雕細琢。其實很多時候這些辛苦得來的效能都沒有起太大的作用。與其始終追求效能,不如建立一個好的架構,使得日後在發現效能瓶頸時,可以輕易更換掉有效能問題的組件。

效能最佳化的態度應該是保持一個「可最佳化」的架構,但不先試著做最佳化。也就是說,等到效能瓶頸浮現時,在這個「可最佳化」的架構上,輕鬆進行最佳化。而不是試著在每個環節都進行最佳化,最後影響了開發的時程,也不見得真的享受到該有的效能。

語言的選用固然在既定的應用情境下,會在效能上展現不同的高下分別,但是,影響效能的因子還有很多。光是挑選一個貌似高效的程式語言並不是萬靈丹,想要兼顧效能,以及其他在開發中你可能會在意的特質(例如生產力),你得對程式語言的本質投入更多的了解才行。

 

專欄作者


熱門新聞

Advertisement