最近我一直在進(jìn)行一個(gè)項(xiàng)目,旨在創(chuàng)建一個(gè)實(shí)時(shí)高性能的JavaScript圖表庫(kù)。該項(xiàng)目使用了一個(gè)雄心勃勃且新穎的技術(shù)堆棧,其中包括一個(gè)大型的C/C++遺留代碼庫(kù),使用Emscripten編譯為WebAssembly,針對(duì)WebGL進(jìn)行渲染,并提供TypeScript API包裝器,可在JavaScript中加載圖表而無(wú)需擔(dān)心底層的Wasm。
為什么要使用WebAssembly?
WebAssembly是一項(xiàng)令人興奮的技術(shù),在許多情況下比JavaScript具有更好的性能優(yōu)勢(shì)。而且,在這種情況下,由于遺留的C++代碼庫(kù)已經(jīng)處理了OpenGL中的大部分圖表渲染工作,只需要做一些工作即可將其定位到WebGL。
使用Emscripten將現(xiàn)有的C++代碼編譯成WebAssembly非常簡(jiǎn)單,只需編寫(xiě)綁定生成類(lèi)型并在Wasm庫(kù)周?chē)帉?xiě)JavaScript API即可使用它。
在開(kāi)發(fā)庫(kù)的過(guò)程中,我們學(xué)到了一些關(guān)于WebAssembly內(nèi)存模型的有趣事實(shí),以及如何避免和調(diào)試內(nèi)存泄漏,下面我將分享給大家。
JavaScript與WebAssembly內(nèi)存模型的區(qū)別
WebAssembly與JavaScript具有完全不同的內(nèi)存模型。雖然JavaScript擁有自動(dòng)垃圾回收器,可以自動(dòng)清理不再需要的變量的內(nèi)存,但WebAssembly則沒(méi)有。在Wasm內(nèi)存中聲明的對(duì)象或緩沖區(qū)必須由調(diào)用者刪除,否則將導(dǎo)致內(nèi)存泄漏。
JavaScript中的內(nèi)存泄漏是如何引起的
無(wú)論是JavaScript還是WebAssembly,都可能發(fā)生內(nèi)存泄漏,開(kāi)發(fā)人員必須小心確保在使用WebAssembly時(shí)正確清理內(nèi)存。
盡管JavaScript是一種帶有垃圾回收機(jī)制的托管式編程語(yǔ)言,但使用純粹的JavaScript仍然很容易創(chuàng)建內(nèi)存泄漏。以下是在JavaScript應(yīng)用程序中無(wú)意中導(dǎo)致內(nèi)存泄漏的幾種方式:
箭頭函數(shù)和閉包可以捕獲變量并使其保持活動(dòng)狀態(tài),因此無(wú)法被JavaScript垃圾回收器刪除。
回調(diào)函數(shù)或事件監(jiān)聽(tīng)器可以捕獲變量并保持其活動(dòng)狀態(tài)。
全局變量或靜態(tài)變量在應(yīng)用程序的生命周期中一直存在。如果忘記使用let或const關(guān)鍵字,變量將轉(zhuǎn)換為全局變量。
即使從DOM中分離的節(jié)點(diǎn)也可以在JavaScript中保持對(duì)象的活動(dòng)狀態(tài)。僅僅移除一個(gè)節(jié)點(diǎn)但保留對(duì)它的引用變量,將阻止該節(jié)點(diǎn)及其子節(jié)點(diǎn)被回收。
WebAssembly中的內(nèi)存泄漏是如何引起的
Wasm擁有與JavaScript虛擬機(jī)不同的堆內(nèi)存。該內(nèi)存在瀏覽器中分配,并從主機(jī)操作系統(tǒng)中保留。當(dāng)您在Wasm中分配內(nèi)存時(shí),Wasm堆會(huì)增長(zhǎng),并且保留了一定范圍的地址。當(dāng)您刪除Wasm中的內(nèi)存時(shí),堆不會(huì)縮小,并且內(nèi)存也不會(huì)返回給主機(jī)操作系統(tǒng)。相反,內(nèi)存只是被標(biāo)記為已刪除或可用。這意味著它可以被未來(lái)的分配重新使用。
要在WebAssembly中引起內(nèi)存泄漏,只需分配內(nèi)存并忘記刪除它即可。由于沒(méi)有自動(dòng)垃圾回收、終結(jié)或?qū)?nèi)存標(biāo)記為不再需要的機(jī)制,必須由用戶(hù)來(lái)處理。由編譯器Emscripten導(dǎo)出的所有WebAssembly類(lèi)型都具有一種在使用Wasm內(nèi)存的對(duì)象上調(diào)用的`.delete()`函數(shù)。當(dāng)不再需要該對(duì)象時(shí),需要調(diào)用此函數(shù)進(jìn)行刪除。以下是一個(gè)快速示例:
示例:Wasm中的內(nèi)存泄漏
假設(shè)您在C++中聲明了如下類(lèi)型:
// person.cpp #include <string> class Person { public: // C++ 構(gòu)造函數(shù) Person(std::string name, int age) : name(name), age(age) {} // C++ 析構(gòu)函數(shù) ~Person() {} std::string getName() { return name; } int getAge() { return age; } private: std::string name; int age; };
`然后使用Emscripten編譯和導(dǎo)出該類(lèi)型,如下所示:
emcc person.cpp -o person.js -s EXPORTED_FUNCTIONS="['_createPerson', '_deletePerson', '_getName', '_getAge']" -s MODULARIZE=1
現(xiàn)在,您可以在JavaScript中實(shí)例化、使用和刪除該類(lèi)型,如下所示:
const Module = require('./person.js'); // 包含生成的JavaScript接口 Module.onRuntimeInitialized = () => { // 實(shí)例化一個(gè)Person對(duì)象 const person = new Module.Person('John Doe', 30); console.log('創(chuàng)建Person對(duì)象:', person); // 訪問(wèn)并打印屬性 console.log('姓名:', person.getName()); console.log('年齡:', person.getAge()); // 刪除Person對(duì)象(調(diào)用C++析構(gòu)函數(shù)) person.delete(); };
然而,如果忘記調(diào)用`.delete()`函數(shù),就會(huì)導(dǎo)致Wasm內(nèi)存泄漏。瀏覽器的內(nèi)存將增長(zhǎng)而不會(huì)縮小。
檢測(cè)WebAssembly應(yīng)用程序中的內(nèi)存泄漏
由于內(nèi)存泄漏對(duì)應(yīng)用程序來(lái)說(shuō)是災(zāi)難性的,我們不僅要確保我們的代碼不會(huì)泄漏內(nèi)存,還要確保用戶(hù)代碼(即使用我們的JavaScript圖表庫(kù)的應(yīng)用程序)不會(huì)泄漏內(nèi)存。
為此,我們開(kāi)發(fā)了內(nèi)部?jī)?nèi)存調(diào)試工具。它實(shí)現(xiàn)為一個(gè)對(duì)象注冊(cè)表,其中包含所有未刪除和未收集的對(duì)象的Map<string, TObjectEntryInfo>,其中TObjectEntryInfo是一個(gè)存儲(chǔ)對(duì)象的WeakRef的類(lèi)型。
通過(guò)使用JavaScript代理技術(shù),我們能夠攔截對(duì)所有WebAssembly類(lèi)型的new/delete的調(diào)用。每次實(shí)例化一個(gè)對(duì)象時(shí),我們將其添加到objectRegistry中;每次刪除一個(gè)對(duì)象時(shí),我們將其從objectRegistry中移除。
現(xiàn)在,您可以運(yùn)行應(yīng)用程序,啟用內(nèi)存調(diào)試工具,并輸出應(yīng)用程序狀態(tài)的特定快照。以下是該工具輸出的示例。
首先,啟用MemoryUsageHelper(內(nèi)存調(diào)試工具):
import { MemoryUsageHelper } from "scichart"; MemoryUsageHelper.isMemoryUsageDebugEnabled = true;
這將自動(dòng)跟蹤我們庫(kù)中的所有類(lèi)型,但您也可以通過(guò)調(diào)用register和unregister來(lái)跟蹤應(yīng)用程序中的任意對(duì)象:
// 注冊(cè)一個(gè)任意對(duì)象 MemoryUsageHelper.register(yourObject, "identifier"); // 注銷(xiāo)一個(gè)任意對(duì)象 MemoryUsageHelper.unregister("identifier");
稍后,在特定的點(diǎn)上通過(guò)調(diào)用此函數(shù)輸出一個(gè)快照:文章來(lái)源:http://www.zghlxwxcb.cn/article/652.html
MemoryUsageHelper.objectRegistry.log();
這將在控制臺(tái)輸出所有未被刪除或未收集的對(duì)象和它們的標(biāo)識(shí)符。通過(guò)檢查這個(gè)快照,您可以確定是否有任何內(nèi)存泄漏。文章來(lái)源地址http://www.zghlxwxcb.cn/article/652.html
到此這篇關(guān)于如何管理用于高規(guī)模服務(wù)的單寫(xiě)數(shù)據(jù)庫(kù)管理系統(tǒng)?的文章就介紹到這了,更多相關(guān)內(nèi)容可以在右上角搜索或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!