H5頁遊戲內存溢出問題

  • 記錄本身解決的第一個H5頁的性能問題, 關於內存溢出
  • 拼字遊戲

問題表現

  • 初始化後, 第一次拼字並不卡.
  • 隨着拼的次數愈來愈多, 愈來愈卡
  • 瀏覽器任務管理器中能夠看出, 內存持續升高
  • 肯定內存問題, 便是卡頓第一問題html

    內存飆升

代碼層面問題

  • 首先但願從代碼層面排查內存問題
  • 思考代碼後, 發現如下兩點

第二舞臺直接刪除Canvas的Dom

  • 問題:
    • 每次都會從新創建第二個分享舞臺.
    • 上一次的canvas直接刪除了Dom.
  • 推斷:
    • 推斷直接刪除DOM, 並不能釋放舞臺上一系列的Bitmap圖像等
    • 上幾回圖像繼續佔用內存
  • 排查與解決:
    • 遊戲舞臺與分享舞臺只保留一個
    • 每次遊戲, 經過指針清空bitmap
  • 結果:
    • 內存無明顯變化

loader進來的臨時圖片未刪除

  • 問題:
    • 每次完成一次拼字都會請求後臺此次結果須要加載的資源
    • 資源中包含大量的圖片等, 都是經過new Image()的src加載過來的
  • 推斷:
    • 每次瀏覽器加載大量圖片, 展現後, 並未釋放圖片空間
  • 排查與解決:
    • 找出不能重複利用的圖片, 使用新的加載器
    • 一次性使用圖片後, 讓加載器爲null
  • 結果:
    • 內存少許減小
    • 遊戲已經卡頓

每次遊戲會從新new一些對象

  • 問題: 因每次進行遊戲都會new一些對象出來
  • 推斷: 懷疑是遊戲過程當中js內存溢
  • 排查: js內存變化
  • 結論: js中的內存變化不能引發內存卡頓問題.

GPU層面問題

  • 直接貼連接了: Chromium Graphics // Chrome GPU
  • 此次經過任務管理器能夠直接看出是GPU緩存的溢出
  • 哭了, 爲何上次我測得時候不顯示... 僅僅是一個內存的表示, 給我好找啊..
  • 最後仍是在安卓中進行排查, 才知道是GPU / chromium grapics的問題

觀察GPU表現體徵

  • 咱們在遊戲中發現內存的溢出主要是GPU的變化
  • 在遊戲中去除全部的序列幀後, 只保留最基本的遊戲, 任何手機都不卡.
  • 那就必定是這些圖片搞得鬼.
  • 已經把加載的圖片緩存清除掉了.
  • 咱們排除了canvas, 排除了圖片加載, 剩下的就是最基本的圖片展現了.
  • 如今惟一剩下的就是瀏覽器本身作的事情了.
  • 展現圖片, 而後清理DOM
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <style>
  
  #stage {
    width: 100%;
    height: 100%;
  }
  </style>
  <title>測試內存</title>
</head>

<body>
    <button id="add">增長圖片</button>
    <button id="clear">清除內存</button>
    <button id="addTen">增長十倍圖片</button>
    <div id="stage"></div>
    <script>
    var count = 0
    var stage = document.getElementById('stage')
    document.getElementById('add').onclick = function () {
      for (var i = 0; i < 23; i++) {
        stage.innerHTML += `<img src='./img${count}/${i}.jpg' />`
      }
      ++count
    }
    document.getElementById('clear').onclick = function () {
      stage.innerHTML = ''
      count = 0
    }
    document.getElementById('addTen').onclick = function () {
      for (var j = 0; j < 10; j++) {
        for (var i = 0; i < 23; i++) {
          stage.innerHTML += `<img src='./img${count}/${i}.jpg' />`
        }
      }
    }
    </script>
</body>

</html>

chrome中測試

  • 在添加三組十倍圖片後, 清除DOM
  • GPU和Image cache, 在瀏覽器中並不能總結出規律

webview表現特徵

  • 咱們寫了一個最簡單的demo, 在安卓上的webview進行了測試
  • 測試結果以下:
    • 初始狀態下的內存: init
    • 添加圖片並瀏覽器後的內存: add
    • 清理頁面dom後的內存: clear

總結

我的理解原理

瀏覽器中的GPU會自動緩存一段時間展現過的 "圖像". 咱們稱爲: "GPU Program Cache"web

  • 瀏覽器在讀取圖片以後會生成GPU可使用的着色器代碼
  • 在GPU使用的時候, 會自動緩存這些着色器代碼.

最後解決辦法

  • 去掉大量序列幀, 序列幀儘可能用小圖
  • 動畫儘可能用代碼實現

附帶一個測試着色器的代碼, 也是當時測試webgl的代碼

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>Document</title>
</head>

<body>
  <canvas id="glcanvas" width="700" height="500"></canvas>
  <script>
    // 獲取WebGL
    var canvas = document.getElementById('glcanvas')
    gl = canvas.getContext("webgl")

    // 建立頂點着色器
    var VSHADER_SOURCE =
      'void main() {\n' +
      '  gl_Position = vec4(0.0, 0.0, 0.0, 1.0);\n' +
      '  gl_PointSize = 10.0;\n' +
      '}\n';
    // 建立片元着色器
    // var FSHADER_SOURCE =
    //   'void main() {\n' +
    //   '  gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);\n' +
    //   '}\n';

    // 繪製圓
    var FSHADER_SOURCE = `
      #ifdef GL_ES
        precision mediump float;
      #endif
        void main() {
          float d = distance(gl_PointCoord, vec2(0.5,0.5));
          if(d < 0.5){
            gl_FragColor = vec4(1.0, 0.0, 0.0, 1.0);
          }else{ discard;} 
        }`;

    // 着色器代碼須要放到一個程序中
    var program = gl.createProgram()
    // 建立頂點着色器
    var vShader = gl.createShader(gl.VERTEX_SHADER)
    // 建立片元着色器
    var fShader = gl.createShader(gl.FRAGMENT_SHADER)

    // shader容器與着色器綁定
    gl.shaderSource(vShader, VSHADER_SOURCE)
    gl.shaderSource(fShader, FSHADER_SOURCE)

    // 將GLSE語言編寫成瀏覽器可用的代碼
    gl.compileShader(vShader)
    gl.compileShader(fShader)

    // 將着色器添加到程序上
    gl.attachShader(program, vShader)
    gl.attachShader(program, fShader)

    // 連接程序
    // 在連接操做之後, 能夠任意修改shader代碼.
    // 對shader從新編譯不會影響整個程序, 除非從新連接程序
    gl.linkProgram(program)

    // 加載並使用連接好的程序
    gl.useProgram(program)

    // 繪製一個點
    gl.clearColor(0.0, 0.0, 0.0, 1.0)
    gl.clear(gl.COLOR_BUFFER_BIT)
    gl.drawArrays(gl.POINTS, 0, 1)
  </script>
</body>

</html>
相關文章
相關標籤/搜索