在Node.js中編寫記憶體高效的軟體應用程式
在Node.js中編寫記憶體高效的軟體應用程式
軟體應用程式在計算機的主儲存器中執行,我們稱之為隨機存取儲存器(RAM)。 JavaScript尤其是Node.js(伺服器端js)允許我們為終端使用者編寫小型到大型軟體專案。處理程式的記憶體總是很棘手,因為糟糕的實現可能會阻止在給定伺服器或系統上執行的所有其他應用程式。 C和C ++程式設計師確實負責記憶體管理,因為那些潛伏在程式碼各個角落的記憶體洩漏。但是,js開發者?你在煩嗎?
由於js開發人員通常在具有高容量的專用伺服器上進行Web伺服器程式設計,因此他們可能感覺不到多工處理的滯後。即使在Web伺服器開發的情況下,我們也會執行多個應用程式,如資料庫伺服器(MySQL),快取伺服器(Redis)以及我們軟體所需的許多其他軟體。我們需要意識到它們也消耗了可用的主儲存器。如果我們隨便編寫應用程式,它會降低其他程序的效能或完全拒絕它們的記憶體分配。在本文中,我們通過解決問題並瞭解它們如何允許編寫記憶體高效的應用程式,看到Node JS構造如流,緩衝區和管道。
narenaryan /節點背壓-內部
一個補充程式碼庫,用於說明Medium ... github.com 上的節點背壓文章
問題:大量檔案複製
如果有人要求在Node.js中編寫檔案複製程式,他們會快速跳轉並建立這個程式。
該程式基本上建立了用於讀取檔案和使用給定檔名寫入檔案的控制代碼,並嘗試在讀取後將資料寫入寫入控制代碼。它適用於小檔案。
讓我們說我們的應用程式複製一個巨大的檔案(> 4GB)作為備份過程的一部分。我有一個7.4 GB大小的超高清4K電影檔案。如果我嘗試執行上面的程式將這個大檔案從我當前的目錄複製到Documents。
$ node basic_copy.js cartoonMovie.mkv ~/Documents/bigMovie.mkv`
我在Ubuntu(Linux)上得到了這個漂亮的緩衝區錯誤。
/home/shobarani/Workspace/basic_copy.js:7 if (err) throw err; ^`
RangeError: File size is greater than possible Buffer: 0x7fffffff bytes at FSReqWrap.readFileAfterStat [as oncomplete] (fs.js:453:11)`
如您所見,讀取操作失敗,因為Node JS只允許您將2GB資料讀入其緩衝區而不會更多。如何克服這一點。當您進行I / O密集型操作(複製,處理,Zip)時,最好考慮系統記憶體。
Node JS中的Streams和Buffers
為了克服上述問題,我們需要一種將大資料分成多個塊的機制,這是一種用於儲存這些塊的資料結構。緩衝區是儲存二進位制資料的資料結構。接下來,我們需要一種系統地讀/寫塊的方法。 Streams提供該功能。
緩衝區
我們可以通過初始化Buffer物件輕鬆建立緩衝區。
let buffer = new Buffer(10); # 10 is size of buffer console.log(buffer); # prints <Buffer 00 00 00 00 00 00 00 00 00 00>`
在較新版本的Node.js(> 8)中,您也可以執行此操作。
let buffer = new Buffer.alloc(10); console.log(buffer); # prints <Buffer 00 00 00 00 00 00 00 00 00 00>`
如果我們有一些資料已經像陣列或任何集合,我們可以使用它建立一個緩衝區。
let name = 'Node JS DEV'; let buffer = Buffer.from(name); console.log(buffer) # prints <Buffer 4e 6f 64 65 20 4a 53 20 44 45 5>`
緩衝區幾乎沒有重要的方法,如buffer.toString()和buffer.toJSON()來檢視儲存在其中的資料。
我們在優化程式碼的過程中不會建立原始緩衝區。 Node JS和V8 Engine通過在使用流或網路套接字時建立內部緩衝區(佇列)來實現這一點。
流
簡單來說,流就像是Node JS物件上的科幻門戶。在計算機網路中,入口是一個傳入的動作,出口是傳出的。我們在此後使用這些術語。
有四種類型的流可用:
- 可讀流(您可以從中讀取資料)
- 可寫流(您可以將資料輸入其中)
- 雙工流(對讀取和寫入都開放)
- 轉換流(用於處理入口/出口的資料的自定義雙工流(壓縮,有效性檢查))
這一行可以準確地說明為什麼應該使用流。
流API的一個重要目標,特別是stream.pipe()方法,是將資料緩衝限制在可接受的水平,使得不同速度的源和目標不會阻塞可用記憶體。
你需要一些方法來操作而不會壓倒系統。這就是我們在本文的最初句子中談到的內容。 禮貌:節點JS文件
在上圖中,我們有兩種型別的流。可讀和可寫。 .pipe()方法是一個非常基本的原語,用於將可讀流附加到可寫流。如果你不理解上面的圖表,那很好。看完我們的例子後,你可以回到這裡,一切都對你有意義。管道是一種引人注目的機制,下面我們用兩個例子說明它。
解決方案1(帶流的Naive檔案副本)
讓我們設計一個解決方案來克服我們之前討論過的巨大的檔案複製問題。為了實現這一點,我們可以建立兩個流並實現此過程。
- 在可讀流上偵聽資料塊
- 在可寫流上寫下該塊
- 跟蹤複製操作進度
讓我們將程式命名為 streams_copy_basic.js Streams,無需管道
在此程式中,我們要求使用者輸入兩個檔案(源和目標)並建立兩個流以將塊從可讀源複製到可寫目標。我們聲明瞭更多的變數來跟蹤進度並將其列印到標準輸出(這裡是控制檯)。我們訂閱了以下幾個活動:
' data ':在讀取資料塊時呼叫
' end ':從可讀流中讀取塊時呼叫
'error': 如果在閱讀過程中有任何問題,則呼叫
執行此程式,我們可以成功複製一個大檔案(在我的情況下為7.4 GB)
$ time node streams_copy_basic.js cartoonMovie.mkv ~/Documents/4kdemo.mkv`
但是,有一個問題。觀察計算機上活動/程序監視器中Node.js程序使用的記憶體。 僅以88%的副本檢視Node程序使用的記憶體
4.6GB?在這種情況下,我們的檔案複製程式的RAM使用是瘋狂的,可能會阻止其他應用程式
為什麼會這樣?
如果您在上面的圖片中觀察到磁碟的讀寫速率,那麼有一些東西可以引起您的注意。
磁碟讀取: 53.4 MiB / s
磁碟寫入: 14.8 MiB / s
這意味著生產者以更快的速度生產,消費者無法趕上節奏。儲存資料塊的計算機讀取,將多餘的資料儲存到機器的RAM中。這就是記憶體飆升的原因。
這個程式在我的機器上運行了3分16秒..
17.16s user 25.06s system 21% cpu 3:16.61 total`
解決方案2(帶流和自動背壓的檔案複製)
為了克服上述問題,我們可以修改程式,自動調整磁碟的讀寫速度。這種機制是背壓。我們不需要做太多。只需將可讀流傳輸到可寫流中即可。 Node.js負責對系統進行反壓。
讓我們將程式命名為 streams_copy_efficient.js
在這裡,我們用單個語句替換了塊寫入操作。
readabale.pipe(writeable); // Auto pilot ON!`
管道是所有魔法將要發生的原因。它控制磁碟的讀寫速度,因此不會阻塞記憶體(RAM)。
現在執行程式:
$ time node streams_copy_efficient.js cartoonMovie.mkv ~/Documents/4kdemo.mkv`
我們這次也在複製相同的大檔案(7.4 GB)。讓我們看看記憶趨勢是怎樣的。 管道是Node中的魔杖
哇!現在Node程序只消耗61.9MiB RAM。如果您觀察到磁碟的讀寫速率:
磁碟讀取: 35.5 MiB / s
磁碟寫入: 35.5 MiB / s
在任何給定時間,由於背壓,讀寫速度相同。獎勵是這個優化的程式比前一個程式快13秒。
12.13s user 28.50s system 22% cpu 3:03.35 total`
由於Node JS流和管道,記憶體負載減少了 98.68% ,執行時間也減少了。這就是為什麼我們說 管道 是一個強大的構造。
61.9 MiB是讀取流建立的緩衝區大小。我們還可以使用可讀流上的 read 方法為該緩衝區塊分配自定義大小。
const readabale = fs.createReadStream(fileName);`
readable.read(no_of_bytes_size);`
這種技術可以用於優化處理I / O的許多事情,而不是在本地複製檔案:
- 來自Kafka並進入資料庫的資料流。
- 來自檔案系統的資料流,即時壓縮並寫入磁碟。
- 還有很多...
原始碼(Git)
narenaryan /節點背壓-內部
一個補充程式碼庫,用於說明Medium ... github.com 上的節點背壓文章
結論:
我寫這篇文章的主要動機是展示我們可以多快地編寫效能不佳的壞程式,即使NodeJS為我們提供了很好的API。如果我們更多地關注內建工具,我們可以改變軟體的執行方式。
希望你喜歡這篇文章。如果您有任何疑問,請在此處或我的推特牆上發表評論。 https://twitter.com/@Narenarya3
祝你今天愉快 :)
參考文獻:
檔案系統| Node.js v11.1.0文件
在POSIX系統上,對於每個程序,核心都維護一個當前開啟的檔案和資源的表。每個開啟的檔案... nodejs.org
緩衝區| Node.js v11.1.0文件
將一個數字作為第一個引數傳遞給Buffer()(例如new Buffer(10))分配一個指定的新的Buffer物件... nodejs.org
公眾號:銀河系1號
聯絡郵箱:[email protected]
(未經同意,請勿轉載)