概念
游戏性能测试是评估游戏在不同硬件和软件环境下运行表现的过程。它涉及多个方面,包括帧率、内存使用、CPU和GPU负载、加载时间等。以下是游戏性能测试的一些关键步骤和方法:
一、测试目标设定
1. 确定性能指标:
明确需要测试的性能指标,如帧率、内存占用、CPU/GPU使用率等。
根据游戏类型和目标平台设定合理的性能目标。
2. 定义测试场景:
选择代表性的游戏场景进行测试,如开场、战斗、探索等。
设计测试用例,覆盖各种游戏功能和负载情况。
二、测试环境搭建
1. 硬件配置:
准备不同配置的设备,包括高端、中端和低端设备。
使用模拟器或真实设备进行测试。
2. 软件环境:
设置一致的操作系统版本和驱动程序。
确保测试环境稳定,避免外部干扰。
三、测试执行
1. 自动化测试:
使用自动化测试工具(如Unity Performance Testing Tools、Unreal Engine的自动化测试功能等)执行测试。
编写脚本模拟玩家操作,记录性能数据。
2. 手动测试:
进行人工操作测试,验证游戏在真实玩家操作下的表现。
观察游戏在不同负载下的行为,记录异常情况。
四、数据分析
1. 性能指标分析:
分析测试结果,重点关注帧率、内存使用、CPU/GPU负载等关键指标。
比较不同设备和配置下的性能表现,找出性能瓶颈。
2. 瓶颈诊断:
使用性能分析工具(如Unity Profiler、Unreal Engine Profiler等)深入分析性能数据。
定位导致性能问题的具体原因,如渲染、物理计算、内存管理等。
五、优化与迭代
1. 性能优化:
根据测试结果进行针对性的优化,如减少Draw Call、优化脚本、降低资源消耗等。
调整游戏设置,平衡性能与画质。
2. 回归测试:
对优化后的版本进行回归测试,验证优化效果。
确保优化措施没有引入新的问题。
六、报告与沟通
1. 测试报告:
编写详细的性能测试报告,包含测试方法、结果分析、优化建议等。
报告应清晰易懂,便于团队成员理解和使用。
2. 团队协作:
与开发、美术、策划等部门紧密合作,共同解决性能问题。
定期召开性能优化会议,分享进展和经验。
结语
游戏性能测试是确保游戏质量和用户体验的重要环节。通过科学的测试方法和有效的沟通协作,可以及时发现并解决性能问题,提升游戏的整体表现。
前言
随着手游3D类型游戏增多,对机器资源占用越发高,但用户手中硬件的提升是一个不可控且缓慢的过程,为了保证在尽量广泛的机型上流畅运行,提高游戏本身的潜在用户群体,所以手游客户端性能测试工作就越发重要,如何做客户端性能审核,如何快速,专业,准确的做客户端性能审核是我们需要重点关注、且持续建设的内容。
客户端性能测试
客户端性能审核主要针对不同主流机器(一般来自适配TOP10)根据不同配置划分高、中、低机型后,通过在各机型下模拟玩家核心体验场景、进行性能参数收集、评价该游戏的性能表现,并且进一步深入分析造成低性能表现原因的审核过程。
基础方案
性能基础参数
数据正确输出到前台显示的步骤,其经历CPU处理、内存管理、GPU处理,显存管理等阶段,若过程出现性能不佳,只有可能是中间的某一环节异常导致,而客户端性能审核(第一步)就需要定位具体是哪个环节异常。
android平台详细采集指标
基础指标:
FPS帧率:应用程序每秒钟显示的帧数
CPU占用率:应用程序占用的CPU资源情况
内存:应用程序存放到系统内存中占用情况,目前主要采集PSS
显存:应用程序存放到显卡存储区域的资源数据占用情况,目前主要采集VBO
GPU占用率:应用程序占用GPU资源情况
手游特有指标:
流量:单位时间内通过网络端口传输的数据总量
电量:单位时间内应用程序消耗的电荷数量
采集原理
FPS获取:HOOK到libEGL.so中的eglSwapBuffers函数,并获得两次调用的时间差,用1000除以时间差值,即为帧率。
Draw call获取:HOOK到opengl库中,统计glDrawElements,glDrawArrays函数执行次数,即每帧中上述函数的执行次数
Primitive获取:HOOK到opengl库中,统计glDrawElements,glDrawArrays函数中的顶点数,通过其绘制原理进行换算成图元,获取到primitive总数
VBO获取:HOOK到opengl库中,统计glBufferData、glBufferSubData函数参数的size大小,统计获取得到VBO大小
精准流量
支持UDP消息统计
原理:基于libpcap实现的一个网络数据流量统计模块。libpcap是unix/linux平台下的网络数据包捕获函数库,大多数网络监控软件都以它为基础。Linux下著名的tcpdump就是以它为基础的,通过其可以捕获到链路层的数据帧,通过包解析去头给予应用层的流量大小统计,同时工具可支持对比链路层统计流量大小和应用层流量大小,差异太大一般是由于数据量小的包太多从而判断应用本身是否存在需要合并包。
瓶颈识别
GPU相关异常信息,请关注是否绘制对象是否超量,或可尝试单帧分析定位瓶颈
CPU相关异常信息,请关注逻辑函数相关异常,可尝试函数耗时定位分析
VBO上传值超标,可优化相关使用场景,尝试定位当时场景单帧定位问题根因
内存瓶颈识别
场景内存PSS峰值上限评估,4核 2G机器<=320M 2 2核 1G机器<=210MB 单核 768M 机器<=155M
内存泄露评估,场景设计超2小时以上采集,内存无明显不释放表现趋势
流量瓶颈识别
目前识别方式10分钟消耗流量不超过500KB
定位方法
CPU瓶颈定位
函数级CPU占用率统计,执行耗时统计
函数级GC大小统计,执行耗时统计
判断准则
GC关注—任何一次性GC ALLOC大于3KB的选项
GC关注—每一帧都GC ALLOC大于40B的选项
CPU占用关注—CPU占用超过35ms以上的选项
CPU占用关注—CPU占用超过50ms以上,但占比小于11%的函数列表重点关注
CPU占用关注—CPU占用TOP选项
GPU瓶颈定位
定位参数
Draw call 耗时
Shader复杂度
绘制对象属性
常见判断准则
渲染关注—Draw call 耗时TOP
渲染关注—Draw call 次数不超过300,图元不超过4W
渲染关注—shader的复杂度Instructions 条目(主流的GPU每帧处理计算条目平均50~10)
渲染关注—shader中纹理采样次数建议不超过2
渲染关注—整体场景透明物件占比不超过30%
渲染关注—深度查看over draw具体情况
内存瓶颈定位
定位参数
资源的重复情况
资源的大小是否超阈值
资源是否存在泄漏
判断准则
内存阈值关注—ManagedHeap.UsedSize建议不超过20M
内存阈值关注—总体Mone建议不超过40M
内存阈值关注—Reserved Memory建议不超过150M
内存阈值关注—2D纹理建议不超过50M
内存阈值关注—Mesh大小建议不超过30M
内存阈值关注—AnimationClip大小建议不超过25M
内存阈值关注—AudioClip大小建议不超过15M
资源属性关注—Texture2D、AnimationClip、Mesh、AudioClip是否存在重复对象
资源属性关注—Texture2D是否存过大1024的对象
资源泄漏关注—SerializedFile等函数加载后是否有释放(u3d profiler不太方便定位)
依赖工具
Adreno profiler
U3D profiler
游戏性能测试底层钩子注入原理
游戏性能测试中的底层钩子注入是一种技术手段,用于在运行时拦截、修改或监控程序的执行流程。这种技术在游戏性能测试中特别有用,因为它允许测试人员在不修改源代码的情况下,获取游戏内部的详细性能数据。以下是底层钩子注入的基本原理和实现方法:
底层钩子注入的基本原理
动态链接库(DLL)注入:
原理:通过将自定义的DLL注入到目标进程的地址空间中,实现对目标进程的钩子注入。
实现步骤:
编写一个包含钩子函数的DLL。
使用Windows API(如CreateRemoteThread、LoadLibrary等)将DLL注入到目标进程中。
在DLL的入口点(DllMain)中设置钩子。
API钩子:
原理:通过替换或修改目标API函数的地址,使其指向自定义的钩子函数。
实现步骤:
使用SetWindowsHookEx或直接修改IAT(Import Address Table)来设置API钩子。
在钩子函数中执行所需的操作(如记录性能数据),然后调用原始API函数。
Inline Hooking:
原理:直接修改目标函数的前几个字节,使其跳转到自定义的钩子函数。
实现步骤:
使用汇编指令(如JMP)修改目标函数的起始部分。
在钩子函数中执行所需的操作,然后跳转回原始函数的剩余部分。
底层钩子注入的应用场景
性能监控:
通过钩子函数记录游戏的关键性能指标,如帧率、CPU/GPU使用率、内存使用等。
调试和测试:
在开发和测试阶段,通过钩子函数捕获和分析游戏内部的执行流程和状态。
反作弊和安全:
在游戏中使用钩子函数检测和防止作弊行为,确保游戏的安全性。
注意事项
合法性:
钩子注入技术必须在合法授权的范围内使用,不得用于未经授权的程序或侵犯他人知识产权。
稳定性:
钩子注入可能会引入额外的开销和潜在的稳定性问题,必须谨慎设计和测试。
兼容性:
钩子注入技术可能不适用于所有平台和操作系统,需要根据具体环境进行适配。
结语
底层钩子注入是一种强大的技术手段,能够在运行时动态地监控和修改程序的执行流程。在游戏性能测试中,通过合理使用钩子注入技术,可以获取详细的性能数据,帮助开发人员优化游戏性能。然而,使用钩子注入技术时必须注意合法性、稳定性和兼容性问题,确保其应用的安全和有效。
游戏性能测试底层钩子注入案例
游戏性能测试中的底层钩子注入是一种高级技术,通常用于在运行时捕获游戏的内部状态和行为,以便进行详细的性能分析。以下是一个简化的案例,展示了如何使用底层钩子注入来监控游戏的帧率。
案例背景
假设我们正在开发一款3D游戏,并且需要监控游戏在不同场景下的帧率。为了获取准确的帧率数据,我们决定使用底层钩子注入技术来拦截渲染循环。
实现步骤
1. 编写钩子函数
首先,我们需要编写一个钩子函数,该函数将在每次渲染循环结束时被调用,并记录当前的帧率。
#include <windows.h>
#include <iostream>
// 原始的渲染函数指针
typedef void (*RenderFunction)();
// 钩子函数
void HookedRenderFunction() {
static DWORD lastTime = GetTickCount();
DWORD currentTime = GetTickCount();
static int frameCount = 0;
// 计算帧率
frameCount++;
if (currentTime - lastTime >= 1000) {
std::cout << "FPS: " << frameCount << std::endl;
frameCount = 0;
lastTime = currentTime;
}
// 调用原始的渲染函数
RenderFunction originalRender = (RenderFunction)GetProcAddress(GetModuleHandle("GameDLL.dll"), "OriginalRenderFunction");
originalRender();
}
2. 注入DLL
接下来,我们需要编写一个DLL,该DLL将在游戏进程中加载,并设置钩子。
// DLL入口点
BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved) {
switch (ul_reason_for_call) {
case DLL_PROCESS_ATTACH:
// 设置钩子
SetHook();
break;
case DLL_PROCESS_DETACH:
// 清理钩子
UnhookWindowsHookEx(hookHandle);
break;
}
return TRUE;
}
// 设置钩子
void SetHook() {
HMODULE hModule = GetModuleHandle("GameDLL.dll");
RenderFunction originalRender = (RenderFunction)GetProcAddress(hModule, "OriginalRenderFunction");
// 修改原始渲染函数的地址
DWORD oldProtect;
VirtualProtect(originalRender, sizeof(void*), PAGE_EXECUTE_READWRITE, &oldProtect);
*(void**)originalRender = HookedRenderFunction;
VirtualProtect(originalRender, sizeof(void*), oldProtect, &oldProtect);
hookHandle = SetWindowsHookEx(WH_CBT, (HOOKPROC)HookedRenderFunction, hModule, 0);
}
// 清理钩子
HHOOK hookHandle = NULL;
3. 注入DLL到游戏进程
最后,我们需要将DLL注入到游戏进程中。可以使用以下代码来实现这一点:
#include <windows.h>
bool InjectDLL(DWORD processID, const char* dllPath) {
HANDLE hProcess = OpenProcess(PROCESS_ALL_ACCESS, FALSE, processID);
if (!hProcess) return false;
void* pRemoteMem = VirtualAllocEx(hProcess, NULL, strlen(dllPath) + 1, MEM_COMMIT, PAGE_READWRITE);
if (!pRemoteMem) {
CloseHandle(hProcess);
return false;
}
if (!WriteProcessMemory(hProcess, pRemoteMem, (void*)dllPath, strlen(dllPath) + 1, NULL)) {
VirtualFreeEx(hProcess, pRemoteMem, 0, MEM_RELEASE);
CloseHandle(hProcess);
return false;
}
HANDLE hThread = CreateRemoteThread(hProcess, NULL, 0, (LPTHREAD_START_ROUTINE)GetProcAddress(GetModuleHandle("kernel32.dll"), "LoadLibraryA"), pRemoteMem, 0, NULL);
if (!hThread) {
VirtualFreeEx(hProcess, pRemoteMem, 0, MEM_RELEASE);
CloseHandle(hProcess);
return false;
}
WaitForSingleObject(hThread, INFINITE);
CloseHandle(hThread);
VirtualFreeEx(hProcess, pRemoteMem, 0, MEM_RELEASE);
CloseHandle(hProcess);
return true;
}
感谢每一个认真阅读我文章的人,礼尚往来总是要有的,虽然不是什么很值钱的东西,如果你用得到的话可以直接拿走:
这些资料,对于【软件测试】的朋友来说应该是最全面最完整的备战仓库,这个仓库也陪伴上万个测试工程师们走过最艰难的路程,希望也能帮助到你!有需要的小伙伴可以点击下方小卡片领取