王杰
贫民
贫民
  • UID597
  • 粉丝0
  • 关注0
  • 发帖数2
阅读:6393回复:0

unity发布的WebGl内存研究

楼主#
更多 发布于:2019-08-19 14:47
WebGl的内存研究经验

一、研究背景
一个基于Unity实现的三维可视化开发平台,该平台是一个动态加载模型、搭建场景的网页程序。所以该项目需要发布成WebGl版本,因此需要考虑到模型加载对WebGl内存使用的影响。这就有了本文对WebGl内存使用的研究了,目的是将WebGl内存使用情况实时显示在网页上,主要是WebGl内存占用率。一开始觉得这个插件应该很好实现,但是研究和测试后才发现这里面的坑是很深的。下面就讲讲这些我遇到的这些坑。

二、Process类
1.定义
负责启动和停止本机进程,获取和设置进程优先级,确定进程是否响应,是否以及退出,以及获取系统正在运行的所有进行列表和各进程资源占用情况。一也可以查询远程计算机上进程相关信息,包括进程内的线程集合、加载模块(.dll文件和.exe文件)和性能信息(如当前进程使用的内存量)
2.属性和方法详见
https://blog.csdn.net/qy1387/article/details/7729117。
下面列举一些常用到的属性以及方法
Id:进程ID,也就是任务管理器中的PID。
ProcessName:进程名称,也就是exe程序的文件名称去掉后缀。
MainModule:获取关联进程的主模块,返回类型为ProcessModule。主模块就是Main函数所在的exe文件。当访问该属性出现Win32Exception异常,表示32bit进程访问64bit进程模块,通过VS->Properties->Build->General->Platform Target(目标平台)->将Any Cpu或x86设置为x64即可。
Threads:获取进程中运行的线程,也就是当前进程关联的所有线程,主线程不一定是索引为0的线程。发挥类型为ProcessThread集合类型。
WorkingSet64:获取关联进程分配的物理内存。
3. 获取当前进程
Process process = Process.GetCurrentProcess()//获取当前进程
4. PerformanceCounter性能计数器
PerformanceCounter(categoryName,counterName,instanceName)
功能:初始化PerformanceCounter类的新的只读实例,并将其与本地计算机上指定的系统性能计数器或自定义性能计数器及类别实例关联。
参数说明:categoryName想要关联的性能计数器类别名称,counterName性能计数器名称,instanceName性能计数器类别的实例。
性能计数器类别表(有很多,只列举最频繁使用的):“Cache”(缓存)、“Memory”(内存)、“Objects”(对象)、“PhysicalDisk”(物理磁盘)、“Process”(进程)、“Processor”(处理器)、“Server”(服务)、“System”(系统)、“Thread”(线程)...
使用下面方法可获取性能计数器类别下的性能计数器名称
1. public static void GetInstanceNameListANDCounterNameList(string CategoryName)
2. {  
3.    string[] instanceNames;  
4.    ArrayList counters = new ArrayList();  
5.    PerformanceCounterCategory mycat = new PerformanceCounterCategory(CategoryName);  
6.    try  
7.    {  
8.        instanceNames = mycat.GetInstanceNames();  
9.        if (instanceNames.Length == 0)  
10.        {  
11.            counters.AddRange(mycat.GetCounters());  
12.        }  
13.        else  
14.        {  
15.            for (int i = 0; i < instanceNames.Length; i++)  
16.            {  
17.                counters.AddRange(mycat.GetCounters(instanceNames));  
18.            }  
19.        }  
20.        for (int i = 0; i < instanceNames.Length; i++)  
21.        {  
22.            Console.WriteLine(instanceNames);  
23.        }  
24.        Console.WriteLine("******************************");  
25.        foreach (PerformanceCounter counter in counters)  
26.        {  
27.            Console.WriteLine(counter.CounterName);  
28.        }  
29.    }  
30.    catch (Exception)  
31.    {  
32.        Console.WriteLine("Unable to list the counters for this category");
33.    }  
34. }  

5. 获取性能计数器返回的对象
1. PerformanceCounter curpc = new PerformanceCounter("Process", "Working Set", cur.ProcessName);  
2. PerformanceCounter curpcp = new PerformanceCounter("Process", "Working Set - Private", cur.ProcessName);  
其中curpc为工作集的性能计数器,通过curpc.NextValue()获取该进程占总内存,单位为B。
curpcp为私有工作集的性能计数器,通过curpcp.NextValue()获取该进程单独占用内存,单位为B。
6. 以为这样就结束了么
这个想法是不错的,但是拿到unity的代码中运行测试。Unity会报错,内容为
1. The type or namespace name `PerformanceCounter' could not be found. Are you missing a using directive or an assembly reference?  
但最神奇的是C#里面没有报错。百思不得其解,最终还是放弃这条路。
7. 根据上面的情况,又将改方法拿到C#程序中运行。居然又没有错,能够获得指定线程的相关信息。之前用过一种方法,就是unity调用C#项目的exe文件,并能够将运行exe文件后输出到控制台的信息进行捕捉。这样一来也间接达到监测WebGl内存的目的。在编辑器运行的时候没问题,但是发布到webgl后就发现根本调用不了这个exe文件。原因是webgl无法调用外部文件,无奈,这条路也放弃。

三、JavaScript
1. 在网上也查了不少资料,有看到说使用js通过wmi获取计算机进程信息,当时就兴奋了。马上研究js访问本地系统信息,发现很多人使用以下方法。
1. var locator = new ActiveXObject ("WbemScripting.SWbemLocator");  
2.    var service = locator.ConnectServer(".");  
3.    var properties = service.ExecQuery("select * from Win32_Processor");//CPU 信息  
然而,当我将代码放到html里面在谷歌浏览器上运行的时候报错,ActiveXObject is not finded,经过查询,说是要将Internet选项->安全->自定义级别->对未标记可安全执行脚本的ActiveX控件初始化并执行脚本:设置为提示;对下载为签名的ActiveX控件:设置为提示。然后再去运行,惊喜的发现还是无法运行。仍然报上面的错误。最后好不容易看到个信息,ActiveX是微软专有的,只有基于IE内核的浏览器才能使用,之前使用谷歌无法运行也是有了解释。很明显谷歌并不是基于IE内核浏览器。然而在IE浏览器上运行该网页后,又报错,说Automation 服务器不能创建对象。
到这里我已经很不想继续弄下去了,但是责任使然,我继续去查该问题,发现解决方法就是配置Internet选项里的信息。但是,差一点我就错过了正确的结果,嘿嘿。这个internet选项不是控制面版里的,就是ie浏览器上的internet选项,在工具里面找到,并配置里面的值。这里有点小小的不同,说不好说,直接上图片,图片是网上某某大佬的:

描述:1

图片:1.png

1
1

描述:2

图片:2.png

2
2

[td=1,1,94.6667]

描述:3

图片:3.png

3
3

2.设置完成后就一路确定到结束,最后在重新打开IE浏览器,再次运行该网页,你就可以发现,网上就可以显示你的电脑信息了。惊不惊喜,刺不刺激!下面上源码:
1. <!doctype html>  
2. <html lang="en-us">  
3.  <head>  
4.    <meta charset="utf-8">  
5.    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">  
6.    <title>Unity WebGL Player | ControlSence_Test1.0_unity5.3.1f1</title>  
7.    <link rel="stylesheet" href="TemplateData/style.css">  
8.    <link rel="shortcut icon" href="TemplateData/favicon.ico" />  
9.    <script src="TemplateData/UnityProgress.js"></script>  
10.    <script type="text/javascript" src="js/jquery1.9.1/jquery-1.9.1.js" ></script>  
11. <script type="text/javascript" src="js/jqueryStudy.js" ></script>  
12.  
13.  </head>  
14.  <body class="template">  
15.      
16.    <script type='text/javascript'>  
17.    
18.  document.write("");  
19.  function GetInformation(){  
20.    var locator = new ActiveXObject ("WbemScripting.SWbemLocator");  
21.    var service = locator.ConnectServer(".");  
22.    var properties = service.ExecQuery("select * from Win32_Processor");//CPU 信息  
23.    var value = "";  
24.    var e = new Enumerator (properties);  
25.    for (; !e.atEnd(); e.moveNext()) {  
26.        var p = e.item ();  
27.        value += p.Name ;  
28.    }  
29.    return value;  
30.  }  
31.  document.write(GetInformation());  
32.  //下面是获取电脑基本信息
33. function getSysInfo(){  
34.     var locator = new ActiveXObject ("WbemScripting.SWbemLocator");    
35.     var service = locator.ConnectServer(".");    
36.     //CPU信息  
37.     var cpu = new Enumerator (service.ExecQuery("SELECT * FROM Win32_Processor")).item();    
38.     var cpuType=cpu.Name,hostName=cpu.SystemName;  
39.     //内存信息  
40.    var memory = new Enumerator (service.ExecQuery("SELECT * FROM Win32_PhysicalMemory"));  
41.    for (var mem=[],i=0;!memory.atEnd();memory.moveNext()) mem[i++]={cap:memory.item().Capacity/1024/1024,speed:memory.item().Speed}  
42.    //系统信息  
43.    var system=new Enumerator (service.ExecQuery("SELECT * FROM Win32_ComputerSystem")).item();  
44.    var physicMenCap=Math.ceil(system.TotalPhysicalMemory/1024/1024),curUser=system.UserName,cpuCount=system.NumberOfProcessors  
45.      
46.    return {cpuType:cpuType,cpuCount:cpuCount,hostName:hostName,curUser:curUser,memCap:physicMenCap,mem:mem}  
47. }  
48. document.write("<h3>");  
49. document.write("CpuType:"+getSysInfo().cpuType+"
"
);  
50. document.write("CpuCount:"+getSysInfo().cpuCount+"
"
);  
51. document.write("HostName:"+getSysInfo().hostName+"
"
);  
52. document.write("CurUser:"+getSysInfo().curUser+"
"
);  
53. document.write("MemCap:"+getSysInfo().memCap+"
"
);  
54. document.write("Mem:"+getSysInfo().mem[0].cap+"
"
);  
55. document.write("</h3>");  
56.  
57.  
58.  
59.  
60. </script>  
61.  
62. <script src="Release/UnityLoader.js"></script>  
63.  
64.  </body>  
65. </html>  
这上面就是网页源码,最后上效果图:
[td=1,1,11.3333]
                   

描述:4

图片:4.png

4

4

MenCap是计算机物理内存,Mem为计算机运行内存。
能实现这一步已经是个好的开始了,虽然开始了很久,才迈出第一步,我们继续往下走,获取进程信息。
3.获取进程信息,包括内存占用情况
首先要找到,进程类,通过上面代码类推出来,很明显进程类就是Win32_Process。果然将字符串替换后,直接就得到本机进正在运行进程信息(只有进程名字)。当然获取名字肯定是不够的,也就自己去百度了,也算是找到了,https://docs.microsoft.com/zh-cn/windows/win32/cimwin32prov/win32-process 该网址上有关于Win32_Process类的各种属性。我也是找了半天才找到这个东西的,发现和Process类差不多。因为wmi好像只能用在基于IE内核的浏览器上,我电脑上也就只有IE浏览器,所以其他的还没有测试。因此在运行之前要对浏览器进行判断。判断代码如下
1. var explorer=navigator.userAgent.toLowerCase();  
2. document.write(explorer);  
3. var browser;  
4.  
5. if(explorer.indexOf("chrome")>-1){  
6.    browser="chrome";  
7. }  
8. else if(explorer.indexOf("firefox")>-1){  
9.    browser="firefox";  
10. }  
11. else if(explorer.indexOf("opera")>-1){  
12.    browser="opera";  
13. }  
14. else if(explorer.indexOf("safari")>-1){  
15.    browser="safari";  
16. }else if(explorer.indexOf(" trident")>-1){  
17.    browser="iexplore";  
18. }  
19. document.write("
"
+browser);  
其中explorer为浏览器信息,包括内核、名称、版本...。只要用字符串进行匹配一下就知道是什么浏览器了。IE浏览器没有名字,就只有用IE的名字来代替。
根据上面网页中提到的属性,我们可以计算:进程内存占用率=工作集大小/最大工作集大小。上代码:

1. <!doctype html>  
2. <html lang="en-us">  
3.  <head>  
4.    <meta charset="utf-8">  
5.    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">  
6.    <title>Unity WebGL Player | ControlSence_Test1.0_unity5.3.1f1</title>  
7.    <link rel="stylesheet" href="TemplateData/style.css">  
8.    <link rel="shortcut icon" href="TemplateData/favicon.ico" />  
9.    <script src="TemplateData/UnityProgress.js"></script>  
10.    <script type="text/javascript" src="js/jquery1.9.1/jquery-1.9.1.js" ></script>  
11. <script type="text/javascript" src="js/jqueryStudy.js" ></script>  
12.  
13.  </head>  
14.  <body class="template">  
15.    <input type="button" value="Reload page"  
16. onclick="reloadPage()" />  
17.    <script type='text/javascript'>  
18.  
19.    var browser=navigator.appName  
20. var b_version=navigator.appVersion  
21. var version=parseFloat(b_version)  
22.  
23. var explorer=navigator.userAgent.toLowerCase();  
24. document.write(explorer);  
25. var browser;  
26.  
27. if(explorer.indexOf("chrome")>-1){  
28.    browser="chrome";  
29. }  
30. else if(explorer.indexOf("firefox")>-1){  
31.    browser="firefox";  
32. }  
33. else if(explorer.indexOf("opera")>-1){  
34.    browser="opera";  
35. }  
36. else if(explorer.indexOf("safari")>-1){  
37.    browser="safari";  
38. }else if(explorer.indexOf(" trident")>-1){  
39.    browser="iexplore";  
40. }  
41. document.write("
"
+browser);  
42.  document.write("
"
);  
43.  function GetInformation(){  
44.    var locator = new ActiveXObject ("WbemScripting.SWbemLocator");  
45.    var service = locator.ConnectServer(".");  
46.    var properties = service.ExecQuery("select * from Win32_Process where Name = '"+browser+".exe' ");//CPU 信息  
47.    var value = "";  
48.    var e = new Enumerator (properties);  
49.    for (; !e.atEnd(); e.moveNext()) {  
50.        var p = e.item ();  
51.        value += p.Name + "_____"+p.ProcessId+"_____"+p.WorkingSetSize/1024+"K_____"+p.MaximumWorkingSetSize+"_____"+(p.WorkingSetSize/1024/1024/p.MaximumWorkingSetSize)+"%
"
;  
52.    }  
53.    return value;  
54.  }  
55.  document.write(GetInformation());    
56. function reloadPage()  
57.  {  
58.  window.location.reload()  
59.  }  
60.  
61.  
62. </script>  
63.
64. <script src="Release/UnityLoader.js"></script>  
65.  </body>  
66. </html>  
在查询进程信息的时候,添加了一个条件判断,只查找该浏览器的进程。运行后效果图如下:
[td=1,1,11.3333]
                               

描述:5

图片:5.png

5

5

其中倒数四行数据为IE浏览器的进程记录,每条记录从左到右的字段分别为:进程名称、进程ID、进程工作集大小(内存大小)、最大工作集大小(M)、内存占用率。
弄到这一步也不是那么容易的,然后就测试内存占满后浏览器是否会崩盘。写了个小程序,网页加载模型,一直加一直加,发现浏览器崩盘的时候,该进程的内存占用率才73%。想了想浏览器是一个程序集,有多个进程,其最大工作集大小为总的大小,所以需要加上浏览器其他进程的内存占有率。然而惊喜的发现内存占用率依旧只有87%左右。然后就是各种测试找错误的地方...
无意之间想到进程有个最大工作集大小,还有个最小工作集大小,还有个WebGl设置内存大小。突然灵光一现。最大的能够理解,最小的突然就不好理解了。为啥会有最小的工作集大小,一般来说,不用的时候不就最小么。然而不是这样的,最小工作集大小可以理解为进程基本大小,例如我上面的模型加载就是在最小工作集上往上加内存。再就是webgl设置大小,就是允许webgl在浏览器上能够使用的内存大小。因此内存占用率就该是这样的:
内存占用率=内存占用大小/(最小工作集大小+webgl设置内存大小)

这样再来算进程的内存占用率就完美了。然而事实就是那么回事,再次测试加载模型发现,进程ID为5876的页面就是测试页面。浏览器崩溃时,该进程内存占用率达到98.13%。几乎是占满了,为啥没有到100%,那是因为浏览器还有其他进程在使用内存。
其中代码改动地方:
1. value += p.Name + "_____"+p.ProcessId+"_____"+p.WorkingSetSize/1024+"K_____"+p.MinimumWorkingSetSize+"_____"+(p.WorkingSetSize/1024/1024/(p.MinimumWorkingSetSize+512))+"%
"
;  

效果图如下:

描述:6

图片:6.png

6

6

上面数据中的200为最小工作集大小(M)。当浏览器崩溃的时候,将后面内存占用率加起来就很接近100%了。
以上就是对WebGl内存检测研究的坎坷旅程。虽然限制了使用范围,但是基于IE内核的浏览器还是蛮多的,比如猎豹安全浏览器、蚂蚁浏览器、腾讯TT、 QQ浏览器、爱帆浏览器、360浏览器、搜狗浏览器、瑞影浏览器、极速云浏览器、360极速浏览器、百度浏览器...当然这些都是网上说的,我也没有去验证,有机会就把这些浏览器安装后测试一番。


游客

返回顶部