Unreal Engine 4 —— 使用反汇编来确定该进行优化的地方

12 篇文章 0 订阅

这篇博客翻译自Robert Troughton的博客Using the Disassembler to Highlight Optimization Targets,已征得原作者同意。

This post is translated from English. You can find the original English language version here: http://coconutlizard.co.uk/blog/ue4/using-the-disassembler/

UE4中有很多的字符串处理的函数,这些函数会在UE4中的各种情况下被调用 —— 例如无论在编辑器、cooking或者运行游戏时,都会有一大堆的字符串函数被调用。

在最近的测试中,我们着重测试了一下FPaths::IsRelative()函数,这个函数可以在Paths.cpp中被找到:

bool FPaths::IsRelative(const FString& InPath)
{
  const bool IsRooted = InPath.StartsWith(TEXT("\\"), ESearchCase::CaseSensitive)   ||
              InPath.StartsWith(TEXT("/"), ESearchCase::CaseSensitive)  ||
              InPath.StartsWith(TEXT("root:/")) |
              (InPath.Len() >= 2 && FChar::IsAlpha(InPath[0]) && InPath[1] == TEXT(':'));
  return !IsRooted;
}

以上的代码看起来是无害的,只是一些很普通的字符串测试来判定InPath是相对路径(eg. “../engine/myfile.uasset”) 还是绝对路径 (eg. “c:\myfile.uasset”)。

为了研究这段代码,我启动Debugger,在函数中设置了断点并且查看其汇编代码。此时发生了非常恐怖的事,以下只是汇编代码中的一小段:

00007FF6DFF97B4E  mov         ecx,2  
00007FF6DFF97B53  xor         edi,edi  
00007FF6DFF97B55  xor         edx,edx  
00007FF6DFF97B57  mov         r8d,ecx  
00007FF6DFF97B5A  mov         qword ptr [rsp+70h],rbx  
00007FF6DFF97B5F  mov         dword ptr [rbp+28h],edi  
00007FF6DFF97B62  mov         qword ptr [rbp-10h],rdi  
00007FF6DFF97B66  mov         qword ptr [rbp-8],2  
00007FF6DFF97B6E  call        DefaultCalculateSlack (07FF6DFEC7DD0h)  
00007FF6DFF97B73  movsxd      rcx,eax  
00007FF6DFF97B76  mov         rax,qword ptr [rbp-10h]  
00007FF6DFF97B7A  mov         dword ptr [rbp-4],ecx  
00007FF6DFF97B7D  test        rax,rax  
00007FF6DFF97B80  jne         FPaths::IsRelative+46h (07FF6DFF97B86h)  
00007FF6DFF97B82  test        ecx,ecx  
00007FF6DFF97B84  je          FPaths::IsRelative+5Bh (07FF6DFF97B9Bh)  
00007FF6DFF97B86  mov         rdx,rcx  
00007FF6DFF97B89  xor         r8d,r8d  
00007FF6DFF97B8C  mov         rcx,rax  
00007FF6DFF97B8F  add         rdx,rdx  
00007FF6DFF97B92  call        FMemory::Realloc (07FF6DFF04CB0h)  
00007FF6DFF97B97  mov         qword ptr [rbp-10h],rax  
00007FF6DFF97B9B  lea         rdx,[ToUpperAdjustmentTable+2ABCh (07FF6E1EFFB9Ch)]  
00007FF6DFF97BA2  mov         r8d,4  
00007FF6DFF97BA8  mov         rcx,rax  
00007FF6DFF97BAB  call        FGenericPlatformString::Memcpy (07FF6DFED3CA0h)  
00007FF6DFF97BB0  lea         rdx,[rbp-10h]  
00007FF6DFF97BB4  xor         r8d,r8d  
00007FF6DFF97BB7  mov         rcx,rsi  
00007FF6DFF97BBA  mov         ebx,1  
00007FF6DFF97BBF  call        FString::StartsWith (07FF6DFEDD440h)  
00007FF6DFF97BC4  test        al,al  
00007FF6DFF97BC6  jne         FPaths::IsRelative+1BBh (07FF6DFF97CFBh)

以上仅仅是整个函数的汇编代码的冰山一角,而整个函数也只有一行cpp代码,这简直是恐怖。

此外,这个代码不仅仅只是长而已,你应该可以看到有FMemory::Realloc()函数的调用。在整个汇编代码中,FMemory::Realloc()函数调用了3次。与之对应的,FMemory::Free()函数也出现了多次。

还有,StartsWith()函数也不是一个很便宜的函数(注意StartsWith()只有一个关于FString的Implementation)。

因此,我做了如下事:

  1. 减少了StartsWith()函数的调用,转而使用更为直接的字符比较。
  2. 移除了runtime的TEXT()区块,取而代之的是在外部直接创建。
  3. 将其中RootPrefix测试设为在编辑器中才有效。

因此,我最终的代码如下:

// Paths.cpp

    #if WITH_EDITOR
    FString FPaths::RootPrefix = TEXT("root:/");
    #endif // WITH_EDITOR
    bool FPaths::IsRelative(const FString& InPath)
    {
      const uint32 PathLen = InPath.Len();
      const bool IsRooted = PathLen &&
        ((InPath[0] == '/') ||
          (PathLen >= 2 && (
            ((InPath[0] == '\\') && (InPath[1] == '\\'))
            || (InPath[1] == ':' && FChar::IsAlpha(InPath[0]))
    #if WITH_EDITOR
            || (InPath.StartsWith(RootPrefix))
    #endif // WITH_EDITOR
          ))
        );
      return !IsRooted;
    }

对于.h文件,添加内容如下:

// Paths.h
    private:
    #if WITH_EDITOR
      static FString RootPrefix;
    #endif // WITH_EDITOR

重新编译后,最终的汇编代码如下:

    00007FF7147B695A  mov         edx,dword ptr [r8+8]  
    00007FF7147B695E  mov         rsi,rcx  
    00007FF7147B6961  test        edx,edx  
    00007FF7147B6963  je          FPaths::ConvertRelativePathToFull+67h (07FF7147B6997h)  
    00007FF7147B6965  dec         edx  
    00007FF7147B6967  je          FPaths::ConvertRelativePathToFull+67h (07FF7147B6997h)  
    00007FF7147B6969  mov         rax,qword ptr [r8]  
    00007FF7147B696C  movzx       ecx,word ptr [rax]  
    00007FF7147B696F  cmp         cx,2Fh  
    00007FF7147B6973  je          FPaths::ConvertRelativePathToFull+0A9h (07FF7147B69D9h)  
    00007FF7147B6975  cmp         edx,2  
    00007FF7147B6978  jb          FPaths::ConvertRelativePathToFull+67h (07FF7147B6997h)  
    00007FF7147B697A  cmp         cx,5Ch  
    00007FF7147B697E  jne         FPaths::ConvertRelativePathToFull+56h (07FF7147B6986h)  
    00007FF7147B6980  cmp         word ptr [rax+2],cx  
    00007FF7147B6984  je          FPaths::ConvertRelativePathToFull+0A9h (07FF7147B69D9h)  
    00007FF7147B6986  cmp         word ptr [rax+2],3Ah  
    00007FF7147B698B  jne         FPaths::ConvertRelativePathToFull+67h (07FF7147B6997h)  
    00007FF7147B698D  call        qword ptr [__imp_iswalpha (07FF716557B48h)]  
    00007FF7147B6993  test        eax,eax  
    00007FF7147B6995  jne         FPaths::ConvertRelativePathToFull+0A9h (07FF7147B69D9h)

以上的汇编代码是在non-editor模式中的整个函数的汇编代码,我相信你我都能同意以上代码的性能要好得多。

优化这段代码还带来了一个很好的副作用:编译器会将这段逻辑进行inline操作,我们甚至不需要声明FORCEINLINE或者INLINE宏!

我的最终测试表明这段代码的性能快了将近20倍,而代码资源占用量只是原来的10%。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值