麻豆小视频在线观看_中文黄色一级片_久久久成人精品_成片免费观看视频大全_午夜精品久久久久久久99热浪潮_成人一区二区三区四区

首頁 > 編程 > Ruby > 正文

對優(yōu)化Ruby on Rails性能的一些辦法的探究

2020-10-29 19:36:55
字體:
供稿:網(wǎng)友

1.導(dǎo)致你的 Rails 應(yīng)用變慢無非以下兩個原因:

  1. 在不應(yīng)該將 Ruby and Rails 作為首選的地方使用 Ruby and Rails。(用 Ruby and Rails 做了不擅長做的工作)
  2. 過度的消耗內(nèi)存導(dǎo)致需要利用大量的時間進行垃圾回收。

Rails 是個令人愉快的框架,而且 Ruby 也是一個簡潔而優(yōu)雅的語言。但是如果它被濫用,那會相當(dāng)?shù)挠绊懶阅堋S泻芏喙ぷ鞑⒉贿m合用 Ruby and Rails,你最好使用其它的工具,比如,數(shù)據(jù)庫在大數(shù)據(jù)處理上優(yōu)勢明顯,R 語言特別適合做統(tǒng)計學(xué)相關(guān)的工作。

內(nèi)存問題是導(dǎo)致諸多 Ruby 應(yīng)用變慢的首要原因。Rails 性能優(yōu)化的 80-20 法則是這樣的:80% 的提速是源自于對內(nèi)存的優(yōu)化,剩下的 20% 屬于其它因素。為什么內(nèi)存消耗如此重要呢?因為你分配的內(nèi)存越多,Ruby GC(Ruby 的垃圾回收機制)需要做的工作也就越多。Rails 就已經(jīng)占用了很大的內(nèi)存了,而且平均每個應(yīng)用剛剛啟動后都要占用將近 100M 的內(nèi)存。如果你不注意內(nèi)存的控制,你的程序內(nèi)存增長超過 1G 是很有可能的。需要回收這么多的內(nèi)存,難怪程序執(zhí)行的大部分時間都被 GC 占用了。

2 我們?nèi)绾问挂粋€ Rails 應(yīng)用運行更快?

有三種方法可以讓你的應(yīng)用更快:擴容、緩存和代碼優(yōu)化。

擴容在如今很容易實現(xiàn)。Heroku 基本上就是為你做這個的,而 Hirefire 則讓這一過程更加的自動化。其它的托管環(huán)境提供了類似的解決方案。總之,可以的話你用它就是了。但是請牢記擴容并不是一顆改善性能的銀彈。如果你的應(yīng)用只需在 5 分鐘內(nèi)響應(yīng)一個請求,擴容就沒有什么用。還有就是用 Heroku + Hirefire 幾乎很容易導(dǎo)致你的銀行賬戶透支。我已經(jīng)見識過 Hirefire 把我一個應(yīng)用的擴容至 36 個實體,讓我為此支付了 $3100。我立馬就手動吧實例減容到了 2 個, 并且對代碼進行了優(yōu)化.

Rails 緩存也很容易實施。Rails 4 中的塊緩存非常不錯。Rails 文檔 是有關(guān)緩存知識的優(yōu)秀資料。不過同擴容相比,緩存并不能成為性能問題的終極解決方案。如果你的代碼無法理想的運行,那么你將發(fā)現(xiàn)自己會把越來越多的資源耗費在緩存上,直到緩存再也不能帶來速度的提升。

讓你的 Rails 應(yīng)用更快的唯一可靠的方式就是代碼優(yōu)化。在 Rails 的場景中這就是內(nèi)存優(yōu)化。而理所當(dāng)然的是,如果你接受了我的建議,并且避免把 Rails 用于它的設(shè)計能力范圍之外,你就會有更少的代碼要優(yōu)化。

2.1 避免內(nèi)存密集型Rails特性

Rails 一些特性花費很多內(nèi)存導(dǎo)致額外的垃圾收集。列表如下。

2.1.1 序列化程序

序列化程序是從數(shù)據(jù)庫讀取的字符串表現(xiàn)為 Ruby 數(shù)據(jù)類型的實用方法。

class Smth < ActiveRecord::Base serialize :data, JSONendSmth.find(...).dataSmth.find(...).data = { ... }

它要消耗更多的內(nèi)存去有效的序列化,你自己看:

class Smth < ActiveRecord::Base def data JSON.parse(read_attribute(:data)) end def data=(value) write_attribute(:data, value.to_json) endend

這將只要 2 倍的內(nèi)存開銷。有些人,包括我自己,看到 Rails 的 JSON 序列化程序內(nèi)存泄漏,大約每個請求 10% 的數(shù)據(jù)量。我不明白這背后的原因。我也不知道是否有一個可復(fù)制的情況。如果你有經(jīng)驗,或者知道怎么減少內(nèi)存,請告訴我。

2.1.2 活動記錄

很容易與 ActiveRecord 操縱數(shù)據(jù)。但是 ActiveRecord 本質(zhì)是包裝了你的數(shù)據(jù)。如果你有 1g 的表數(shù)據(jù),ActiveRecord 表示將要花費 2g,在某些情況下更多。是的,90% 的情況,你獲得了額外的便利。但是有的時候你并不需要,比如,批量更新可以減少 ActiveRecord 開銷。下面的代碼,即不會實例化任何模型,也不會運行驗證和回調(diào)。

Book.where('title LIKE ?', '%Rails%').update_all(author: 'David')
后面的場景它只是執(zhí)行 SQL 更新語句。

update books set author = 'David' where title LIKE '%Rails%'Another example is iteration over a large dataset. Sometimes you need only the data. No typecasting, no updates. This snippet just runs the query and avoids ActiveRecord altogether:result = ActiveRecord::Base.execute 'select * from books'result.each do |row| # do something with row.values_at('col1', 'col2')end

2.1.3 字符串回調(diào)

Rails 回調(diào)像之前/之后的保存,之前/之后的動作,以及大量的使用。但是你寫的這種方式可能影響你的性能。這里有 3 種方式你可以寫,比如:在保存之前回調(diào):

before_save :update_statusbefore_save do |model|model.update_statusendbefore_save “self.update_status”

前兩種方式能夠很好的運行,但是第三種不可以。為什么呢?因為執(zhí)行 Rails 回調(diào)需要存儲執(zhí)行上下文(變量,常量,全局實例等等)就是在回調(diào)的時候。如果你的應(yīng)用很大,你最終在內(nèi)存里復(fù)制了大量的數(shù)據(jù)。因為回調(diào)在任何時候都可以執(zhí)行,內(nèi)存在你程序結(jié)束之前不可以回收。

有象征,回調(diào)在每個請求為我節(jié)省了 0.6 秒。

2.2 寫更少的 Ruby

這是我最喜歡的一步。我的大學(xué)計算機科學(xué)類教授喜歡說,最好的代碼是不存在的。有時候做好手頭的任務(wù)需要其它的工具。最常用的是數(shù)據(jù)庫。為什么呢?因為 Ruby 不善于處理大數(shù)據(jù)集。非常非常的糟糕。記住,Ruby 占用非常大的內(nèi)存。所以舉個例子,處理 1G 的數(shù)據(jù)你可能需要 3G 的或者更多的內(nèi)存。它將要花費幾十秒的時間去垃圾回收這 3G。好的數(shù)據(jù)庫可以一秒處理這些數(shù)據(jù)。讓我來舉一些例子。

2.2.1 屬性預(yù)加載

有時候反規(guī)范化模型的屬性從另外一個數(shù)據(jù)庫獲取。比如,想象我們正在構(gòu)建一個 TODO 列表,包括任務(wù)。每個任務(wù)可以有一個或者幾個標簽標記。規(guī)范化數(shù)據(jù)模型是這樣的:

  • Tasks
  •  id
  •  name
  • Tags
  •  id
  •  name
  • Tasks_Tags
  •  tag_id
  •  task_id

加載任務(wù)以及它們的 Rails 標簽,你會這樣做:

這段代碼有問題,它為每個標簽創(chuàng)建了對象,花費很多內(nèi)存。可選擇的解決方案,將標簽在數(shù)據(jù)庫預(yù)加載。

tasks = Task.select <<-END  *,  array(  select tags.name from tags inner join tasks_tags on (tags.id = tasks_tags.tag_id)  where tasks_tags.task_id=tasks.id  ) as tag_names END > 0.018 sec

這只需要內(nèi)存存儲額外一列,有一個數(shù)組標簽。難怪快 3 倍。

2.2.2 數(shù)據(jù)集合

我所說的數(shù)據(jù)集合任何代碼去總結(jié)或者分析數(shù)據(jù)。這些操作可以簡單的總結(jié),或者一些更復(fù)雜的。以小組排名為例。假設(shè)我們有一個員工,部門,工資的數(shù)據(jù)集,我們要計算員工的工資在一個部門的排名。

SELECT * FROM empsalary;
 depname | empno | salary-----------+-------+------- develop |  6 | 6000 develop |  7 | 4500 develop |  5 | 4200 personnel |  2 | 3900 personnel |  4 | 3500 sales  |  1 | 5000 sales  |  3 | 4800

你可以用 Ruby 計算排名:

salaries = Empsalary.allsalaries.sort_by! { |s| [s.depname, s.salary] }key, counter = nil, nilsalaries.each do |s| if s.depname != key key, counter = s.depname, 0 end counter += 1 s.rank = counterend

Empsalary 表里 100K 的數(shù)據(jù)程序在 4.02 秒內(nèi)完成。替代 Postgres 查詢,使用 window 函數(shù)做同樣的工作在 1.1 秒內(nèi)超過 4 倍。

SELECT depname, empno, salary, rank()OVER (PARTITION BY depname ORDER BY salary DESC)FROM empsalary;
 depname | empno | salary | rank -----------+-------+--------+------ develop |  6 | 6000 | 1 develop |  7 | 4500 | 2 develop |  5 | 4200 | 3 personnel |  2 | 3900 | 1 personnel |  4 | 3500 | 2 sales  |  1 | 5000 | 1 sales  |  3 | 4800 | 2

4 倍加速已經(jīng)令人印象深刻,有時候你得到更多,到 20 倍。從我自己經(jīng)驗舉個例子。我有一個三維 OLAP 多維數(shù)據(jù)集與 600k 數(shù)據(jù)行。我的程序做了切片和聚合。在 Ruby 中,它花費了 1G 的內(nèi)存大約 90 秒完成。等價的 SQL 查詢在 5 內(nèi)完成。

2.3 優(yōu)化 Unicorn

如果你正在使用Unicorn,那么以下的優(yōu)化技巧將會適用。Unicorn 是 Rails 框架中最快的 web 服務(wù)器。但是你仍然可以讓它更運行得快一點。

2.3.1 預(yù)載入 App 應(yīng)用

Unicorn 可以在創(chuàng)建新的 worker 進程前,預(yù)載入 Rails 應(yīng)用。這樣有兩個好處。第一,主線程可以通過寫入時復(fù)制的友好GC機制(Ruby 2.0以上),共享內(nèi)存的數(shù)據(jù)。操作系統(tǒng)會透明的復(fù)制這些數(shù)據(jù),以防被worker修改。第二,預(yù)載入減少了worker進程啟動的時間。Rails worker進程重啟是很常見的(稍后將進一步闡述),所以worker重啟的速度越快,我們就可以得到更好的性能。

若需要開啟應(yīng)用的預(yù)載入,只需要在unicorn的配置文件中添加一行:

preload_app true
2.3.2 在 Request 請求間的 GC

請謹記,GC 的處理時間最大會占到應(yīng)用時間的50%。這個還不是唯一的問題。GC 通常是不可預(yù)知的,并且會在你不想它運行的時候觸發(fā)運行。那么,你該怎么處理?

首先我們會想到,如果完全禁用 GC 會怎么樣?這個似乎是個很糟糕的想法。你的應(yīng)用很可能很快就占滿 1G 的內(nèi)存,而你還未能及時發(fā)現(xiàn)。如果你服務(wù)器還同時運行著幾個 worker,那么你的應(yīng)用將很快會出現(xiàn)內(nèi)存不足,即使你的應(yīng)用是在自托管的服務(wù)器。更不用說只有 512M 內(nèi)存限制的 Heroku。

其實我們有更好的辦法。那么如果我們無法回避GC,我們可以嘗試讓GC運行的時間點盡量的確定,并且在閑時運行。例如,在兩個request之間,運行GC。這個很容易通過配置Unicorn實現(xiàn)。

對于Ruby 2.1以前的版本,有一個unicorn模塊叫做OobGC:

require 'unicorn/oob_gc' use(Unicorn::OobGC, 1) # "1" 表示"強制GC在1個request后運行"

對于Ruby 2.1及以后的版本,最好使用gctools(https://github.com/tmm1/gctools):

require 'gctools/oobgc'use(GC::OOB::UnicornMiddleware)

但在request之間運行GC也有一些注意事項。最重要的是,這種優(yōu)化技術(shù)是可感知的。也就是說,用戶會明顯感覺到性能的提升。但是服務(wù)器需要做更多的工作。不同于在需要時才運行GC,這種技術(shù)需要服務(wù)器頻繁的運行GC. 所以,你要確定你的服務(wù)器有足夠的資源來運行GC,并且在其他worker正在運行GC的過程中,有足夠的worker來處理用戶的請求。

2.4 有限的增長

我已經(jīng)給你展示了一些應(yīng)用會占用1G內(nèi)存的例子。如果你的內(nèi)存是足夠的,那么占用這么一大塊內(nèi)存并不是個大問題。但是Ruby可能不會把這塊內(nèi)存返還給操作系統(tǒng)。接下來讓我來闡述一下為什么。

Ruby通過兩個堆來分配內(nèi)存。所有Ruby的對象在存儲在Ruby自己的堆當(dāng)中。每個對象占用40字節(jié)(64位操作系統(tǒng)中)。當(dāng)對象需要更多內(nèi)存的時候,它就會在操作系統(tǒng)的堆中分配內(nèi)存。當(dāng)對象被垃圾回收并釋放后,被占用的操作系統(tǒng)中的堆的內(nèi)存將會返還給操作系統(tǒng),但是Ruby自有的堆當(dāng)中占用的內(nèi)存只會簡單的標記為free可用,并不會返還給操作系統(tǒng)。

這意味著,Ruby的堆只會增加不會減少。想象一下,如果你從數(shù)據(jù)庫讀取了1百萬行記錄,每行10個列。那么你需要至少分配1千萬個對象來存儲這些數(shù)據(jù)。通常Ruby worker在啟動后占用100M內(nèi)存。為了適應(yīng)這么多數(shù)據(jù),worker需要額外增加400M的內(nèi)存(1千萬個對象,每個對象占用40個字節(jié))。即使這些對象最后被收回,這個worker仍然使用著500M的內(nèi)存。

這里需要聲明, Ruby GC可以減少這個堆的大小。但是我在實戰(zhàn)中還沒發(fā)現(xiàn)有這個功能。因為在生產(chǎn)環(huán)境中,觸發(fā)堆減少的條件很少會出現(xiàn)。

如果你的worker只能增長,最明顯的解決辦法就是每當(dāng)它的內(nèi)存占用太多的時候,就重啟該worker。某些托管的服務(wù)會這么做,例如Heroku。讓我們來看看其他方法來實現(xiàn)這個功能。

2.4.1 內(nèi)部內(nèi)存控制

Trust in God, but lock your car 相信上帝,但別忘了鎖車。(寓意:大部分外國人都有宗教信仰,相信上帝是萬能的,但是日常生活中,誰能指望上帝能幫助自己呢。信仰是信仰,但是有困難的時候 還是要靠自己。)。有兩個途徑可以讓你的應(yīng)用實現(xiàn)自我內(nèi)存限制。我管他們做,Kind(友好)和hard(強制).

Kind 友好內(nèi)存限制是在每個請求后強制內(nèi)存大小。如果worker占用的內(nèi)存過大,那么該worker就會結(jié)束,并且unicorn會創(chuàng)建一個新的worker。這就是為什么我管它做“kind”。它不會導(dǎo)致你的應(yīng)用中斷。

獲取進程的內(nèi)存大小,使用 RSS 度量在 Linux 和 MacOS 或者 OS gem 在 windows 上。我來展示下在 Unicorn 配置文件里怎么實現(xiàn)這個限制:

class Unicorn::HttpServer KIND_MEMORY_LIMIT_RSS = 150 #MB alias process_client_orig process_client undef_method :process_client def process_client(client) process_client_orig(client) rss = `ps -o rss= -p #{Process.pid}`.chomp.to_i / 1024 exit if rss > KIND_MEMORY_LIMIT_RSS endend

硬盤內(nèi)存限制是通過詢問操作系統(tǒng)去殺你的工作進程,如果它增長很多。在 Unix 上你可以叫 setrlimit 去設(shè)置 RSSx 限制。據(jù)我所知,這種只在 Linux 上有效。MacOS 實現(xiàn)被打破了。我會感激任何新的信息。

這個片段來自 Unicorn 硬盤限制的配置文件:

after_fork do |server, worker| worker.set_memory_limitsendclass Unicorn::Worker HARD_MEMORY_LIMIT_RSS = 600 #MB def set_memory_limits Process.setrlimit(Process::RLIMIT_AS, HARD_MEMORY_LIMIT * 1024 * 1024) endend

2.4.2 外部內(nèi)存控制

自動控制沒有從偶爾的 OMM(內(nèi)存不足)拯救你。通常你應(yīng)該設(shè)置一些外部工具。在 Heroku 上,沒有必要因為它們有自己的監(jiān)控。但是如果你是自托管,使用 monit,god 是一個很好的主意,或者其它的監(jiān)視解決方案。

2.5 優(yōu)化 Ruby GC

在某些情況下,你可以調(diào)整 Ruby GC 來改善其性能。我想說,這些 GC 調(diào)優(yōu)變得越來越不重要,Ruby 2.1 的默認設(shè)置,后來已經(jīng)對大多數(shù)人有利。

我的建議是最好不要改變 GC 的設(shè)置,除非你明確知道你想要做什么,而且有足夠的理論知識知道如何提高性能。對于使用 Ruby 2.1 或之后的版本的用戶,這點尤為重要。

我知道只有一種場合 GC 優(yōu)化確實能帶來性能的提升。那就是,當(dāng)你要一次過載入大量的數(shù)據(jù)。你可以通過改變?nèi)缦碌沫h(huán)境變量來達到減少GC運行的頻率:RUBY_GC_HEAP_GROWTH_FACTOR,RUBY_GC_MALLOC_LIMIT,RUBY_GC_MALLOC_LIMIT_MAX,RUBY_GC_OLDMALLOC_LIMIT,和 RUBY_GC_OLDMALLOC_LIMIT。

請注意,這些變量只適用于 Ruby 2.1 及之后的版本。對于 2.1 之前的版本,可能缺少某一個變量,或者變量不是使用這個名字。

RUBY_GC_HEAP_GROWTH_FACTOR 默認值 1.8,它用于當(dāng) Ruby 的堆沒有足夠的空間來分配內(nèi)存的時候,每次應(yīng)該增加多少。當(dāng)你需要使用大量的對象的時候,你希望堆的內(nèi)存空間增長的快一點。在這種場合,你需要增加該因子的大小。

內(nèi)存限制是用于定義當(dāng)你需要向操作系統(tǒng)的堆申請空間的時候,GC 被觸發(fā)的頻率。Ruby 2.1 及之后的版本,默認的限額為:

New generation malloc limit RUBY_GC_MALLOC_LIMIT 16MMaximum new generation malloc limit RUBY_GC_MALLOC_LIMIT_MAX 32MOld generation malloc limit RUBY_GC_OLDMALLOC_LIMIT 16MMaximum old generation malloc limit RUBY_GC_OLDMALLOC_LIMIT_MAX 128M

讓我簡要的說明一下這些值的意義。通過設(shè)置以上的值,每次新對象分配 16M 到 32M 之間,并且舊對象每占用 16M 到 128M 之間的時候 (“舊對象” 的意思是,該對象至少被垃圾回收調(diào)用過一次), Ruby 將運行 GC。Ruby 會根據(jù)你的內(nèi)存模式,動態(tài)的調(diào)整當(dāng)前的限額值。

所以,當(dāng)你只有少數(shù)幾個對象,卻占用了大量的內(nèi)存(例如讀取一個很大的文件到字符串對象中),你可以增加該限額,以減少 GC 被觸發(fā)的頻率。請記住,要同時增加 4 個限額值,而且最好是該默認值的倍數(shù)。

我的建議是可能和其他人的建議不一樣。對我可能合適,但對于你卻未必。這些文章將介紹,哪些對 Twitter 適用,而哪些對 Discourse 適用。

2.6 Profile

有時候,這些建議未必就是通用。你需要弄清楚你的問題。這時候,你就要使用 profiler。Ruby-Prof 是每個 Ruby 用戶都會使用的工具。

想知道更多關(guān)于 profiling 的知識, 請閱讀 Chris Heald's 和我的關(guān)于在 Rails 中 使用ruby-prof 的文章。還有一些也許有點過時的關(guān)于 memory profiling 的建議.

2.7 編寫性能測試用例

最后,提高 Rails 性能的技巧中,雖然不是最重要的,就是確認應(yīng)用的性能不會因你修改了代碼而導(dǎo)致性能再次下降。

3 總結(jié)感言

對于一篇文章中,對于如何提高 Ruby 和 Rails 的性能,要面面俱到,確實不可能。所以,在這之后,我會通過寫一本書來總結(jié)我的經(jīng)驗。如果你覺得我的建議有用,請登記 mailinglist ,當(dāng)我準備好了該書的預(yù)覽版之后,將會第一時間通知你。現(xiàn)在,讓我們一起來動手,讓 Rails 應(yīng)用跑得更快一些吧!

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 亚洲午夜久久久久 | 国产流白浆高潮在线观看 | 色诱亚洲精品久久久久久 | 精品国产乱码久久久久久久 | 欧美国产一区二区三区 | 欧美精品久久天天躁 | 亚洲成人精品一区二区 | 在线看免费观看av | 好吊色欧美一区二区三区四区 | 久久久久久艹 | 欧美日韩国产一区二区三区在线观看 | 一级网站片 | 黑人一区二区 | 91精品国产综合久久男男 | 欧美精品久久久久久久多人混战 | 成年人视频免费 | 92看片淫黄大片欧美看国产片 | 免费一级a毛片在线播放视 日日草夜夜操 | 麻豆视频在线观看 | av在线播放观看 | 性大片1000免费看 | 九色新网址| 国产成人高清成人av片在线看 | 午夜视频在线观看91 | 国产做爰| 欧美一级黄色免费看 | 国产一级桃视频播放 | 羞羞视频入口 | 欧美国产日韩在线观看成人 | 男女隐私免费视频 | 日本黄色一级视频 | 久久精品免费国产 | 女人裸体让男人桶全过程 | 少妇的肉体的满足毛片 | 免费看污视频在线观看 | 久久999久久 | 蜜桃传媒视频麻豆第一区免费观看 | 国产精品视频免费在线观看 | 777sesese| 毛片免费视频网站 | 92看片淫黄大片一级 |