自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

No.js 中 V8 堆外內(nèi)存管理和字符編碼解碼的實(shí)現(xiàn)

開(kāi)發(fā) 前端
對(duì)于基于 V8 的 JS 運(yùn)行時(shí)來(lái)說(shuō),堆外內(nèi)存的管理是非常重要的一部分,因?yàn)?gc 的原因,V8 自己管理堆內(nèi)存大小是有限制的,我們不能什么數(shù)據(jù)都往 V8 的堆里存儲(chǔ),比如我們想一下讀取一個(gè) 1G 的文件,如果存到 V8 的堆,一下子就滿了,所以我們需要定義堆外內(nèi)存并進(jìn)行管理。

[[427211]]

對(duì)于基于 V8 的 JS 運(yùn)行時(shí)來(lái)說(shuō),堆外內(nèi)存的管理是非常重要的一部分,因?yàn)?gc 的原因,V8 自己管理堆內(nèi)存大小是有限制的,我們不能什么數(shù)據(jù)都往 V8 的堆里存儲(chǔ),比如我們想一下讀取一個(gè) 1G 的文件,如果存到 V8 的堆,一下子就滿了,所以我們需要定義堆外內(nèi)存并進(jìn)行管理。本文介紹 No.js 里目前支持的簡(jiǎn)單堆內(nèi)存管理機(jī)制和字符編碼解碼的實(shí)現(xiàn)。

1 字符串的使用

數(shù)據(jù)的讀寫(xiě),在底層都是一個(gè)個(gè)字節(jié),那么我們?cè)?JS 層定義的字符串,C++ 層是怎么獲取的呢?比如我們?cè)?JS 里調(diào)用自定義 log 函數(shù)打印日志。

  1. log("hello"); 

我們來(lái)看看 JS 運(yùn)行時(shí)中 log 函數(shù)的實(shí)現(xiàn)。

  1. void No::Console::log(V8_ARGS) { 
  2.     V8_ISOLATE 
  3.     String::Utf8Value str(isolate, args[0]); 
  4.     Log(*str); 

最終在 C++ 里可以通過(guò) V8 提供的 String::Utf8Value 從 args 中獲得 JS 層的字符串,然后調(diào)用系統(tǒng)函數(shù)把它打印到屏幕就行。但是這種形式使用的內(nèi)容是 V8 的堆內(nèi)存。那么如果我們需要操作一個(gè)非常大的字符串,那怎么辦呢?這時(shí)候就需要使用 V8 提供的堆外內(nèi)存機(jī)制 ArrayBuffer。

2 ArrayBuffer 的實(shí)現(xiàn)

我們看看這個(gè)類(lèi)關(guān)于內(nèi)存申請(qǐng)的一些實(shí)現(xiàn)細(xì)節(jié)。當(dāng)我們?cè)?JS 里執(zhí)行以下代碼時(shí)

  1. new ArrayBuffer(1) 

來(lái)看看 V8 的實(shí)現(xiàn)。

  1. BUILTIN(ArrayBufferConstructor) { 
  2.   // [[Construct]]  args 為 JS 層的參數(shù) 
  3.   Handle<JSReceiver> new_target = Handle<JSReceiver>::cast(args.new_target()); 
  4.   // JS 層定義的長(zhǎng)度,即 ArrayBuffer 的第一個(gè)參數(shù) 
  5.   Handle<Object> length = args.atOrUndefined(isolate, 1); 
  6.  
  7.   return ConstructBuffer(isolate,  
  8.                          target,  
  9.                          new_target,  
  10.                          number_length, // = length 
  11.                          number_max_length,  // 空 
  12.                          InitializedFlag::kZeroInitialized); 

接著看 ConstructBuffer 。

  1. Object ConstructBuffer(Isolate* isolate, Handle<JSFunction> target, 
  2.                        Handle<JSReceiver> new_target, Handle<Object> length, 
  3.                        Handle<Object> max_length, InitializedFlag initialized) { 
  4.       // resizable = ResizableFlag::kNotResizable 
  5.       ResizableFlag resizable = max_length.is_null() ? ResizableFlag::kNotResizable : ResizableFlag::kResizable; 
  6.       // 申請(qǐng)一個(gè) JSArrayBuffer 對(duì)象,不包括存儲(chǔ)數(shù)據(jù)的內(nèi)存                                               
  7.       Handle<JSObject> result; 
  8.       ASSIGN_RETURN_FAILURE_ON_EXCEPTION( 
  9.           isolate, result, 
  10.           JSObject::New(target, new_target, Handle<AllocationSite>::null())); 
  11.       auto array_buffer = Handle<JSArrayBuffer>::cast(result); 
  12.      
  13.       size_t byte_length; 
  14.       size_t max_byte_length = 0; 
  15.       // byte_length:需要申請(qǐng)的字節(jié)數(shù),由 length Object 解析得到,并且校驗(yàn)申請(qǐng)的大小是否超過(guò)閾值 
  16.       if (!TryNumberToSize(*length, &byte_length) || 
  17.           byte_length > JSArrayBuffer::kMaxByteLength) { 
  18.           // ... 
  19.       } 
  20.       std::unique_ptr<BackingStore> backing_store; 
  21.       // 申請(qǐng)存儲(chǔ)數(shù)據(jù)的內(nèi)存 
  22.       backing_store = BackingStore::Allocate(isolate, byte_length, shared, initialized); 
  23.       max_byte_length = byte_length; 
  24.       // 保存ArrayBuffer 存儲(chǔ)數(shù)據(jù)的內(nèi)存 
  25.       array_buffer->Attach(std::move(backing_store)); 
  26.       array_buffer->set_max_byte_length(max_byte_length); 

以上代碼首先申請(qǐng)了一個(gè) JSArrayBuffer 對(duì)象,但是申請(qǐng)的對(duì)象中不包括存儲(chǔ)數(shù)據(jù)的內(nèi)存,接著通過(guò) BackingStore::Allocate 申請(qǐng)存儲(chǔ)數(shù)據(jù)的內(nèi)存,并且保存到 JSArrayBuffer 中。我們接著看 BackingStore::Allocate 的內(nèi)存分配邏輯。

  1. std::unique_ptr<BackingStore> BackingStore::Allocate( 
  2.     Isolate* isolate, size_t byte_length, SharedFlag shared, 
  3.     InitializedFlag initialized) { 
  4.   void* buffer_start = nullptr; 
  5.   // ArrayBuffer 的內(nèi)存分配器,初始化 V8 的時(shí)候可以設(shè)置 
  6.   auto allocator = isolate->array_buffer_allocator(); 
  7.   if (byte_length != 0) { 
  8.     auto allocate_buffer = [allocator, initialized](size_t byte_length) { 
  9.       void* buffer_start = allocator->Allocate(byte_length); 
  10.       return buffer_start; 
  11.     }; 
  12.     // 執(zhí)行 allocate_buffer 分配內(nèi)存 
  13.     buffer_start = isolate->heap()->AllocateExternalBackingStore(allocate_buffer, byte_length); 
  14.   } 
  15.   / 分配一個(gè) BackingStore 對(duì)象管理上面申請(qǐng)的內(nèi)存 
  16.   auto result = new BackingStore(...); 
  17.   return std::unique_ptr<BackingStore>(result); 

我們看到最終通過(guò) allocator->Allocate 分配內(nèi)存,allocator 是在初始化 V8 的時(shí)候設(shè)置的,比如 No.js 設(shè)置的 ArrayBuffer::Allocator::NewDefaultAllocator()。

  1. v8::ArrayBuffer::Allocator* v8::ArrayBuffer::Allocator::NewDefaultAllocator() { 
  2.   return new ArrayBufferAllocator(); 

我們看看 ArrayBufferAllocator。

  1. class ArrayBufferAllocator : public v8::ArrayBuffer::Allocator { 
  2.  public
  3.   void* Allocate(size_t length) override { 
  4.     return page_allocator_->AllocatePages(nullptr, RoundUp(length, page_size_), 
  5.                                           page_size_, 
  6.                                           PageAllocator::kReadWrite); 
  7.   } 
  8.  private: 
  9.   PageAllocator* page_allocator_ = internal::GetPlatformDataCagePageAllocator(); 
  10.   const size_t page_size_ = page_allocator_->AllocatePageSize(); 
  11. }; 

最終調(diào)用 page_allocator_ 去分配內(nèi)存,從 page_allocator_ 的值 GetPlatformDataCagePageAllocator 我們可以看到這里是調(diào)用系統(tǒng)相關(guān)的函數(shù)去申請(qǐng)內(nèi)存,比如 Linux 下的 mmap。至此我們看到了 ArrayBuffer 的內(nèi)存由來(lái),

3 ArrayBuffer 應(yīng)用

有了 ArrayBuffer,我們就可以在 V8 堆之外申請(qǐng)內(nèi)存了,我們看看 No.js 里怎么使用。

  1. http.createServer({host: '127.0.0.1', port: 8888}, (req, res) => { 
  2.     // HTTP 響應(yīng)的 body 
  3.     const body  = `...`; 
  4.     // HTTP 響應(yīng)報(bào)文 
  5.     const response = `...`; 
  6.     // 申請(qǐng)堆外內(nèi)存 
  7.     const responseBuffer = new ArrayBuffer(response.length); 
  8.     // 把響應(yīng)內(nèi)容寫(xiě)入堆外內(nèi)存 
  9.     const bytes = new Uint8Array(responseBuffer); 
  10.     for (let i = 0; i < response.length; i++) { 
  11.         bytes[i] = response[i].charCodeAt(0); 
  12.     } 
  13.     // 發(fā)送給客戶端 
  14.     res.write(responseBuffer); 
  15. }); 

接著我們看看 write 的實(shí)現(xiàn)。

  1. // 拿到 JS 的 ArrayBuffer 
  2. Local<ArrayBuffer> arrayBuffer = args[1].As<ArrayBuffer>(); 
  3. std::shared_ptr<BackingStore> backing = arrayBuffer->GetBackingStore();// 申請(qǐng)一個(gè)寫(xiě)請(qǐng)求struct io_request *io_req = (struct io_request *)malloc(sizeof(*io_req));memset(io_req, 0, sizeof(*io_req));// 拿到底層存儲(chǔ)數(shù)據(jù)的內(nèi)存,保存到 request 中等待發(fā)送 
  4. io_req->buf = backing->Data(); 
  5. io_req->len = backing->ByteLength(); 

JS 層設(shè)置數(shù)據(jù),然后在 C++ 層拿到存儲(chǔ)數(shù)據(jù)的內(nèi)存發(fā)送出去,這個(gè)看起來(lái)可以滿足需求,但是似乎還不夠,首先每次都要自己申請(qǐng)一個(gè) ArrayBuffer 和 Uint8Array 比較麻煩,而且還需要自己設(shè)置 Uint8Array 的內(nèi)容,最重要的是 Uint8Array 只能保存單字節(jié)的數(shù)據(jù),如果我們要發(fā)送非單字節(jié)的字符就會(huì)出現(xiàn)問(wèn)題了。比如 “??“ 在 JS 里長(zhǎng)度是 2,底層占四個(gè)字節(jié)。

  1. '𠮷'.length => 2 

所以還需要封裝一個(gè)模塊處理這些問(wèn)題。

4 Buffer

類(lèi)似 Node.js,No.js 也提供 Buffer 模塊處理 V8 堆外內(nèi)存,但是 No.js 沒(méi)有 Node.js 實(shí)現(xiàn)的功能那么多。下面我們看看如何實(shí)現(xiàn)。

  1. class Buffer { 
  2.     bytes = null
  3.     memory = null
  4.     constructor({ length }) { 
  5.         this.memory = new ArrayBuffer(length); 
  6.         this.bytes = new Uint8Array(this.memory); 
  7.         this.byteLength = length; 
  8.     } 
  9.  
  10.     static from(str) { 
  11.         const chars = toUTF8(str); 
  12.         const buffer = new Buffer({length: chars.length}); 
  13.         for (let i = 0; i < buffer.byteLength; i++) { 
  14.             buffer.bytes[i] = chars[i]; 
  15.         } 
  16.         return buffer; 
  17.     } 
  18.  
  19.     static toString(bytes) { 
  20.         return fromUTF8(bytes); 
  21.     } 

使用的方式和 Node.js 一樣。

  1. Buffer.from("你好"

字符串通過(guò) Buffer 類(lèi)實(shí)現(xiàn),Buffer 封裝了 ArrayBuffer 和 Uint8Array,不過(guò)更重要的是實(shí)現(xiàn)了 UTF-8 編碼和解碼,這樣應(yīng)用層就可以傳任何字符串,Buffer 會(huì)轉(zhuǎn)成對(duì)應(yīng)的 UTF-8 編碼(一系列二進(jìn)制數(shù)據(jù)),處理完后再通過(guò)底層傳輸就可以??匆幌?UTF-8 編碼解碼的實(shí)現(xiàn)。

  1. function toUTF8(str) { 
  2.     // 通過(guò) ... 解決多字節(jié)字符問(wèn)題 
  3.     const chars = [...str]; 
  4.     const bytes = []; 
  5.     for (let i = 0; i < chars.length; i++) { 
  6.         const char = chars[i]; 
  7.         const code = char.codePointAt(0); 
  8.         if (code > 0 && code < 0x7F) { 
  9.             bytes.push(code) 
  10.         } else if (code > 0x80 && code < 0x7FF) { 
  11.             bytes.push((code >> 6) & 0x1f | 0xC0); 
  12.             bytes.push(code & 0x3f | 0x80);   
  13.         } else if ((code > 0x800 && code < 0xFFFF) || (code > 0xE000 && code < 0xFFFF)) { 
  14.             bytes.push((code >> 12) & 0x0f | 0xE0); 
  15.             bytes.push((code >> 6) & 0x3f | 0x80); 
  16.             bytes.push(code & 0x3f | 0x80);  
  17.         } else if (code > 0x10000 && code < 0x10FFFF) { 
  18.             bytes.push((code >> 18) & 0x07 | 0xF0); 
  19.             bytes.push((code >> 12) & 0x3f | 0x80); 
  20.             bytes.push((code >> 6) & 0x3f | 0x80); 
  21.             bytes.push(code & 0x3f | 0x80);  
  22.         }  
  23.     } 
  24.     return bytes; 

toUTF8 把字符的 Unicode 碼變成 UTF-8 編碼,具體實(shí)現(xiàn)就是根據(jù) UTF-8 的規(guī)則,但是有一個(gè)地方需要注意的是,不能簡(jiǎn)單遍歷 JS 字符串。比如 “??“ 在遍歷的時(shí)候情況如下

  1. '𠮷'[0] => '\uD842''𠮷'[1] => '\uDFB7' 

所以需要處理一下使得每個(gè)字符變得一個(gè)獨(dú)立的元素,再獲得它的 unicode 碼進(jìn)行處理。

  1. const chars = [...str]; 

接著看看 解碼。

  1. // 計(jì)算二進(jìn)制數(shù)最左邊有多少個(gè)連續(xù)的 1 
  2. function countByte(byte) { 
  3.     let bytelen = 0; 
  4.     while(byte & 0x80) { 
  5.         bytelen++; 
  6.         byte = (byte << 1) & 0xFF; 
  7.     } 
  8.     return bytelen || 1;} 
  9.  
  10. function fromUTF8(bytes) { 
  11.     let i = 0; 
  12.     const chars = []; 
  13.     while(i < bytes.length) { 
  14.         const byteLen = countByte(bytes[i]); 
  15.         switch(byteLen) { 
  16.             case 1: 
  17.                 chars.push(String.fromCodePoint(bytes[i])); 
  18.                 i += 1; 
  19.                 break; 
  20.             case 2: 
  21.                 chars.push(String.fromCodePoint( (bytes[i] & 0x1F) << 6 | (bytes[i + 1] & 0x3F) )); 
  22.                 i += 2; 
  23.                 break; 
  24.             case 3: 
  25.                 chars.push(String.fromCodePoint( (bytes[i] & 0x0F) << 12 | (bytes[i + 1] & 0x3F) << 6| (bytes[i + 2] & 0x3F) )); 
  26.                 i += 3; 
  27.                 break; 
  28.             case 4: 
  29.                 chars.push(String.fromCodePoint( (bytes[i] & 0x07) << 18 | (bytes[i + 1] & 0x3F) << 12 | (bytes[i + 2] & 0x3F) << 6 | (bytes[i + 3] & 0x3F) )); 
  30.                 i += 4; 
  31.                 break; 
  32.             default
  33.                 throw new Error('invalid byte'); 
  34.         } 
  35.     } 
  36.     return chars.join(''); 

解碼的原理是首先計(jì)算單字節(jié)的最左邊有多少個(gè) 1,這個(gè)表示后續(xù)的多少個(gè)字節(jié)組成一個(gè)字符。計(jì)算完后就把一個(gè)或多個(gè)字節(jié)按照 UTF-8 規(guī)則拼出 unicode 碼,然后使用 fromCodePoint 轉(zhuǎn)成對(duì)應(yīng)字符。最后看看使用例子。

  1. http.createServer({host: '127.0.0.1', port: 8888}, (req, res) => { 
  2.     const body  = `<html> 
  3.         <head></head> 
  4.         <body> 
  5.             你好! 
  6.         </body> 
  7.         </html>`; 
  8.     res.setHeaders({ 
  9.         "Content-Type""text/html; charset=UTF-8" 
  10.     }); 
  11.     res.end(body); 
  12. }); 

5 總結(jié)

 

 

 

目前初步實(shí)現(xiàn)了堆外內(nèi)存管理和編碼解碼的功能,這樣應(yīng)用層就不需要面對(duì)麻煩的堆外內(nèi)存管理和數(shù)據(jù)設(shè)置問(wèn)題。另外 V8 堆外內(nèi)存我們平時(shí)可能關(guān)注的不是很多,但是卻是一個(gè)重要的部分。

 

責(zé)任編輯:武曉燕 來(lái)源: 編程雜技
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)