爲何移動Web應用程序很慢(譯)

前些日子,看到Herb Sutter在本身的博客中推薦了一篇文章《Why mobile web apps are slow》,在推薦裏他這樣寫道:php


「I don’t often link to other articles, but this one is worth reading.」html

  我不常常連接到其它文章,可是這篇文章的確值得一讀。前端

「He offers data (imagine!) to justly debunk many common memes and 「easy answers」 that routinely litter HN/Reddit/Slashdot comment threads.」java

  這句話挺難翻譯的,大概意思應該是做者使用了確切的數據來支持本身的觀點,而不像其餘不少人同樣只是隨意地發出毫無根據的評論。android

「Don’t be distracted by the author’s viewpoint and emphasis on 「iOS and Javascript」 development – the article covers lots of important ground, including:ios

  • developing for ARM vs. x86;
  • developing for desktop vs. mobile;
  • managed vs. native code performance;
  • JIT issues vs. inherent language design tensions;
  • why garbage collection is not at all the panacea it’s often billed to be and often needs to be emphatically avoided (did you realize Apple already jettisoned GC?); and
  • as many of you know already, why if you’re serious about performance you’ll be seriously serious about memory usage and access patterns as a first-order issue.」

  不要被做者的觀點以及iOS和Javascript開發等字眼分散注意力——這篇文章包含了不少重要的基礎知識,包括:git

  • ARM平臺編程和x86平臺編程比較;
  • 桌面環境編程和移動設備編程比較;
  • 託管代碼和原生代碼性能比較;
  • JIT相關話題和語言內在的設計張力;
  • 爲何垃圾回收不是宣傳中所說的萬能藥,並且常常被強調要避免使用(你意識到蘋果公司已經拋棄了GC嗎?);
  • 就像大家中的不少人已經知道的那樣,爲何若是你很在意性能,那麼你就應該認真嚴肅地將內存使用和訪問模式做爲最優先須要考慮的問題。

既然Sutter大神如此推薦,我就好好把這篇文章看下來了,的確收穫頗豐,因此特地把這篇文章翻譯下來,一方面加深理解,另外一方面跟你們分享。我翻譯的首要目標是可讀性和流暢性,並不必定拘泥於字眼;難以翻譯和習慣用英文表達的詞彙會保留。我能夠保證理解做者95%以上的意思(畢竟是技術類文章),可是做者的一些幽默我極可能無法傳神地翻譯出來,還請你們包涵。程序員

(提示:這是一篇很是長的文章,認真讀下來可能須要一段時間。下面是正文翻譯。)github


我寫過很多文章來討論爲何移動Web應用程序很慢,這也引發了很多的討論。可是不幸的是,這些討論沒有像我喜歡的那樣的基於事實web

因此我這篇文章的目地就是給這些問題帶來一些真正的證據,而不是僅僅過來對罵。在這篇文章的中,你能夠看到基準測試(benchmark),能夠看到專家的觀點,你甚至能夠看到很是誠實(honest-to-God)的期刊文章。這篇文章有超過100個引用(不是開玩笑)。我不保證這篇文章能使你信服,甚至不保證這篇文章中的全部內容都是正確的(在這樣大規模的文章中作到這一點幾乎是不可能的),可是我能夠保證這是一篇關於許多iOS開發者都抱有的想法——移動Web應用很慢而且會在可預計的將來繼續如此——分析最完備和全面的文章。

如今我要警告你:這是一篇長得嚇人的文章,差很少10000字。固然,這是我故意的。我更喜歡好文章,而不是流行的文章。我嘗試使得這篇文章成爲前者,同時宣揚我認同的風氣:咱們應該鼓勵那些優秀的、基於證據的、有趣的討論,不鼓勵那些詼諧、譁衆取寵的評論。

我寫這篇的文章,在某種程度上是由於這是話題已經到了一種爭論不休的地步。這不是另外一篇爭論的文章,若是你想看到30秒左右的對罵:「真的!Web應用很渣!」和「誰說的?Web程序挺好!」,那麼這篇文章不適合你。另外一方面,據我所知,到如今爲止尚未一個關於這個話題全面的、正式的、理性的討論。這篇文章中我嘗試去理性地討論這個激起千層浪的話題,儘管這多是一個很是愚蠢的想法。這裏我給本身辯護一下,我相信這個問題與那些原本能夠更好地去討論卻沒有這樣作的人更有關係,而不是主題自己。

若是你想知道你那些原生代碼(native code)程序員朋友爲何在現在開放的網絡革命時期還在寫着萬惡的原生代碼,那就把本頁面加入書籤吧,給本身倒杯咖啡,找出一個下午的時間,找到一個舒服的椅子,而後咱們就正式開始吧!

簡單回顧

上一篇博客中寫道:基於SunSpider的benchmark給出的數據能夠看出當今的移動Web應用很慢。

若是你認爲「Web應用程序」就是「一個網頁加上一兩個按鈕」,那麼你就可讓那些花哨的benchmark——好比SunSpider——滾一邊去。可是若是你認爲「Web應用程序」是指「簡單的文字處理,簡單的照片編輯,本地存儲和屏幕之間的切換動畫」,那麼除非你有想死的心,不然你永遠不會願意在ARM上寫Web應用程序。

你應該先讀一下那篇文章,可是我仍是在這邊給你看下benchmark:

關於這個benchmark,主要三種主要的批評:

1. JS比原生代碼要慢並非什麼新鮮事了,每一個人在上第一學期的計算機基礎課時討論編譯型語言、JIT語言和解釋型語言是時候就知道了。問題是JS是否是慢到已經成爲你如今所寫軟件的大問題了,可是像這樣的benchmark並不能說明這個問題。

2. JS是很慢,這也的確是個問題,可是它在變得愈來愈快,因此在不久的將來,咱們能夠發現它不會那麼慢了。因此你們一塊兒學JS吧。

3. 我是Python/PHP/Ruby的服務器端的開發者,我不知道大家在說什麼。我知道個人服務器比大家的移動設備快,可是若是我能夠自信地保證使用真正的解釋型語言寫出支持上千個用戶的代碼,大家難道不能用一個帶有高性能JIT的語言寫出一個支持單個用戶的代碼嗎?真的有那麼難嗎?

我有一個至關高的目標,那就是反駁以上全部觀點:是的,JS的確是慢到必定程度了;不,它在不久的將來不會變得有多快;不,你在服務器端的編程經驗不能正確地映射到移動應用中。

可是真正的問題在於,在全部討論這個話題的文章裏面,基本上沒有人真正量化JS到底有多慢,或者提供某種真正有用的比較標準(相對於什麼來講慢)。爲了糾正這樣的狀況,我在這篇文章中提出了三種(不只僅是一種)比較JavaScript性能的辦法。我不會說「JS在什麼狀況下都慢」,而是真正量化它慢的程度,而且將它跟咱們再日常編程經驗中的事情作對比,這樣你就能夠根據這個結果結合本身的編程平臺作出決定,你也能夠本身計算下看看是否JavaScript適合你本身的特定問題。

OK,可是JS的性能相比於原生代碼到底如何?

這是一個好問題。爲了回答這個問題,我從Benchmark Game中隨意抓取了一個基準測試。而後我找到了一個作一樣benchmark的較老的C程序(老到不像不少新程序有一些x86特性)。我在本身的iPhone 4S上分別測試Nitro和LLVM。全部的代碼已經傳到了Github

這是一個隨機的測試,正如平常生活中運行的代碼同樣。若是你想要一個更好的實驗,能夠本身運行。我運行這個實驗還有另一個緣由,就是由於其它的實驗都不存在LLVM和Nitro的對比。

在這個綜合的基準測試中,LLVM一致地比Nitro快4.5倍:

Screen Shot 2013-05-14 at 5.32.06 AM

若是你在想「若是是計算密集型(CPU-bound)的功能,本地代碼比Nitro JS快多少呢」,那麼答案是差很少5倍。這個結果大體上和Benchmark Game在x86/GCC/V8上面的結果一致,那裏面的GCC/x86一般比V8/x86快2到9倍。因此結果大體上是正確的,不管是ARM仍是x86。

可是1/5的性能對每一個人來講還不夠好嗎?

在x86上是足夠好了。當渲染一個電子表格的時候,CPU的計算能有多密集呢?其實並非那麼難。問題是,ARM不是x86。

根據GeekBench的結果,最新的MacBook Pro的性能是最新的iPhone性能的10倍。這其實不算太大問題——電子表格沒那麼複雜。咱們能夠忍受10%的性能。可是我還要把它除以5?好傢伙!咱們如今只有桌面性能的2%了。

OK,可是文字處理到底有多難?咱們可不能夠用一個m68k芯片加上一個協處理器來搞定呢?這是一個能夠回答的問題。你可能記不起來,Google Doc的實時協做以前事實上還不是一個正式的功能,後來他們進行了大規模的重寫而且在2010年4月份加入到Google Doc裏面。咱們來看一下2010年瀏覽器的性能:

BrowserCompChart1 9-6-10[7]

從圖中能夠清晰地看到,iPhone 4S在Google Docs的實時協做方面徹底不是桌面網頁瀏覽器的對手。固然了,它仍是能夠跟IE8比上一比的。恭喜iPhone 4S,可喜可賀。

咱們再看看另一個正經的JavaScript應用:Google Wave。Wave歷來沒有支持IE8,由於它實在是太慢了。

Notice how all these browsers bench faster than the iPhone 4S?

看到這些瀏覽器比iPhone 4S快多少了嗎?

注意,全部支持的瀏覽器的得分都低於1000,其中那個得分3800的由於太慢了而被忽略了。iPhone得分爲2400。差很少和IE8同樣,太慢幾乎沒法運行。

這邊要說明的是,在移動設備上實現實時協做是可能的,只是不太可能用JavaScript來實現。原生代碼和Web應用的性能差距基本上和Firefox與IE8的性能差距差很少,這麼大的差距足以影響正常的工做。

可是我感受V8或者是現代JS已經有了接近C的性能了?

這取決於你怎麼理解「接近」了。若是你的C程序運行了10ms,那麼一個運行50ms的JavaScript程序差很少是接近C的速度了。若是你的C程序運行了10s,那麼一個運行了50s的JavaScript程序對於大多數正常人來講極可能就不是接近C的速度了。

硬件角度

1/5的速度在x86上是沒問題的,畢竟x86起點就比ARM快10倍,你還有不少上升空間。解決方案顯然是讓ARM變成10倍快,這樣就能夠跟x86競爭了,而後咱們就能夠不用作任何工做就能夠獲得桌面環境的JS性能了。

這個方法行不行得通取決於你是否相信摩爾定律,以及給每一個芯片配置一個3盎司的電池是否可行。我不是一個硬件工程師,可是我曾經爲一家大型半導體公司工做過,那裏的人告訴我說當今硬件的性能基本上是製做工藝(process)起的做用。iPhone 5使人印象深入的性能主要是由於其芯片工藝從45nm作到了32nm,減小了差很少1/3。可是若是想繼續這麼作,蘋果就要達到22nm的工藝。

順便提一下,Intel22nm工藝的Atom處理器如今還沒上市。並且Intel不得不從新發明全新的半導體,畢竟原來的半導體在22nm級別已經不適用了。他們會把工藝受權給ARM?再好好想一想吧。現在22nm的產品少之又少,並且大部分被Intel掌控着。

事實上,ARM彷佛已經在着手在明年嘗試28nm了(看看A7),同時Intel正在嘗試22nm甚至在稍微晚些時候嘗試20nm。從純硬件的角度,我感受具備x86級別性能的x86芯片極可能遠遠比具備x86性能的ARM芯片更早登陸智能手機市場。

看一個前Intel工程師給我發的郵件:

我是一個前Intel工程師,剛開始從事於移動微處理器的工做,後來工做轉向了Atom處理器。不管如何,我有一個很偏激的觀點,即x86從較大的核心轉向手機市場的難度遠比ARM從頭開始設計技術細節以達到x86的性能級別的難度要低不少。

再看一個機器人領域的工程師給我發的郵件:

你說得很是對,這些(譯註:指的是ARM的發展)不會帶來多大的性能提高,Intel可能在近幾年以內就會有更高性能的移動處理器。事實上,移動處理器當前和桌面處理器面臨着一樣的問題,即工做頻率達到3GHz左右的時候,再提升時鐘速度就不可避免地使得功耗大大增長。這種狀況一樣會發生在下一代工藝上,儘管IPC(Instruction per Clock,即CPU每一時鐘週期內所執行的指令多少)會獲得一些提升(差很少10%-20%)。在面臨這種限制的狀況下,桌面處理器開始向雙核和四核方向變化,可是移動處理器如今已是雙核和四核了,因此想提升性能不是那麼容易。

摩爾定律不管怎麼說均可能是正確的,可是這須要整個移動生態環境向x86環境轉變。這並不是徹底不可能,畢竟曾經有人作過這樣的事。但那是在移動處理器一年才賣出去100萬個的時候作的,不像如今,每一個季度就能夠賣出62萬個芯片。那個時候現成的虛擬化環境能夠模擬出老架構的60%的速度,而按照如今的研究來看,虛擬化系統上運行優化過的(O3)ARM代碼的速度已經接近27%了。

若是你堅信JavaScript的性能最終會到達一個合理的水平,那麼硬件性能的提高絕對是最好的方式。要麼Intel會在5年以內開發出可行的iPhone芯片(這是有可能的),而且蘋果迅速轉向x86架構(這是不太可能的),或者ARM可以在將來的10年以內獲得性能的飛躍。可是在我看來,10年是一個很長的時間,長到足夠使某件事情可能成功

恐怕個人硬件的知識只能分析到這裏了。我能夠告訴你的是,若是你相信ARM能夠在將來的5年以內填補與x86之間的性能差距,那麼第一步就是找到一個在ARM或者x86上工做的人(也就是真正懂硬件的人),讓他贊成你的見解。我寫這篇文章以前,曾近諮詢過不少有很高資質的硬件工程師,他們全部人都拒絕公開發表這個觀點,這讓我感受這個觀點不是很靠譜。

軟件角度

這是一個不少優秀軟件工程師犯錯誤的地方。他們的思路是這樣的:JavaScript已經變得更快了,而且它會變得更快。

這個觀點的前一部分是正確的,JavaScript的確變得快不少。可是咱們如今已經達到了JavaScript性能的頂點了,它不可能變得更快多少。

爲何?其實前一部分JavaScript的性能提高從某種程度上是硬件的緣由,正如Jeff Atwood寫道:

我感受從1996到2006之間JavaScript的性能變快了100倍。若是Web 2.0主要創建在JavaScript上的話,這極可能主要是由於摩爾定律所帶來的硬件性能提高。

若是咱們把JS的性能提高總結爲硬件性能提高的話,那麼JS的已有的硬件性能提高不能預測將來的軟件性能提高。這就是爲何若是你相信JS會變得更快的話,最有可能的方式就是硬件變得更快,由於歷史趨勢就是如此。

那麼JIT如何呢?V8,Nitro/SFX,TraceMonkey/IonMonkey,Chakra等等?固然,當他們剛剛問世的時候,的確是很了不得的(但或許不像你認爲的那麼了不得)。V8在2008年9月發佈,我找到了一份差很少那個時候同期的Firefox 3.0.3,看看它的性能:

Screen Shot 2013-05-14 at 6.41.48 PM

不要誤解個人意思,9倍的性能提高的確值得稱讚,畢竟這差很少是ARM和x86之間的性能差距了。即使如此,Chrome 8和Chrome 26之間的性能卻呈現出了水平線,由於自從2008開始,幾乎沒有什麼重大的事情發生。其餘瀏覽器廠商都已經遇上來了,有些快有些慢,可是沒人真正提升過JavaScript的性能了。

JavaScript性能在提高嗎?

Screen Shot 2013-05-14 at 3.59.04 AM

這是我Mac上的Chrome 8(可運行的最先版本,2010年12月份的版本)和Chrome 26

看不出差異?由於根本沒有差異。JavaScript的性能最近根本沒有獲得大的提高

若是你感受如今的瀏覽器比2010年的瀏覽器跑的快的話,那極可能是由於你有了一臺更快的電腦,可是這與Chrome的性能提高沒什麼關係。

更新:有些聰明的人指出SunSpider如今不是一個好的benchmark(而且拒絕提供任何實際的數字或其它什麼)。爲了可以能夠理性地討論,我在一些舊版本的Chrome上面運行Octane(一個Google的benchmark),的確顯示出了一些性能提高:

Octane on V8, 2011 to 2013

在我看來,在這個期間的性能提高仍是過小,不足以支撐JS立刻就會足夠快這樣的論調。然而,要說我過度強調這個狀況也沒錯,畢竟JavaScript的計算密集型操做的確在發生變化。可是推我來講,這些數字能夠得出更大的推斷:這些性能提高的幅度還不足以在必定時間以內使得JavaScript的速度遇上原生代碼。你須要性能達到2-9倍才能跟LLVM競爭。這些提高是好的,但還不足夠好。更新結束

問題是,讓JavaScript採用JIT技術是一個60年前就有的想法,而且這60年來一直有人在研究。數以千計的你可能想到的編程語言對JIT的實現都證實這是一個好主意。可是既然咱們已經作到了,咱們已經用完了這個60年前的想法。夥計們,就是這樣的,表演結束了。或許咱們能夠在將來的60年以內想到另外一個好辦法。

可是Safari恐怕比之前要快吧?

Is Safari 7 3.8x faster than the other guys?

Safari 7 是否是比其它的瀏覽器快3.8倍?

這個結論或許對蘋果來講很容易獲得,可是這個版本的Safari在NDA協議之下,因此沒有人可以公開關於Safari性能的獨立參數。可是我能夠僅僅根據如今已經獲得的信息來作一些分析。

我發現一些現象頗有意思。第1、蘋果官方在公開的JSBench上的數據要比在他們在較老的benchmark(如SunSpider)上給出的數據高出很多。如今JSBench背後有一些很是酷的名字,包括JavaScript之父Brenden Eich。可是和傳統的benchmark不同,JSBench的工做方式不會考慮整數和其它重要因素,它反而自動爲Amazon、Facebook和Twitter提供的內容進行優化,並且根據它們提供的內容來創建benchmark。

若是你在寫一個多數人用來瀏覽Facebook的瀏覽器,我能夠理解用一個只測試Facebook性能的benchmark是頗有用的。可是從另外的角度講,若是你在寫一個電子表格的程序,或者遊戲,或是一個圖片過濾應用,在我看來傳統的benchmark(注重整數運算和md5哈希)會更可以準確地幫你預測出Facebook的分析代碼有多快。

另外一個重要的事實是,蘋果聲稱的在SunSpider上性能的提高並不能表明其它東西的提高,Eich et al在這篇提到蘋果所偏心的benchmark的文章中寫道:

圖中清楚地顯示出了Firefox的3.6版本比1.5版本在SunSpider的benchmark上性能提升了13倍。可是當咱們看它在amazon的benchmark上的性能表現時,發現只有較謙虛的3倍的提高。更有意思的是,在過去的兩年時間,在amazon的benchmark上的性能提高實際上是被誇大了。這意味着在SunSpider上作的一些優化幾乎對amazon沒有太大做用。

在這篇文章中,JavaScript之父和Mozilla的首席架構師之一曾公開認可在過去的兩年以內Amazon的JavaScript性能幾乎沒有提高,沒有發生過什麼特別重要的事情。從這一點你也能夠看出來,那些營銷人員這些年都在過度誇大本身產品的性能。

他們繼續爭辯道:對於那些人們用來瀏覽Amazon網頁的瀏覽器來講,運行Amazon的benchmark比運行其它benchmark要更能準確地預測出瀏覽器的性能(這是固然了……),可是這些手段不會幫助你更好地寫出一個照片處理程序。

可是不管怎麼說,從我能夠看到的公開信息來看,蘋果聲稱的3.8倍的性能提高對你來講幾乎沒有什麼太有用的東西。我能夠告訴你,若是我有一些可以反駁蘋果聲稱擊敗Chrome的benchmark的話,我將不被容許發佈它們。

因此,咱們總結一下這一節,若是有一些人拿出一個柱狀圖來顯示網頁瀏覽器變得更快了,那並不能真正說明整個JS變得更快了。

可是還有一個更大的問題。

並不是爲性能而設計

JavaScript-the-good-parts

下面這段話出自於Herb Sutter,現代C++中最著名的人物之一:

在過去的20年裏,有一種很難根除的文化基因——只要的等下一代的(包括JIT和靜態)編譯器出來,託管語言就會變得和原生語言同樣高效。是的,我徹底但願C#和Java編譯器可以不斷提升,包括JIT和類NGEN的靜態編譯器。可是,它們永遠不會消除與原生代碼之間的效率差距,有兩個緣由:1、JIT編譯不是主要問題。根本緣由更爲基本:託管語言在編程人員的開發效率(當時的確是個問題)和程序的運行效率之間從設計上作了故意的妥協。特別的,託管語言選擇選擇在全部的程序上添加額外的性能開銷,儘管你根本沒有用到一個特性,你都會受到這個特性帶來的額外的性能開銷。主要的例子是assumption/reliance、垃圾回收、虛擬運行環境和元數據等功能在託管語言中是默認打開。固然還有其它的例子,好比託管代碼中函數默認是virtual的,而C++代碼中的函數是默認inline的。1盎司(譯註:12盎司=1磅)的內聯阻止(inlining prevention)抵得上1磅的去虛擬化優化(devirtualization optimization cure)。

下面這段話出自於Mono項目組的Miguel de Icaza,他是爲數很少的「維護着一個主流JIT編譯器的人」。他說道:

關於主流託管語言(.NET、Java和JavaScript)的虛擬機之間的差別,有一個比較準確的說法。託管語言的設計者在他們設計一門語言的過程當中更傾向於安全性,而不是性能。

或者你能夠找Alex Gaynor談一談,他負責維護和優化Ruby的JIT編譯器,而且也爲Python的JIT的優化工做作出了貢獻:

這是加在這些具備高生產力的動態語言身上的詛咒。它們使得建立一個哈希表十分容易。這是一件很是好的事情。我認爲C程序員多數不太會使用哈希表,由於對他們來講用哈希表實在是是一件痛苦的事情。緣由有二:第一,你沒有一個內置的哈希表;第2、當你嘗試去使用的時候,你會左右碰壁。對比來看,Python、Ruby和JavaScript程序員都過分使用哈希表了,由於使用它們實在太容易了……因此你們都不在意。

Google彷佛意識到了JavaScript正面臨着性能的瓶頸:

複雜的Web應用(這是Google比較擅長的)在某些平臺上正在面臨着不小的掙扎,主要是由於這些應用用到了一些不能被性能調優的語言,這些語言有內在的性能問題。

最後,咱們聽聽權威人士的意見。個人一個讀者向我指出這段Brenden Eich的評論。正如你所知,他是JavaScript之父。

有一點Mike沒有強調:獲得一個更簡單的語言。Lua比JS簡單得多。這意味着你能夠寫出一個簡單的解釋器使得它跑得足夠快,同時可以保持對trace-JIT代碼的尊重(這和JS不一樣)。

稍微下面一點又提到:

關於JS和Lua之間的差異,你能夠說這徹底是正確的設計和工程上的問題,可是內在的複雜性區別仍是很大。你固然能夠把較難的案例從熱路徑中刪除,可是他們也會所以付出代價。JS比Lua有更多的更難的案例。一個例子是:Lua(沒有顯式的元表使用)沒有像JS中的原型對象鏈(prototype object chain)的東西。

在這些真正從事相關工做的人當中,持有JS或者是其它動態語言可以遇上C語言性能這個觀點的,只佔極少數(very much the minority)。處處都有和主流想法不一樣的人,因此根本沒法沒有什麼辦法可以達到真正的一致。可是,從語言的角度說到JIT語言是否可以遇上原生語言的效率,他們給出的答案都是「不,不可能,除非修改語言自己或者API」。

可是還有一個更更大的問題。

都是由於垃圾回收

你能夠發現,CPU問題、CPU相關的benchmark以及全部有關CPU的設計決定,都只是故事的一半。故事的另外一半是內存。內存問題如今看來是如此的巨大,大到使整個CPU的問題看上去都僅僅是冰山一角。實際上能夠討論的是,全部關於CPU的討論都是轉移注意力的話題(red herring)。你接下來要閱讀的應該會徹底改變你對移動設備軟件開發的理解。

2012年,蘋果作了一件很是奇怪的事情(固然了,除非你是John Gruber,可以看到它的到來)。他們把垃圾回收從OSX中除去了。真的,你能夠去看看程序員指南。標題右邊有一個大大的「不推薦(Not Recommended)」。若是你以前是Ruby、Python、JavaScript、Java、C#或是其它任何1990年代以後誕生的語言的開發者,這應該會讓你感受很奇怪。可是這極可能不會影響到你,由於你極可能不在Mac下面使用ObjC,在HN點擊下一個連接。可是這仍然看上去很奇怪,畢竟GC一直被你們使用着,並且它的價值也獲得了證實。爲何你要反對它呢?蘋果是這麼說的:

咱們十分堅信ARC纔是內存管理的正確方式,因此咱們決定使OSX上的垃圾回收變成過期的(deprecate)。——ession 101, Platforms Kickoff, 2012, ~01:13:50

這段話沒有告訴你的是,當聽到這句話的時候,臺下的觀衆爆發出了熱烈的掌聲。OK,這就變得真的很是奇怪了。你是否是在告訴我有那麼一個屋子裏的程序員在爲了垃圾回收以前的那種混亂的迴歸鼓掌?你能夠想象下若是Matz在RubyConf上宣佈GC過期的時候整個會場的寂靜,幾乎一顆針掉在地上都聽獲得聲音。而這羣人卻所以而高興?太古怪了吧?

你應該根據這些古怪的反應發現一些你如今看不到可是倒是在真正發生的事情,而不是僅僅把這些事情歸結於這羣人對於蘋果的狂熱。這些正在發生的事情就是咱們下面就要討論的主題。

思惟過程是這樣的:把一個工做得好好的垃圾回收器從一個語言中拿出來簡直是瘋了吧?一個簡答的解釋多是ARC可能僅僅是蘋果爲了給垃圾回收披上一層美麗新裝而創造的一個營銷詞彙,因此這些開發者是爲了這種升級而不是降級而鼓掌的。事實上,這就是不少iOS簇擁們的抱有的想法。

ARC不是一個垃圾收集器

全部的那些認爲ARC是某種垃圾回收器的人,我想經過下面這個蘋果的幻燈片給你迎頭一擊(beat your face):

Screen Shot 2013-05-14 at 9.44.43 PM

這與和垃圾回收有相似名字的算法無關。它不是垃圾回收,他不是什麼像垃圾回收的東西,它表現得一點都不像垃圾回收,它不會打亂任何保留週期,它沒有去回收任何東西,它甚至沒有去作掃描。OK,故事結束,它絕對不是垃圾回收。

由於正式的文檔還在NDA協議下,因此有不少傳言認爲這並非真的(可是細則已經能夠看獲得了,沒有任何藉口了),並且不少博客都紛紛說這些不是真的。它是真的。不要再討論了。

垃圾回收不像你的經驗讓你感受的那樣可行

這是蘋果在壓力之下給出的關於ARC和GC的說法:

在願望清單的頂端上咱們能爲大家作的最重要的事情就是把垃圾回收帶到了iOS中,而這偏偏是咱們最不該該作的。不幸的是,垃圾回收給性能帶來了不少次優的影響。你程序中的垃圾回收會使得你的內存使用率變得很高,並且垃圾回收器常常在不肯定的時間點上被觸發而致使很是高的CPU使用率,從而打斷用戶正在作的事情。這就是爲何GC不適合在咱們的移動平臺上使用的緣由。對比來看,帶有獲取和釋放(retain/release)的手動內存管理學起來比較難,坦率的講有些像痔瘡(譯註:這個翻譯可能不許確,原文是pain in the ass)。可是它產生了更好更可預測的性能,這也是爲何咱們選擇手動內存管理做爲咱們內存管理策略的基礎的緣由。由於在外面真實的世界,高性能以及用戶體驗的連續性是咱們的用戶最看重的。(譯註:在蘋果看來,用戶體驗要比開發者體驗重要。)~Session 300, Developer Tools Kickoff, 2011, 00:47:49

可是這仍是徹底瘋狂了,不是嗎?這只是開始:

1. 這可能會直接影響你整個職業生涯對於垃圾回收語言給桌面和服務器上帶來影響的理解;

2. Windows Mobile、Andriod、Mono Touch以及全部其它移動平臺上的GC彷佛均可以挺好地工做。

因此讓咱們反過來看這個問題。

移動平臺上的GC和桌面平臺上的GC不是同一回事

我知道你在想什麼,你是一個有了N年開發經驗的Python程序員。如今是2013年了,垃圾回收徹底能夠解決問題。

這是一篇你想看到的文章,彷佛問題並無解決:

Screen Shot 2013-05-14 at 10.15.29 PM

若是你在這篇文章中其它什麼都記不得,那麼請記住這張圖。Y軸是垃圾回收所用的時間,X軸是「相對的內存足跡」,相對於什麼?相對於所需的最小內存

這張圖想說明的是,「若是你有6倍以上你實際須要的內存,那麼使用垃圾回收是沒有問題的。可是若是你只有小於4倍你實際須要的內存,那麼災難就要降臨了。」。可是不要相信個人話:

特別的,若是垃圾回收時系統擁有5倍於所需的內存時,它的運行時性能差很少甚至是超過顯式內存管理。可是,垃圾回收的性能在必須使用小堆(small heap)的狀況下會出現急劇降低。若是有3倍於所需的內存的話,它會跑得慢17%;若是隻有2倍於所需的內存的話,會慢70%。垃圾回收比物理內存的換頁更容易受到內存不足的影響。在這種狀況下,咱們所測試的全部垃圾回收器相對於手動內存管理都出現指數級的性能降低

如今咱們再來比較一下顯式內存管理的策略:

這些圖顯示,若是可用內存在合理的範圍的狀況下(但不足以容得下整個應用),顯式內存管理器都要比垃圾回收器快太多。好比說,pesudoJBB以63M的可用內存運行,Lea allocator在25s的時間內完成運行。在相同可用的內存下運行GenMS,花了超過10倍的時間來運行(255s)。咱們能夠看到其它benchmark套件的相同趨勢。最值得一提的例子是213 javac和Lea allocator一塊兒在36M的內存下運行,整體運行時間是14s,而與GenMS一塊兒運行的狀況下,運行時間爲211s,用了超過15倍的時間。

基本的事實是,在內存受限的環境下垃圾回收性能的降低是指數級的。若是你在桌面電腦上寫Python或者JS程序的話,你整個的體驗多是這幅圖的右邊部分,你能夠一生都體會不到垃圾回收帶來的性能問題。花點時間想一想這幅圖的左邊部分,而且想一想咱們該如何應對。

iOS上有多少可用內存?

這一點很難準確地描述。從iPhone 4到iPhone 5,這些設備上的物理內存從512M到1G不等。可是其中很大一部分爲系統預留了,還有更大的一部分爲多任務處理預留了。因此惟一真正的方法是在不一樣的狀況下進行嘗試。Jan Ilavsky寫了一個很是有用的工具來完成這個任務,可是貌似沒有人公開任何數據。但這一點如今已經改變了。

如今,在一種「正常」的狀況下(這一點很難具體說清楚是什麼意思)進行測試是很是重要的,由於若是你在一臺剛剛啓動的機器上測試的話,你會獲得更好的數據,畢竟你的系統裏面沒有Safari所打開的頁面。因此我就在「真實世界」的狀況下拿出一些我公寓裏的設備進行本次benchmark。

Photo May 14, 10 51 13 PMPhoto May 14, 10 51 13 PM

你能夠點擊進去看看詳細的結果,大致上來講,在iPhone 4S上,當你的程序使用了40M內存的時候就會獲得警告,而使用了213M內存的時候,程序就會被殺死;在iPad 3上,使用400M左右時得到警告,而使用550M左右的時候,程序被殺死。固然了,這些也僅僅是數字而已,若是你的用戶在聽音樂或者在後臺跑一些程序,你可用的內存會比我測試裏面可用的內存更少,這只是給你一個思路而已。這麼多內存看上去很多(213M應該對每一個人來講都足夠了,是吧?),但實際上這還不夠。舉個例子,iPhone 4S拍照時的分辨率爲3264*2448,每張照片有超過30M的位圖數據。若是你在內存里加載了2張照片你就會得到警告,而若是加載了7張照片,程序就會被殺死。哦,你打算給你寫個循環在你的相冊中逐個處理?程序會被殺死。

還有一點須要十分注意:實際的狀況下,一張照片可能存在於內存的多個位置。好比說,若是你在拍照片,那麼你在如下位置都有數據:1) 你經過屏幕看到的攝像頭中的數據,2) 攝像頭實際上拍到的照片數據,3) 你嘗試寫到存儲卡中的壓縮JPEG緩衝數據;4) 你準備在下一個屏中顯示的數據;5) 你準備上傳到某個服務器中的數據。

在一些點上你會發現,保留30M的緩衝區去顯示照片縮略圖是一個很是很差的想法,由於這樣你會引入更多的數據:6) 用來保留下一屏顯示合適大小照片的緩存;7) 用於在後臺從新調整照片大小的緩存(在前臺作實在太慢了)。而後你發現你可能真正須要5個不一樣的大小,而後你的程序就不是通常的慢了,而是慢到讓人抓狂。在實際的應用程序中,僅僅是處理一個照片就會遇到內存的瓶頸並不是罕見的事情。可是不要相信我說的話:

你能作的最糟糕的事情就是在內存不充足的狀況下在內存中緩存圖片。當一張圖片被畫成位圖或者顯示到屏幕上時,咱們就不得不把照片解碼爲位圖。位圖的每一個像素點爲4字節,不管原始圖片多大都是如此。每當咱們將它解碼一次,位圖就會綁定到圖片自己而且一直維持到這個對象生命週期結束之時。因此若是你把圖片加載到內存並且曾經顯示過一次,那你如今就會在內存中保留整個位圖,直到你釋放它爲止。因此永遠不要把UIImage或者CGImage放到緩存中,除非你有一個很是明確(希望是很是短時間的)的目標。- Session 318, iOS Performance In Depth, 2011

你甚至不要相信上面的話!你給本身分配的內存其實只是冰山一角。下圖是蘋果一張幻燈片中給出的冰山的全圖。Session 242, iOS App Performance – Memory, 2012:

Screen Shot 2013-05-15 at 5.39.12 AM

你能夠從兩方面考慮這個問題。第1、在213M可用內存的狀況下,在iOS上寫一個照片處理程序比在桌面上寫一個要困難許多。第二,你在iOS上寫一個照片處理程序時,你對內存的須要會更多,由於你的桌面程序沒有一個能夠放進你口袋的攝像頭

咱們能夠看看另一個例子:在iPad 3上,你要顯示一個視頻,這種照片的大小極可能比你電腦上的視頻要大很多(後面的高分辨率攝像頭,差很少2000-4000像素)。每一幀要顯示的就是一個12M的位圖。若是你對內存的使用很節省的話,每一時刻你能夠在內存中保留45幀的未壓縮視頻或動畫緩存,也就是在30fps的狀況下每1.5s,在60fps的狀況下0.75s。你想爲一個全屏的動畫預留緩存?應用被殺死。值得指出的是,AirPlay的延遲是2s,因此對於任何多媒體類型的應用,你幾乎是保證沒有足夠的內存

這種狀況下咱們一樣面臨着和照片的多個數據拷貝差很少的問題。好比說,蘋果指出,「每個UIView背後都有一個CALayer,並且只要CALayer存在於在這個層次中,對應的圖片數據會一直保存在內存中」。這意味着,極可能有許多中間的渲染數據的拷貝存在於內存中。

還有剪切矩形和備份存儲這些可能會佔用內存的事情。這樣的數據處理架構事實上是很是高效的,可是這帶來的代價是程序會盡量地佔用內存。iOS不是爲低內存使用而設計的,它是爲了快速運行而設計的。這沒有和垃圾回收扯在一塊兒。

咱們一樣須要從兩個方面考慮問題。第一,你在一種內存很是緊缺的狀況下作出動畫效果;第2、作出這樣超級高質量的視頻和動畫是須要極大的內存的。而爲了使得普通消費者買得起消費級別的、具備高攝像頭分辨率的產品,這種糟糕的、內存受限的環境幾乎是必然的選擇。若是你想寫一個軟件來毫無壓力地播放視頻,那麼你就得說服別人爲了屏幕多花700美圓,或者花500美圓買一個iPad,它實際上已經包含了一個內置的電腦。

咱們會得到更多內存嗎?(更新)

一些聰明的人說:「OK,你說了不少關於咱們不會有更快的CPU。可是咱們應該回有更多的內存吧?這正是桌面環境上發生的事情。」

這種理論的一個問題是,ARM平臺上的內存就在處理器自己上,這被稱爲package on package。因此在ARM上得到更多的內存幾乎和提升CPU性能是同一個問題,由於它們歸根結底是同一件事情:在CPU上集成更多的晶體管。內存晶體管處理起來稍微容易一些,由於它們是統一的,因此不是那麼難,但實際上也不是那麼簡單。

若是你看看iFixit的A6的照片,你會發如今CPU模具最表面的硅幾乎100%都是內存。這意味着,若是你想擁有更多的內存,你要麼使得製做工藝更精細,要麼提升模具的大小。事實上,若是你把工藝的大小作歸一化處理,那麼其實伴隨着每次內存升級,你的模具都在變得更大。

normalized die size for iPhone chips

硅實際上是一種不完美的材料,爲了得到更大的尺寸所付出的的代價是指數級增加的。它們也很難維持較低的溫度,也很難放進小設備中。它們和製做出更好的CPU的目標是重複的,由於內存也面臨一樣的問題:CPU最上層的硅中須要放進更多的晶體管。

搞不懂的是,面臨着PoP的這些問題,CPU廠商們繼續使用PoP的方式爲系統提供內存。我尚未遇到任何ARM工程師可以解釋這一點。或許如下的評論可能會幫助咱們理解。咱們有可能從PoP架構轉向電腦中採用的分離內存模塊,並且我感受這比較可行。緣由很簡單,將內存分爲單獨的模塊比製造出更大的芯片和進一步減少製做工藝對廠商來講毫無疑問成本是更低的。可是如今全部的廠商都在不停地嘗試提升製做工藝或者製做出更大的芯片,而不是把內存模塊獨立出來。

然而,一些聰明的工程師曾經給我發了郵件讓我填補了這方面的空白。

一個前Intel工程師說道:

PoP內存模型能夠大量減小內存延遲,也能夠減輕路由問題。可是我不是ARM工程師,也不肯定這是不是所有的緣由。

一個機器人學的工程師提到:

當PoP內存不夠用時,「3D」內存會提供足夠大的內存:內存芯片在生產的時候堆疊在一塊兒,1G的RAM在同一層堆成10層向上,就像如今的硬件模型同樣。可是,這樣的開銷會很大,頻率和電壓都要相應地下降使得電力消耗處於一個合理的水平。

移動RAM的帶寬不會像最近提升得這麼快了。帶寬被鏈接SoC和RAM包的總線的數量所限制。當前RAM的總線多數使用的是高性能SoC的圓柱體表面。SoC的中間部分不能用來加入RAM總線,由於這些RAM包是層疊的。接下來的重大改變應該回來自於將SoC和內存放在一個單獨的、高度集成的包中,容許更小、更密集和大量的RAM總線(更大的帶寬),並給SoC設計和更低的RAM電壓帶來更多自由。根據這樣的設計,更大的緩存也就可能成爲現實,由於RAM可能用更高的帶寬放在SoC模具中。

可是Mono/Andriod/Windows Mobile平臺怎麼解決這個問題呢?

這個問題事實上有兩個答案。第一個答案咱們能夠從圖中看出。若是你發現你有6倍於所需的內存,垃圾回收實際上是很是快的。舉個例子來講,若是你在寫一個文本編輯器,你可能能夠用35M的內存完成本身要作的全部事情,這是個人iPhone 4S會崩潰內存上界的1/6。你可能在Mono上寫個文本編輯器,看到很是不錯的性能,而後從這個例子中得出以下結論:垃圾回收十分適合這個任務。你是對的。

然而Xamarin框架在案例中有一個飛行器模擬器。很顯然的是,垃圾回收對於現實生活中的較大的應用程序來講是不適合的。難道不是嗎?

你在開發和維護這個遊戲時必定會遇到什麼樣的問題?「性能一直是一個大問題,並且將持續是咱們再跨平臺中會遇到的最大的問題之一。最初的Windows Phone設備是很是慢的,咱們不得不花不少時間來優化程序,使得它達到一個體面的幀率。咱們不只僅在飛行模擬代碼上進行優化,並且在3D引擎上優化。垃圾回收和GPU的弱點是最大的瓶頸。

程序員不約而同地聲稱垃圾回收是最大的瓶頸。當你的案例中的人在抱怨的時候,那應該是一個足夠引發你重視的線索了。可是Xamarin多是一個局外人,咱們仍是來看看Andriod開發者怎麼說的吧:

請記住下面是我在個人Galaxy Nexus上運行的狀況:不管怎麼說都是性能很是不錯的設備。可是看看渲染的時間!我在電腦上只要花幾百毫秒就能夠渲染出這些圖片,但是在這臺手機上卻花了超過兩個數量級的時間。渲染「inferno」圖片超過6s?這簡直是瘋了吧!要生成一副圖片,須要10-15倍的時間來運行垃圾回收器。

一個開發者:

若是你想在Andriod手機上爲實時物體識別或者基於內容的現實加強進行對照相機圖片的處理,那麼你極可能據說過照相機預覽回調(Camera Preview Callback)的內存問題。每次Java程序嘗試從系統獲取預覽圖片時,系統就會建立一大塊新的內存。當垃圾回收器釋放這塊內存時,系統會卡住(freezes)100ms到200ms。若是系統在高負載的狀況下,事情可能會變得很是糟糕(我曾經在手機上作過物體識別——天吶,它幾乎把整個CPU都佔用了)。若是你看過Andriod 1.6的源代碼你就會知道,這只是由於這個功能的包裝類(wrapper,用來包裝原生代碼)每次在一個新的幀可用時都會申請一個新的字節數組。固然,內置的原生代碼能夠避免這個問題

或者,我還能夠去看看Stack Overflow

我負責在Andriod平臺上爲Java寫的交互式遊戲進行性能調優。不少時候,當候垃圾回收開始工做會讓使得遊戲的畫圖和交互功能發生打嗝。一般狀況下這種打嗝持續不到1/10s,但有的時候在比較慢的設備上會長達200ms。若是我在一個內部循環中使用樹或者哈希表,我就就知道我要很當心,或者甚至不用Java標準的Collections框架,而是本身從新實現一個,由於我承擔不起垃圾回收帶來的額外開銷。

這是一個「接受的答案(accepted answer)」,有27我的贊同:

我也是Java手機遊戲的開發者……避免垃圾回收(垃圾回收可能在某個點被出發從而大幅下降你遊戲的性能)的最好方法就是不要再遊戲的主循環中建立對象。實在沒有什麼「簡潔」的方式來處理這種問題,或許只有手動追蹤這些對象了,真悲哀。這地也是目前大部分當前移動設備上性能優良的Java遊戲所採起的的方式。

咱們來看看Facebook的Jon Perlow怎麼看待這個問題:

對於開發流暢的安卓應用來講,GC是一個很是大的性能問題。在Facebook,咱們遇到的最大問題之一是GC會使得UI線程暫停。當咱們處理不少位圖數據時,GC被觸發的頻率很高,並且難以免。GC常常致使掉幀的問題。即便GC只會阻塞UI線程幾毫秒,但這卻會嚴重影響本來須要16毫秒的幀渲染。

OK,咱們再聽聽一個微軟MVP的說法:

一般狀況下,你的代碼在33.33ms以內就會完成執行,從而使得30fps的幀率變得很不錯。可是當GC運行的時候,它會佔用那個時間。若是你的堆比較整潔和簡單,那麼GC通常能夠運行得不錯,不會對程序產生什麼影響。可是讓一個簡單的堆處於一個使GC能夠快速運行的情形是一件困難的編程任務,它要求大量的計劃和/或程序重寫,即便是這樣也不是徹底安全的(有時在一個複雜的、有不少玩意的遊戲中你的堆裏面有不少內容)。更簡單的方法是(假設你能這麼作),在遊戲過程當中限制甚至是禁用內存分配。

在有垃圾回收的狀況下,在遊戲中保證必定會贏的方法就是不要玩(做者的幽默,原文:the winning move is not play)。比這種哲理稍弱的一種形式如Andriod官方文檔中所說的:

對象建立永遠不是免費的。帶有線程級別內存池的垃圾回收器使得內存分配的成本變得稍微低一些,可是分配內存永遠比不分配內存開銷大。由於當你在程序中分配對象時,你會強制垃圾回收週期性地工做,從而使得用戶體驗不是那麼流暢。Andriod 2.3當前引入的垃圾回收器有一些做用,可是要是應該避免沒必要要的工做。所以,你應該避免建立你不須要的對象實例。通常而言,儘量不要建立短時間臨時對象。越少的對象建立就意味着越低頻率的垃圾回收,從而提升用戶體驗。

還不信?那讓我來問問一位真正從事垃圾回收工做的工程師,他爲移動設備實現垃圾回收器。

然而,WP7系統的手機CPU和內存性能正在大幅度地提高。遊戲和大型Silverlight應用愈來愈多,這些程序會佔用100M左右的內存。隨着內存的變得愈來愈大,不少對象擁有的引用會指數級地變多。在上面解釋的模式中,GC不得不去遍歷每一個對象以及他們的引用,標記它們,而後清理沒喲引用指向的內存。全部GC的時間也大幅增長,而且成爲這個應用的工做集(workingset)的一個函數。這會在大型XN遊戲中和SL應用中致使程序卡住,體如今很長的啓動時間(由於GC有可能在遊戲啓動的時候運行)或者遊戲過程當中的小問題。

仍是不信?Chrome有一個測量GC性能的benchmark。咱們來看看它都幹嗎了:

Photo May 15, 2 19 26 AM

你能夠看到不少GC致使的卡頓。固然了,這是一個壓力測試,但仍是能說明問題的。你真的願意花幾秒時間來渲染一幀?你瘋了吧。

這麼多引用,我纔不會挨個看呢,直接告訴我結論就好了。

結論是:移動設備上的內存管理很難。iOS平臺的開發者已經造成了一種文化,即手動作大部分事情,讓編譯器作其它容易的部分。Andriod平臺造成的文化是,提升垃圾收集器的性能,但事實上開發者在實際開發中儘可能避免使用它。這二者的共同點是,你們在開發移動應用時,開始愈來愈多地考慮內存管理問題了。

當JavaScript、Ruby或是Python開發者聽到「垃圾收集器」這個詞時,他們習慣將它理解爲「銀彈(silver bullet)垃圾收集器」,也就是「讓我不要讓我再考慮內存問題的垃圾收集器」。可是移動設備上根被沒有銀彈可言,每一個人寫移動應用時都在考慮內存問題,無論他們是否使用了垃圾收集器。得到「銀彈」內存管理方式的惟一方式就像咱們在桌面環境上同樣,擁有10倍於程序實際須要內存。

JavaScript的整個設計基於一個思想,即不要擔憂內存。看看Chromium開發者們怎麼說

有沒有任何一種方式強制chrome的js引擎進行垃圾回收?通常意義而言,沒有,這是從設計的角度就已經肯定了的。

ECMAScript規範沒有提到「分配(allocation)」這個詞,惟一與「內存」相關的話題本質上就是說整個主題都是「實現相關的(host-defined)」。

ECMA 6的維基頁面上有幾頁提案的草稿,歸根結底是說(不是開玩笑):

「垃圾回收器不能夠回收那些程序須要繼續使用來完成正確執行的內存。全部不能從根節點傳遞遍歷到的對象都應該最終被銷燬,防止程序由於內存耗盡而發生錯誤。」

是的,他們的確在思考將這個需求規約:垃圾回收器不該該回收那些不該該被回收的東西,可是應該回收那些須要被回收的。歡迎來到tautology club。可是下面這段話可能與咱們的話題更相關:

然而,並無規範說明單個對象佔用多少內存,也不太可能會有。所以當任何程序在內存耗盡的狀況下,咱們永遠不會獲得任何保證,因此任何準確的、可觀察的下界。

用英語來講就是,JavaScript的思想(若是這算是一種思想的話)是你不該該可以觀察到系統內存中的狀況,想都不要想。這種思想和人們在寫實際的程序時候的想法簡直是使人難以置信的背道而馳(so unbelievably out of touch),我甚至找不到正確的詞語來向你形容。個人意思是,在iOS的世界裏,咱們並不相信垃圾回收器,咱們感受Andriod開發者都瘋了(nuts)。我懷疑Andriod開發者會這麼認爲:iOS開發者居然會用手動內存管理,簡直是瘋子。可是你知道這兩個水火不容的陣營的人能夠在哪件事情上達成共識嗎?那就是JavaScript開發者是真正的瘋子。你在移動平臺上寫出一個有點意思的程序,而歷來不關心繫統內存的分配和釋放,是絕對不可能的(absolutely zero chance)。絕對不可能。暫時把SunSpider的benchmark上的問題和CPU計算密集型的問題都拋開,咱們能夠得出這樣的結論:JavaScript,儘管如今存在着,是和移動平臺軟件開發過程當中絕對重要的思想,即永遠要考慮內存問題,從根本上是背道而馳的

只要人們想要人們想在移動設備上開發各類視頻和照片處理程序(不像桌面電腦),只要移動設備的內存不是那麼充足,這個問題就是很是棘手的。你在移動設備上須要理性的、正式的內存管理保證。而JavaScript從設計上來講是拒絕提供這些的

假設它可以提供這些

如今你可能會問,「OK,桌面環境上的JS開發者不會移動設備上的開發者遇到的問題。假設他們相信你說的,或者假設有一些知道這些問題的移動開發者們根據JS從新設計一門語言。你感受理論上他們能夠作哪些事情?」

我不肯定這是不是解決的,可是我能夠在這個問題上放一些邊界。有另外一羣人曾經嘗試在JS的基礎上設計一門適合移動開發者的語言——RubyMotion

這些人很是聰明,他們很瞭解Ruby。而後這些Ruby開發者認爲垃圾回收對於他們的語言來講是一個糟糕的想法。(GC倡導者們,大家看到我說的了嗎?)因此他們用了一種很是相似ARC的技術而後嫁接到語言當中,然而卻沒有成功

總結:不少人正經歷着因爲RM-3或者其它難以辨別的問題所致使的內存相關議題,咱們能夠看看他們怎麼說。

Ben Sheldon

不只僅是你,我也面臨着內存相關的程序崩潰(好比SIGSEGV和SIGBUS)生產環境下有10-20%的用戶遇到過這種狀況。

有一些人懷疑這個問題是否易於處理:

我在最近的一次Motion Meetup會上提出關於RM-3的問題,Laurent和Watson都對此提出了本身的見解。Watson提到說,RM-3是最難修復的bug;Laurent說他嘗試了不少方法,但最終都沒有很好地解決這個問題。他們兩我的都是很是聰明和厲害的程序員,因此我相信他們說的話。

還有一些人懷疑編譯器理論上是否可以解決這個問題:

很長的一段時間內,我都認爲編譯器能夠簡單明確地處理程序塊,即靜態地分析程序塊內部的內容來判斷程序塊是否引用了這個程序塊外部的變量。我認爲,對於全部這些變量,編譯器能夠在程序塊建立時獲取,在程序塊銷燬時釋放。這個過程吧這些變量的生命週期綁定到程序塊上(固然,在某種狀況下不是「完整的」生命週期)。有一個問題是instance_eval(譯註:Ruby中Object類的方法)。程序塊中的內容或許是按你提早知道的方式使用的,但也有可能並非你可以提早知道的。

RubyMotion還有一個對立的問題:內存泄露,並且它還有可能有其它問題。沒有人真正知道程序崩潰時內存泄露有2個緣由仍是有200個緣由。

因此無論怎麼說,咱們的結論是:一部分世界上最好的Ruby程序員專門爲移動設備開發設計了一種語言,他們設計了一個系統,這個系統不只會崩潰,並且還會內存泄露,這些問題都是你可能會面臨到的。至今爲止他們並無可以處理這個問題,儘管他們已經很是盡力了。對了,他們也表示他們「本身嘗試了很多次,但沒有可以可以找到一個好的而且可以保持高性能的解決方案」。

我並非說在JavaScript的基礎上建立一門具備較高內存性能的語言是不可能的,我只是想說不少證據顯示這個問題會很是難

更新:一個Rust語言的貢獻者提到:

我爲Rust項目工做,個人主要目標是實現零額外開銷的內存安全。咱們經過「@-boxes」(@T聲明的類型是任何類型T)的方式來支持經過GC處理的對象,而咱們最近遇到的比較麻煩的事情是,GC觸碰到語言中的全部內容。若是你想支持GC但卻不須要它,你就要很是仔細地設計你的怨言來支持零額外開銷的非GC指針。這不是一個簡單的問題,我不認爲能夠經過創建在JS的基礎上建立一門新語言來解決

OK,可是ASM.JS如何呢?

asm.js就比較有趣了,由於它提供了一個JavaScript模型,但這個模型嚴格意義上不是創建在垃圾回收的基礎上的。因此從理論上講,使用正確的網頁瀏覽器,使用正確的API就能夠了。問題是,「咱們會獲得正確的瀏覽器嗎?」

Mozilla顯然在這個概念上被出賣了,做爲這個技術的做者,他們的實現今年晚些時候實現了它。Chrome的反應一直是含糊不清的,由於這個技術顯然和Google的其它提案,包括Dart和PNaC1有直接的競爭關係。關於它有一個開發的bug清單,可是一個V8的黑客對此不滿意。至於Apple陣營,按照我如今看來,WebKit那羣人對比徹底保持沉默。IE?我歷來就沒有抱任何但願。

不管如何,如今還不能說asm.js就是真正解決JavaScript問題,且可以擊敗全部其它提案的方法。另外,若是它能作到,它真的不多是JavaScript,畢竟它可以可行的緣由就是拋開了麻煩的垃圾回收器。因此它有可能和C/C++或者其它手動管理內存的語言的前端一塊兒工做,但確定不和咱們如今知道而且喜歡的動態語言同樣。

相對什麼來講慢?

當一些文章裏面說「X慢」和「X不慢」的時候,一個問題是,沒有人真正說的清楚它們的參照系是什麼。對於一個網頁瀏覽器開發者,和對於一個高性能集羣的開發者,以及對於一個嵌入式系統的開發者,等等,「慢」的含義是不同的。既然咱們已經闖過了戰壕並且作了這麼多benchmark,我能夠給出你三個有用大體正確座標系

若是你是一個Web開發者,把iPhone 4S的Nitro當作IE8來看,由於它們的benchmark成績差很少。這就給了你寫代碼時的正確座標系。寫代碼的時候應該謹慎地使用JS,不然你會面臨一大堆平臺相關的性能問題要處理。有些應用用JS來寫性價比是不高的,即便是流行的瀏覽器。

若是你是x86平臺上的C/C++開發者,把iPhone 4S的Web開發環境當成只有桌面開發環境性能的1/50。其中1/10來自於ARM相對於x86的性能差距,1/5來自於JavaScript相對於C/C++的性能差距。在非JavaScript、性能爲桌面環境1/10的狀況下,仔細考慮正反面的因素。

若是你是Java、Ruby、Python或者C#開發者,按照如下方式去理解iPhone 4S的Web開發環境:它的性能是你電腦的1/10(ARM的因素),而且若是你的內存使用超過35M,性能會指數級降低,這是由垃圾回收的工做方式決定的。還有,若是你的程序分配了超過213M的內存,程序就會崩潰。注意,沒有人「從設計的角度」在運行時刻給你這個信息。對了,人們都但願你在這種環境下寫出很耗內存的照片處理和視頻應用。

這是一篇很是長的文章

下面是你應該記得的內容:

  • 2013年,用JavaScript寫的移動應用(如照片編輯等)實在是太慢了。
    • 比原生代碼慢5倍
    • 性能和IE差很少
    • 比x86平臺上的C/C++代碼慢50倍
    • 若是你的程序全部的內存不超過35M,比服務器端的Java/Ruby/Python/C#慢10倍;若是內存使用超過這個數,性能開始指數級降低
  • 要使這個速度變得快一些,最可能的方式是讓硬件性能達到桌面水平的性能。從長遠來看這是可行的,可是看起來要等很長時間。
  • 最近一段時間JavaScript語言自己並無變得更快,在JavaScript上工做的人認爲,在現有的語言和API下,它永遠不會向原生代碼那麼快。
  • 垃圾回收在內存受限的環境下會呈現指數級的性能降低,這一點比桌面和服務器級別的狀況差不少。
  • 任何能幹的移動開發者都花不少時間來爲目標設備考慮內存性能問題,無論他們是否使用具備GC的環境。
  • 當前的JavaScript,從本質上是和容許程序爲目標設備考慮內存性能問題這一點背道而馳的。
  • 若是JavaScript的工做者們意識到這問題而且作出改變,容許開發者考慮內存問題,經驗代表這是技術上的難題。
  • asm.js讓人看到了一些但願,可是就算它能成功,它應該是用了C/C++或者相似的「過期的」語言的前端,而不是像JavaScript這樣的前端。

讓咱們提升爭論的層次

毫無疑問的一點是,我不久就將收到上百封郵件,這些郵件圈出我說的某句話,而後在不提供任何實際的證據(或者根本不能算是證據)的狀況下,指出我說得不對。或者說「我曾經用JavaScript寫過一個文本編輯器,挺好」,或者說「有些我歷來沒見過的人寫了一個飛行模擬器,可是歷來沒有給我寫郵件說明他們遇到性能問題」,這些郵件我會一概刪除。

若是咱們想要在移動Web開發(或者是原生應用,或者是任何其它事情)上取得一些進展,咱們都須要各類至少看上去有說服力(at least appear to have a plausible basis)的討論,包括benchmark、期刊以及編譯器做者們的引用等等。網上有不少HN關於「我曾經寫了一個Web應用,挺好」的評論,還有不少關於Facebook在知道他們將會知道如今應該知道的東西的狀況下(譯註:原味knowing what they would have known then what they could have known now,不知這樣翻譯對不對?)是選擇HTML5仍是原生應用是對是錯的爭論(譯註:原文爲bikeshedding,這是一個比較有意思的詞,意思是在還沒完成自行車車架還沒弄好的狀況下就去討論車的顏色,意指過於關心細節和邊緣的問題,而忽視主要問題)。

對於咱們來講,剩下來的任務是,明確地量化如何使得移動Web和原生生態環境變得愈來愈好,接着爲此作出一些事情。正如你所知,這也是一個軟件開發者應該作的事情。


翻譯到此結束,翻譯過程前先後後花了4天的時候。當快回宿舍以前,終於能夠放到博客上去時,居然感受有些難以想象。謝謝你們支持!

原博文地址

相關文章
相關標籤/搜索