使用手动和自动编译插桩对
This presentation is the property of its rightful owner.
Sponsored Links
1 / 67

使用手动和自动编译插桩对 CPU 运行时占用率突增进行检测 PowerPoint PPT Presentation


  • 72 Views
  • Uploaded on
  • Presentation posted in: General

使用手动和自动编译插桩对 CPU 运行时占用率突增进行检测. Adisak Pochanayon 首席软件工程师 Netherrealm 工作室 [email protected] 涵盖的主题. 本演讲是关于基于插桩的运行时间分析 。 代码插桩的方法 《 真人快打 》 ( MK )的 PET 分析程序( 占用率突增 检测器). 分析程序. 分析程序的 常见类型 硬件跟踪 基于事件的硬件 基于事件的软件 采样 插桩. 手动插桩. 显式插桩 / 代码标记 Wrapper 函数 Detours 代码库和“蹦床”( Trampolines )功能.

Download Presentation

使用手动和自动编译插桩对 CPU 运行时占用率突增进行检测

An Image/Link below is provided (as is) to download presentation

Download Policy: Content on the Website is provided to you AS IS for your information and personal use and may not be sold / licensed / shared on other websites without getting consent from its author.While downloading, if for some reason you are not able to download a presentation, the publisher may have deleted the file from their server.


- - - - - - - - - - - - - - - - - - - - - - - - - - E N D - - - - - - - - - - - - - - - - - - - - - - - - - -

Presentation Transcript


Cpu

使用手动和自动编译插桩对CPU运行时占用率突增进行检测

Adisak Pochanayon

首席软件工程师

Netherrealm工作室

[email protected]


Cpu

涵盖的主题

  • 本演讲是关于基于插桩的运行时间分析。

  • 代码插桩的方法

  • 《真人快打》(MK)的PET分析程序(占用率突增检测器)


Cpu

分析程序

  • 分析程序的常见类型

    • 硬件跟踪

    • 基于事件的硬件

    • 基于事件的软件

    • 采样

    • 插桩


Cpu

手动插桩

  • 显式插桩 / 代码标记

  • Wrapper函数

  • Detours代码库和“蹦床”(Trampolines)功能


Cpu

显式插桩

  • 要求代码标记(修改源代码)

    • StartMarker(INFO) / StopMarker(INFO)

  • 作用域 - ScopedMarker(INFO)

    class CScopedMarker {

    CScopedMarker(ProfDesc &info) { StartMarker(info); }

    ~CScopedMarker(ProfDesc &info) { StopMarker(info); }

    };

    #define ScopedMarker(INFO) CScopedMarker(INFO) \

    ProfInfo##__LINE__


Wrapper

Wrapper函数

  • 编译时间

    • #定义函数 (…) wrapper函数

      • 附加说明 – 与实现编译器无关

      • 缺点– 只有在你有源代码时起作用

  • 链接时间更换 / Wrapping

    • GCC选项:-Wl,--wrap,函数名

      __wrap_函数名 ()

      __real_函数名()


Wrapper1

Wrapper函数

调用函数

目标函数


Wrapper2

Wrapper函数

1

2

调用函数

“WRAPPER”函数

“REAL”函数

3

4


Wrapper3

Wrapper函数

  • 使用GCC / SNC进行wrapping malloc() 的示例

    • 添加链接器标志:-Wl,--wrap,malloc

      extern "C" void* __real_malloc(size_t);

      extern "C" void* __wrap_malloc(size_t Size)

      {

      // 调用原始malloc() 函数

      return __real_malloc(Size);

      }


Detours trampolines

Detours代码库和蹦床(Trampolines)

  • 这是一个为插桩修改代码的方法

    • 可以由分析程序在目标代码/二进制文件上进行

    • 调用库函数插桩的运行时间

      • 请参阅微软Detours代码库

      • MIPS示例代码(讲义)

      • 这是另一种形式的手动插桩,但此方式不要求对目标函数进行源代码标记。


Detours trampolines1

Detours代码库和蹦床(Trampolines)

调用函数

目标函数


Detours

Detours代码库

1

2

调用函数

DETOUR函数

目标函数

跳转

3


Trampolines

蹦床(Trampolines)功能

Trampoline

缓冲器

调用函数

目标函数

目标PROLOG语言

目标PROLOG语言

拷贝


Trampolines1

蹦床(Trampolines)功能

Trampoline

缓冲器

调动函数

目标函数

目标

PROLOG语言

目标

PROLOG语言

跳转


Trampolines2

蹦床(Trampolines)功能

Trampoline

缓冲器

调用函数

目标函数

目标PROLOG语言

目标PROLOG语言

跳转


Detours trampolines2

Detours代码库和蹦床(Trampolines)

1

Trampoline

缓冲器

2

3

调用函数

DETOUR函数

目标函数

跳转

目标

PROLOG语言

跳转

4

5

6


Detours trampolines3

Detours代码库和蹦床(Trampolines)

  • 小结(缺点)

    • 必须自行编写:基于精简指令集RISC很琐碎 / 基于复杂指令集CISC更加困难

    • 处理页面保护/ NX (不执行)

    • 商业使用需要付费

      • 微软Detours软件

        • http://research.microsoft.com/en-us/projects/detours/

      • 微软1999年关于Detours代码库和蹦床功能(Trampolines)的研究论文:

        • http://research.microsoft.com/pubs/68568/huntusenixnt99.pdf


Cpu

手动插桩

  • 手动插桩方法总结

    • 显示标记

    • Wrapper函数

    • Detours代码库和蹦床功能(Trampolines)

  • 所有方法都要求对函数进行识别和用户干预(代码标志、库函数调用或者链接器参数)。


Cpu

自动插桩

  • 你可能已经在使用自动插桩

    • 有许多分析器支持自动插桩

      • Metrowerks CATS、VTune Call Graph、Visual Studio Profiler & Visual C++ /callcap以及fastcap、GNU gprof (w/ gcc –pg)

  • 辅助编译器插桩(CAI)

    • 允许用户执行分析程序而编译器会为你进行标记

      • GCC: -finstrument-functions / SNC: -Xhooktrace

      • Visual C++: _penter() & _pexit() using /Gh and /GH


Cpu

自动插桩

函数体

PROLOG语言

当一个编译器为一个函数生成机器代码,除了函数体之外,它还生成一段prolog语言(保存寄存器、堆栈帧等等)以及epilog语言(恢复之前保存的寄存器和状态返回)。

EPILOG语言


Cpu

自动插桩

编译器自动插桩

函数体

PROLOG语言

Log Entry { _penter() __cyg_profile_func_enter () }

Log Exit { _pexit() __cyg_profile_func_exit () }

EPILOG语言


Gcc snc cai

GCC编译器 & SNC CAI自动编译器插桩

  • 编译器选项:函式追踪 -finstrument-functions

    • 一般而言插桩需要进入和退出函数。在函数进入之后以及函数退出之前,使用当前函数及它的调用地址调用下列分析函数。

      void __cyg_profile_func_enter (void *this_fn, void *call_site);

      void __cyg_profile_func_exit (void *this_fn,void *call_site);


Snc cai ps3 vita

SNC CAI自动编译器插桩 (PS3 / VITA)

void __cyg_profile_func_enter(void *this_fn, void *call_site)

{

如果(0==tls_PET_bIsInProcessing)

{

tls_PET_bIsInProcessing=true;

_internal_PET_LogEntry(0);

tls_PET_bIsInProcessing=false;

}

}


Visual c cai

Visual C++ CAI

  • 在Visual C++里使用_penter() & _pexit()要稍微困难一些。

    • Visual C++在函数入口和出口插入调用但并不保存任何寄存器。

    • 最起码需要根据应用程序二进制接口ABI平台编写汇编程序来保存寄存器

    • 需要额外的检查来确保“可用”


Visual c cai x86

Visual C++ CAI – X86

extern "C" void __declspec(naked) _cdecl

_penter( void )

{

_asm

{

push eax

push ebx

push ecx

push edx

push ebp

push edi

push esi

}

if(0==tls_PET_bIsInProcessing)

{

tls_PET_bIsInProcessing=true;

// Call C Work Function

_internal_PET_LogEntry(0);

tls_PET_bIsInProcessing=false;

}

_asm

{

pop esi

pop edi

pop ebp

pop edx

pop ecx

pop ebx

pop eax

ret

}

}


Visual c cai xbox 360

Visual C++ CAI – XBOX 360

  • 自动编译器插桩CAI支持XBOX 360平台(PowerPC)

  • 几乎和PC上一样

    • 保存寄存器

    • 新步骤 – 检查DPC(延迟过程调用)

    • TLS – 可重入检查

    • 调用实际工作函数

    • 恢复寄存器


Visual c cai xbox 3601

Visual C++ CAI – XBOX 360

  • PowerPC版本更加复杂

    • 需要保存和恢复更多的应用程序二进制接口ABI寄存器

      • 如果进行函数式程序设计FP,必须保存和恢复FP寄存器

    • 优化和早期退出

    • TLS访问必须在ASM中进行

      • 混合了naked函数的asm / C语言不像在 X86上那样运行良好

    • 后续内容请查看讲义…


Visual c cai xbox 3602

Visual C++ CAI – XBOX 360

void __declspec(naked) _cdecl __penter( void )

{

__asm

{

// Tiny Prolog

// - 设置链接寄存器(r12) & 返回地址(两个步骤)

stdr12,-20h(r1)// 在此处保存链接寄存器LR是额外步骤!

mflrr12

stwr12,-8h(r1)// 返回地址

blPET_prolog

bl_internal_PET_LogEntry

bPET_epilog

}

}


Xbox 360 cai penter

XBOX 360 CAI流程: _penter()

1

“C++”

插桩函数

_penter()

{asm}


Xbox 360 cai penter1

XBOX 360 CAI流程: _penter()

1

2

“C++”

插桩函数

_penter()

{asm}

PET_Prolog

{asm}

3


Xbox 360 cai penter2

XBOX 360 CAI流程: _penter()

4

1

2

3

“C++”

插桩函数

_penter()

{asm}

PET_Prolog

{asm}

PET_Prolog

早期退出 {asm}

3


Xbox 360 cai penter3

XBOX 360 CAI流程: _penter()

1

2

“C++”

插桩函数

_penter()

{asm}

PET_Prolog

{asm}

3

4

对进入Logging函数的“C++”分析例程

5


Xbox 360 cai penter4

XBOX 360 CAI流程: _penter()

1

2

“C++”

插桩函数

_penter()

{asm}

PET_Prolog

{asm}

3

4

对进入Logging函数的“C++”分析例程

5

6

PET_Epilog

{asm}

7


Xbox 360 cai penter5

XBOX 360 CAI流程: _penter()

4

1

2

3

“C++”

插桩函数

_penter()

{asm}

PET_Prolog

{asm}

PET_Prolog

早期退出{asm}

3

4

对进入Logging函数的“C++”分析例程

5

6

PET_Epilog

{asm}

7


Visual c cai xbox 3603

Visual C++ CAI – XBOX 360

  • PET_Prolog语言具有五个特点

    • 小型Prolog程序用来保存最小的寄存器

    • 检查DPC & 可能的早期退出

    • 检查递归 (TLS var) &可能的早期退出

    • 保存临时对象(包括r2)并返回上级

    • 早期退出一路返回至祖父类函数


Visual c cai xbox 3604

Visual C++ CAI – XBOX 360

  • 小型Prolog用来保存寄存器

    // 小型Prolog

    // - 保存寄存器(r11,r14)

    // - 设置堆栈帧(r1)

    stdr11,-30h(r1)

    stdr14,-28h(r1)

    // 原来的堆栈指针 (r1) 在此指令后处于0(r1)

    stwur1,-100h(r1)


Visual c cai xbox 3605

Visual C++ CAI – XBOX 360

  • 检查DPC & 可能的早期退出

    // 获取基于特定线程的TLS

    lwzr11,0(r13)

    // 不要试图在目的信令点编码DPC中运行!

    // 在DPC中 { 0(r13) == 0 }

    cmplwicr6,r11,0

    beqcr6,label__early_exit_prolog


Visual c cai xbox 3606

Visual C++ CAI – XBOX 360

  • 检查递归 (TLS var) &可能的早期退出

    laur14,_tls_start// 获取基于全局的TLS

    laur12,tls_PET_bIsInProcessing

    lalr14,r14,_tls_start

    lalr12,r12,tls_PET_bIsInProcessing

    subr11,r11,r14// TLS Base Offset (r11)

    addr14,r11,r12// r14 == &tls_PET_bIsInProcessing

    // 使用变量线程 tls_PET_bIsInProcessing避免递归

    lwzxr12,r11,r12

    cmplwicr6,r12,0

    bnecr6,label__early_exit_prolog

    lir12,1

    stwr12,0(r14)// 设置tls_PET_bIsInProcessing


Visual c cai xbox 3607

Visual C++ CAI – XBOX 360

  • 检查递归 (TLS var) &可能的早期退出

    这看起来很复杂实际上非常简单。前一张幻灯片的内容等价于下列C++内容:

    如果(tls_PET_bIsInProcessing)

    gotolabel__early_exit_prolog;

    tls_PET_bIsInProcessing=true;


Visual c cai xbox 3608

Visual C++ CAI – XBOX 360

  • 早期退出一路返回至祖父类函数

    // 保存 r0/r2-r10 (临时对象)

    stdr0,8h(r1)

    stdr2,10h(r1) // (r2保存在 XBOX 360上)

    stdr3,18h(r1)

    stdr4,20h(r1)

    stdr5,28h(r1)

    stdr6,30h(r1)

    stdr7,38h(r1)

    stdr8,40h(r1)

    stdr9,48h(r1)

    stdr10,50h(r1)

    blr// 返回至调用者


Visual c cai xbox 3609

Visual C++ CAI – XBOX 360

  • 早期退出一路返回至祖父类函数

    label__early_exit_prolog:

    // 小型Epilog – 调整堆栈 (r1) & 恢复 r12/r14/r11

    addir1,r1,100h

    lwzr12,-8h(r1)

    mtlrr12

    ldr12,-20h(r1)

    ldr14,-28h(r1)

    ldr11,-30h(r1)

    blr


Visual c cai xbox 36010

Visual C++ CAI – XBOX 360

  • PET_Epilog更加简单(请参阅讲义)

    • 清除TLS递归预防变量

    • 恢复临时对象

    • 恢复用于小型Prolog中的寄存器

  • 最后请注意:Worker函数如果执行任何浮点工作必须保存/恢复FP寄存器(fr0-fr13)。


Xbox 360 cai pexit

XBOX 360 CAI流程: _pexit()

4

2

3

“C++”

插桩函数

_pexit()

{asm}

PET_Prolog

{asm}

PET_Prolog

早期退出{asm}

3

4

对进入Logging函数的“C++”分析例程

5

1

6

PET_Epilog

{asm}

7


Cpu

如何使用插桩

  • 那么现在我们有了所有这些方法来进行代码插桩,我们该如何使用这些技术?

  • 将其挂接到你的分析代码中

  • 在真人快打《Mortal Kombat》团队中,插桩运用的例子之一就是用于内部开发来检测运行时占用率突增的探测器,我们将之称为PET分析程序。


Cpu

PET分析程序

  • PET = Prolog Epilog Timing

    • 计算进入和退出函数的次数和检测峰值

    • 能够设置一个全局阈值,任何超过该阈值的插桩函数都会受到记录

      • 使用堆栈以及来自标识的潜在额外信息

    • 运用自动编译器插桩进行工作

      • 对游戏中每一个编译函数进行峰值检测

      • 使用自动编译插桩CAI的费用大约为性能开销的15-30%


Cpu

PET分析程序

  • 没有代码标记需要检测峰值

    • 打开CAI,它会自动找到执行时间超出你所设定的全局阈值的任何函数

  • PET标签 / 代码标记仍然是有用的

    • 简单的 PET_FUNCTION() (作用域标记)

    • 为PET分析程序提供额外的信息

    • 允许在没有自动编译器插桩CAI的情况下在各平台上进行峰值检测

    • 备用执行方案= 增强其他分析程序


Cpu

PET的实现

  • PET是通过使用TLS信息栈来实现

    • 栈由CAI进入递增

    • 栈由CAI退出递减

    • 当CAI不存在时,PET栈的递增和递减由标记决定(作用域标记)

    • 作用域标记和应用程序编程接口API标记在全局级别、函数级别、子系统级别或者线程级别提供了额外的信息和功能


Cpu

PET的实现

  • 栈区数据

    • 可以小至4个字,这取决于所选的定义#define选项

      • 进入时间

      • 可选阈值(覆盖全局阈值)

      • 针对子类对象的可选阈值

      • 描述(其中大部分是可选的)

        • 函数地址

        • 函数名称(指针为静态)

        • 线数

        • 源文件名称(指针为静态)

        • 用户生成描述(动态字符串)


Pet api

PET应用程序编程接口API / 基本标记

PET_FUNCTION()

PET_SECTION_MANUAL(name)

PET_SetGlobalThresholdMSec(msecs)

PET_SetFunctionThresholdMSec(msecs)

PET_SetChildrenThresholdMSec(msecs)

PET_SetAllParentsThresholdMSec(msecs)


Pet api1

PET应用程序编程接口API / 阈值

为什么阈值是有效的?让我们假定你设置了一个全局阈值为1毫秒用来找到任何运行1毫秒以上的函数。对于一个60帧下运行的游戏,你的Game::Tick()函数将需要16.66毫秒的时间,这样可以避免触发峰值警告,你可以把下面这个标记插入到Game::Tick()中:

PET_FUNCTION();

PET_SetFunctionThresholdMSec(1000.0/MIN_FPS);

那么PET分析程序将不会在你的Game::Tick()里记录一次峰值。


Cpu

PET阈值示例

脚本函数 (0.1毫秒)

大部分游戏函数的全局阈值 (1毫秒)

Tick函数 (1 帧 = 16 毫秒)

文件输入输出I/O 函数(2 秒 – 想象这个范围大概是一个足球场长度)


Pet api2

PET应用程序编程接口API / 信息

PET_设定函数描述PET_SetFunctionDescf(FMT,...)

  • 从字符串池中分配一个临时(共享)字符串并根据描述对其进行格式化。

  • 如果发生占用率突增,在log日志文件中发出说明。

    PET_超时(OT)信息 PET_OTMessagef(FMT,...)

  • 如果该函数超过阈值时间(OT)则记录一条附加信息。

  • 系统开销比设定函数描述 f(FMT,...) 更少,这是由于输出字符串只在信息需要时生成。


Pet api3

PET应用程序编程接口API / 信息

  • 示例:

    void ExecuteScript(ScriptContext *sc)

    {

    PET_FUNCTION();

    sc->ExecuteStep();

    PET_OTMessagef("Script function: %s",

    sc->GetCurrentFunctionName());

    }


Pet api4

PET应用程序编程接口API / 信息

  • 示例:

    void ExecuteScript(ScriptContext *sc)

    {

    PET_FUNCTION();

    PET_SetFunctionThresholdMSec(0.1);

    PET_SetFunctionDescf("Script function: %s",

    sc->GetCurrentFunctionName());

    sc->ExecuteStep();

    }


Pet api5

PET应用程序编程接口API / 条件控制

如果分析处于活动状态时进行控制

PET_SetThreadActive()

PET_FUNCTION_IGNORE()

PET_FUNCTION_IGNORE_CHILDREN()

PET_Pause()

PET_Unpause()

条件分析(可以允许“通道(channels)”)

PET_FUNCTION_CONDITIONAL(cond)

PET_FUNCTION_CONDITIONAL_PAUSED(cond)

PET_FUNCTION_CONDITIONAL_IGNORE_CHILDREN(cond)

PET_FUNCTION_CONDITIONAL_IGNORE(cond)


Cpu

PET日志输出示例

PET: Function took too long: 11.302 MSec

    Thread: MainThread

    PEAK CHILD @ frame 2323

  Function Name Trace

     0) 0x82449eb4 - main

     1) 0x824499c8 - GuardedMain

     2) 0x82444c80 - FEngineLoop::Tick

     3) 0x82440ddc - UMK9GameEngine::Tick

     4) 0x82d6d39c - MKScriptVM::Tick

     5) 0x82d6f2dc - MKListNoDestroy::ForEach

     6) 0x82d6cd54 - MKScriptVM::Step

     7) 0x82d690ac - _call_c_function

     8) 0x82440ddc - CreateEnduranceOpponentPhase1

     9) 0x82440ddc - CreatePlayerPhase1

    10) 0x82440ddc - SpawnMKFGCharacterObj

    11) 0x82440ddc – CreateMeshes

PET: Thread (MainThread) Function took too long: 14.499 MSec

    10) 0x82440ddc - SpawnMKFGCharacterObj

PET: Thread (MainThread) Function took too long: 14.599 MSec

     9) 0x82440ddc - CreatePlayerPhase1

PET: Thread (MainThread) Function took too long: 15.097 MSec

     8) 0x82440ddc - CreateEnduranceOpponentPhase1

!!!!!-----------------------------------------------------------!!!!!

Slow Script->C Call: _CreateEnduranceOpponentPhase1

takes 16.576 ms (0.995 frames) to execute

!!!!!-----------------------------------------------------------!!!!!

PET: Thread (MainThread) Function took too long: 15.377 MSec

     7) 0x82d690ac - _call_c_function

PET: Thread (MainThread) Function took too long: 15.401 MSec

     6) 0x82d6cd54 - MKScriptVM::Step

PET: Thread (MainThread) Function took too long: 15.490 MSec

     5) 0x82d6f2dc - MKListNoDestroy::ForEach

PET: Thread (MainThread) Function took too long: 15.578 MSec

     4) 0x82d6d39c - MKScriptVM::Tick


Cpu

PET 日志输出示例

PET: Function took too long: 8.586 MSec

Thread: MainThread

PEAK CHILD @ frame 2422

Function Name Trace

0) Tick (LaunchEngineLoop.cpp - LINE: 1981)

1) Tick (MK9Game.cpp - LINE: 499)

2) Tick (ScriptCore.cpp - LINE: 908)

3) Step (ScriptCore.cpp - LINE: 440)

4) _call_c_function (ScriptCore.cpp - LINE: 4194)

5) CreateEnduranceOpponentPhase1 (FGPlayer.cpp - LINE: 881)

6) CreatePlayerPhase1 (FGPlayer.cpp - LINE: 527)

7) SpawnMKFGCharacterObj (FGPlayer.cpp - LINE: 243)

8) CreateMeshes (FGPlayer.cpp - LINE: 1088)

9) SetSkeletalMesh (UnSkeletalComponent.cpp - LINE: 3465)

PET: Thread (MainThread) Function took too long: 12.851 MSec

8) CreateMeshes (FGPlayer.cpp - LINE: 1088)

PET: Thread (MainThread) Function took too long: 15.429 MSec

7) SpawnMKFGCharacterObj (FGPlayer.cpp - LINE: 243)

PET: Thread (MainThread) Function took too long: 15.522 MSec

6) CreatePlayerPhase1 (FGPlayer.cpp - LINE: 527)

PET: Thread (MainThread) Function took too long: 15.869 MSec

5) CreateEnduranceOpponentPhase1 (FGPlayer.cpp - LINE: 881)

!!!!!-----------------------------------------------------------!!!!!

Slow Script->C Call: _CreateEnduranceOpponentPhase1

takes 16.446 ms (0.987 frames) to execute

!!!!!-----------------------------------------------------------!!!!!

PET: Thread (MainThread) Function took too long: 16.015 MSec

4) _call_c_function (ScriptCore.cpp - LINE: 4194)

PET: Thread (MainThread) Function took too long: 16.035 MSec

3) Step (ScriptCore.cpp - LINE: 440)

PET: Thread (MainThread) Function took too long: 16.200 MSec

2) Tick (ScriptCore.cpp - LINE: 908)


Cpu

PET分析程序注意事项

  • 应用程序编译接口API为可选项(CAI运行无需API)

  • 在各发布的版本中API编译体现为nop指令

    • 在不使用时,运行时代码开销为零

  • 手动插桩 / 备用实现方法开销极低

    • 开销取决于插桩数量但通常几乎无法察觉(相当于CAI开销的15-30%)


Cpu

PET分析程序注意事项

  • 标记对PET来说不是必要的但能够给你更精确的粒子控制… 特别是在一些特定的系统中。

  • 典型的游戏标记涉及为CAI添加的总数仅为30到50的标记线路来取得最好的结果。

    • PET分析程序将检测峰值并为你“建议”在CAI模式下进行标记的线路

  • 在手动模式中更多的标记会提供更好的详细信息


Cpu

实际的范例

  • 《真人快打9》(MK9)里的代码优化过程

    • 只需在游戏和日志文件中打开

      • 创建spiking函数名单

      • 范围缩小至“真(real)”和“假(false)”峰值

      • 使用PET_Pause、PET_Ignore或者适当的阈值对少数的“假”峰值进行插桩

      • 获取“真”峰值清单并将之分发给不同的程序员进行优化


Cpu

实际的范例

  • 有时候运行会发现不同寻常的和意想不到的峰值。

    • 同游戏内稳定的60帧状态相比加载例程不再同之前一样需要沉重的配置(《真人快打》由于加载屏幕或解析电影,基本加载无法达到60帧)

    • 例子:由于糟糕的代码选取Ladder模式人物可能要花费将近1秒的时间!!!


Cpu

实际的范例

  • 追踪具体问题

    • 《蝙蝠侠:阿甘之城》(Batman ArkhamCity)采用了流媒体加载方式。我们曾在几处地方寻找加载位置。我的一名同事曾花费了两天的时间寻找位置都没有成功。我去到他的办公室,打开PET分析程序并在第一次尝试的几分钟内就找到了具体的代码位置。


Cpu

实际的范例

  • …追踪具体问题

    • 在VITA平台上格斗动作引起的“bog”问题

      • 首先尝试 手动插桩

        • 获得了缓慢但有效的结果,已在RAZOR上得到验证

        • 分而治之 / 对Slow函数的子类函数进行插桩

      • 接着自动插桩

        • 打开CAI模式/ 执行slow move

        • 一组函数被记录到日志文件中(如果想要可以进行插桩)

        • 在定位问题上快速许多(自动)


Cpu

实际的范例

  • 针对脚本调用(*移动到原生代码?)

    • 虚幻引擎脚本(Unreal Script)、MKScript、 Kismet等等

    • 对脚本执行(script execute)函数插桩

      • 使用PET_OTMessagef() 或者PET_SetFunctionDescf() 以取得额外信息

    • 为什么这比计时脚本调用更好?

      • 自动插桩模式将锁定子类函数内地址并给出脚本上下文信息


Cpu

有什么问题吗 ???

联系方式:

Adisak Pochanayon

首席软件工程师

Netherrealm工作室

[email protected]


Cpu

《真人快打》的“自动”分析

  • 代码插桩

    • 手动

    • 自动*

  • 创建一个分

    析程序

  • 《真人快打》

    @ 60帧

    • 检测峰值


  • Login