日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網為廣大站長提供免費收錄網站服務,提交前請做好本站友鏈:【 網站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

作者:chrongzhang,騰訊 WXG 客戶端開發工程師

這是一篇介紹微信小游戲客戶端底層,如果進行優化,可以讓所有小游戲獲得更好性能的文章。不是你想像的怎么優化某個小游戲的文章。來都來了,就了解一下吧:)

小游戲主要分為渲染和邏輯兩部分。渲染優化能讓渲染相關的指令(WebGL/GFX)得到更高效的執行,邏輯優化是讓除渲染之外的代碼也能更高效的執行,本篇主要講述邏輯相關的優化。

基礎功能優化

V8

微信小游戲是在 2017 年 12 月 28 日上線的,當時微信Android/ target=_blank class=infotextkey>安卓客戶端使用的 V8 版本還是 5.5。而 google 在 V8 上的迭代速度是很快的,其中一個大的版本變更是從 5.9 版本開始,編譯器由原來的 FullCodeGenerator + Crankshaft 變更成更加高效的 Ignition + TurboFan。

微信小游戲背后的技術優化,可以讓所有小游戲獲得更好性能

mark

V8 引擎之所以性能高,在于其出色的 JIT 執行效率。JIT 依賴了一個可以在運行時優化代碼的動態編譯器。V8 早期的 JIT 編譯器是 FullCodegen,后來是 Crankshaft,然后是一直沿用至今的 Turbofan。

升級 V8,可以獲得更高的執行性能(TurboFan)、更快的啟動速度(Snapshot + Code Caching)、更低的內存占用(64 位壓縮指針)。小游戲上線至今,客戶端使用的 V8 也一直在升級當中,從最初的 5.5,升級到 6.6,然后是 7.6,直到目前的 8.0。

JSBinding

微信小游戲對開發者暴露的是 JS 的接口,開發者調用某些 JS 函數時,最終會調用到客戶端底層的原生能力。而從 JS 到客戶端底層之間的橋接能力,就是所謂的 JS 綁定。JS 綁定又分為兩種:裸綁定和非裸綁定。裸綁定是通過 V8/JAVAScriptCore 提供的原生接口,將某個 JS 函數和原生函數實現綁定到一起,這是最直接,也是最高效的綁定方式。

非裸綁定是指通過某個 JS 和原生的通信的橋梁(evaluate/prompt/postMessage 等等),在此基礎上再封裝和轉發具體的函數調用。由于存在中間一層的轉發處理,會有額外的消耗。因此小游戲對外提供的 WebGL 等接口的實現,都采用了裸綁定的方式。直接用原生裸綁定的 API,又會存在以下問題:

  1. 原生 API 使用較復雜
  2. 不方便實現更高層次的類綁定
  3. V8 和 JavaScriptCore 的 API 差異很大,兩個平臺需要重復實現綁定

于是,我們實現了一套通用的綁定庫: jsbinding,公司內是開源的,未來計劃對外也開源。

具有如下特點:

  1. 簡單易用,支持類綁定
  2. 裸綁定,性能高
  3. 同時支持 V8 和 JavaScriptCore
  4. 支持 node addon 綁定實現

未來甚至計劃提供 WebAssembly 的綁定實現,是不是還有點小期待呢?

NodeJs/libuv

安卓客戶端已經全面擁抱 node。集成 node runtime 后,擁有了如下能力:

  1. node 內置能力(如文件、setTimeout 等)
  2. libuv 異步 IO 處理的能力

node 很多內置能力,是通過原生來實現的(node addon),屬于裸綁定,性能較高。有了 libuv 事件驅動后,可以更加靈活和高效的處理一些異步事件。比如 WebSocket 的回調,之前的處理流程是,在子線程收到 socket 消息后,將消息內容通過 JNI 調用到 Java 層,Java 層再拋到 JS 線程(也是 JVM 線程),回調到 JS。而如果使用 libuv,可以在子線程通過 uv_async_send 封裝的 ASyncCall 機制,在底層就直接拋到 JS 線程回調到 JS,避免了中間頻繁的 JNI 調用和數據傳輸的開銷。

調用鏈路優化

我們都知道,兩點之間,直線最短。代碼也是一樣,調用鏈路越短,越直接,中間的開銷就越小。

JsApi 優化

1. JsApi 調用優化

首先來看看之前 JsApi 的調用鏈路:

微信小游戲背后的技術優化,可以讓所有小游戲獲得更好性能

mark

一個 js api 的調用(WeixinJSCore.invokeHandler),首先會調用到 C/C++ 統一的回調函數 voidCallback,然后再通過 JNI 調用到 Java 的統一處理函數 callVoidJavaMethod。在這個函數里,需要根據 methodID 從 map 中找到對應的 Java Method,然后再通過多次 JNI 調用 J2V8 各種接口將 js api 的參數轉換為 Java 類型參數,最后再調用到具體 API 的 Java 實現函數 Invoke。

這個調用鏈路顯然不是前面提到的裸綁定的實現方法,因為中間還夾了一層 Java 的中轉處理層,產生了一些性能消耗。

針對 invokeHandler,縮短調用鏈路,減少 JNI 調用優化后,流程如下:

微信小游戲背后的技術優化,可以讓所有小游戲獲得更好性能

mark

針對 js 的 WeixinJSCore.invokeHandler 接口提供專門的 C++ 裸綁定接口 InvokeHandler,取出所有參數后,只需要一次 JNI 調用到 nativeInvokeHandler,然后調到具體 API 的 Java 實現函數 Invoke。

除此之外,針對異步 JsApi 調用,之前的流程是在 java 層拋到另一個線程執行。有了 libuv Looper 后,優化成在底層起一個 uv 的 worker 線程,通過 ASyncCall 將任務拋到 worker 線程,這樣 worker 里只需要執行同步的 api 流程,流程上簡化了,效率上比拋 Java 層線程更高。

2. JsApi 回調優化

當框架層需要觸發 JS 回調時,之前的做法是拼好一段 JS 字符串然后 evaluate:

evaluateJavascript(String.format(
  "typeof WeixinJSBridge !== 'undefined' && " +
  "WeixinJSBridge.invokeCallbackHandler(%d, %s)",
  callbackId, data
));

這里的本質是去調用 JS 里的統一回調處理函數 WeixinJSBridge.invokeCallbackHandler,采取了直接執行一段 JS 的方法。優點是實現簡單,缺點是效率不高。

因為讓 JS 引擎執行一段 JS 代碼時,需要先編譯,parse 抽象語法樹,生成 Ignition 字節碼,甚至啟用到 TurboFan 編譯優化器,最后才真正執行到想調用的 JS 函數。

同時每個回調都拼一個字符串執行,在 JS 引擎內部會積攢大量臨時字符串,占用內存資源。

優化的方法其實也很簡單,就是通過 jsbinding 預先查找好 WeixinJSBridge.invokeCallbackHandler 函數,在需要回調這個 JS 函數時,直接調用即可。

// 查找到 invokeCallbackHandler 函數后,保存下來
mm::JSObject func =
         JS_GET_AS(mm::JSObject, js_bridge, "invokeCallbackHandler");
js_func_holder_ = JS_NEW_OBJECT_HOLDER(func);

// ...

// 當需要回調時,直接調用
JS_CALL(js_func_holder_->Get(), nullptr, nullptr,
              js_bridge, callbackId, data);

并行調用優化

開發者在執行某些耗時較重的任務時,可以使用多線程 Worker,類比標準 H5 的 WebWorker。

// 主線程初始化 Worker
const worker = wx.createWorker('workers/request/index.js') // 文件名指定 worker 的入口文件路徑,絕對路徑
// 向 Worker 發送消息
worker.postMessage({
  msg: 'hello worker'
})

// workers/request/index.js
// 在 Worker 線程執行上下文會全局暴露一個 `worker` 對象
worker.onMessage(function (res) {
  console.log(res)
})

之前的 Worker 有個限制,只能執行一些純邏輯運算的代碼,不支持 JsApi 的調用。這很大程度限制了 Worker 的使用,于是我們也在不斷的擴展 Worker 的能力,增加了音頻、網絡、文件等能力。

// Worker 線程
var audio = worker.createInnerAudioContext()
audio.src = url
audio.play()

未來 Worker 將會賦予更多能力,提高開發者并行化處理的效率。

數據傳輸優化

開發者在 JS 層的數據(ArrayBuffer)需要傳到客戶端底層,同時客戶端底層的數據也需要傳到 JS 上層,這中間涉及到數據的高效傳輸。在渲染優化時,可以通過 wgfx 提供的 createNativeBuffer 接口,創建一塊 JS 和 Naitve 共享的內存,雙方可直接讀寫該內存而無需額外的傳輸,極大的提高了效率。

NativeBuffer 的共享內存傳輸機制,可以應用到多個需要頻繁傳輸數據的場景,比如 Camera 傳輸的數據、JS 的 WebGL CommandBuffer 傳輸等等。

還有一種情況是前面提到的 Worker 之間傳輸數據,如果通過默認的 postMessage 來傳輸,效率是非常低的,不利于傳輸較大的 ArrayBuffer 數據。為了解決這個問題,我們提供了類似標準 H5 的 SharedArrayBuffer 的能力,用來 Worker 之間高效的傳輸數據。

// game.js
const sab = wx.createSharedArrayBuffer(2)
worker.postMessage({
  sab
})

// worker.js
worker.onMessage(function (res) {
  res.sab.lock(() => {
    setTimeout(() => {
      res.sab.unlock()
    }, 3000)
  })
})

總結

小游戲的性能瓶頸,很大程度局限于 JavaScript,而我們所做的各種優化,是希望能盡量抹平 JavaScript 本身帶來的性能損耗,接近并向原生性能靠齊,極具困難和挑戰。

在 IOS 上,我們也為讓 JavaScript 擁有 JIT 能力做了深入探索。同時,我們也在 WebAssembly 上也進行了深入的探索和支持,未來有機會再進行分享。

為了小游戲有更好的運行性能,開發者能更好的發揮其創意,我們所有的性能優化還將持續不斷的迭代下去。

分享到:
標簽:優化 技術
用戶無頭像

網友整理

注冊時間:

網站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網站吧!
最新入駐小程序

數獨大挑戰2018-06-03

數獨一種數學游戲,玩家需要根據9

答題星2018-06-03

您可以通過答題星輕松地創建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學四六

運動步數有氧達人2018-06-03

記錄運動步數,積累氧氣值。還可偷

每日養生app2018-06-03

每日養生,天天健康

體育訓練成績評定2018-06-03

通用課目體育訓練成績評定