H5頁游戲內存溢出問題


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

問題表現

  • 初始化后, 第一次拼字並不卡.

  • 隨着拼的次數越來越多, 越來越卡

  • 瀏覽器任務管理器中可以看出, 內存持續升高

  • 確定內存問題, 即是卡頓第一問題

    內存飆升

代碼層面問題

  • 首先希望從代碼層面排查內存問題
  • 思考代碼后, 發現以下兩點

第二舞台直接刪除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"

  • 瀏覽器在讀取圖片之后會生成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>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM