這篇部落格翻譯自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() >= && FChar::IsAlpha(InPath[]) && InPath[] == TEXT(':'));
return !IsRooted;
}
以上的代碼看起來是無害的,隻是一些很普通的字元串測試來判定InPath是相對路徑(eg. “../engine/myfile.uasset”) 還是絕對路徑 (eg. “c:\myfile.uasset”)。
為了研究這段代碼,我啟動Debugger,在函數中設定了斷點并且檢視其彙編代碼。此時發生了非常恐怖的事,以下隻是彙編代碼中的一小段:
F6DFF97B4E mov ecx,
F6DFF97B53 xor edi,edi
F6DFF97B55 xor edx,edx
F6DFF97B57 mov r8d,ecx
F6DFF97B5A mov qword ptr [rsp+h],rbx
F6DFF97B5F mov dword ptr [rbp+h],edi
F6DFF97B62 mov qword ptr [rbp-h],rdi
F6DFF97B66 mov qword ptr [rbp-],
F6DFF97B6E call DefaultCalculateSlack (F6DFEC7DD0h)
F6DFF97B73 movsxd rcx,eax
F6DFF97B76 mov rax,qword ptr [rbp-h]
F6DFF97B7A mov dword ptr [rbp-],ecx
F6DFF97B7D test rax,rax
F6DFF97B80 jne FPaths::IsRelative+h (F6DFF97B86h)
F6DFF97B82 test ecx,ecx
F6DFF97B84 je FPaths::IsRelative+Bh (F6DFF97B9Bh)
F6DFF97B86 mov rdx,rcx
F6DFF97B89 xor r8d,r8d
F6DFF97B8C mov rcx,rax
F6DFF97B8F add rdx,rdx
F6DFF97B92 call FMemory::Realloc (F6DFF04CB0h)
F6DFF97B97 mov qword ptr [rbp-h],rax
F6DFF97B9B lea rdx,[ToUpperAdjustmentTable+ABCh (F6E1EFFB9Ch)]
F6DFF97BA2 mov r8d,
F6DFF97BA8 mov rcx,rax
F6DFF97BAB call FGenericPlatformString::Memcpy (F6DFED3CA0h)
F6DFF97BB0 lea rdx,[rbp-h]
F6DFF97BB4 xor r8d,r8d
F6DFF97BB7 mov rcx,rsi
F6DFF97BBA mov ebx,
F6DFF97BBF call FString::StartsWith (F6DFEDD440h)
F6DFF97BC4 test al,al
F6DFF97BC6 jne FPaths::IsRelative+BBh (F6DFF97CFBh)
以上僅僅是整個函數的彙編代碼的冰山一角,而整個函數也隻有一行cpp代碼,這簡直是恐怖。
此外,這個代碼不僅僅隻是長而已,你應該可以看到有
FMemory::Realloc()
函數的調用。在整個彙編代碼中,
FMemory::Realloc()
函數調用了3次。與之對應的,
FMemory::Free()
函數也出現了多次。
還有,
StartsWith()
函數也不是一個很便宜的函數(注意
StartsWith()
隻有一個關于
FString
的Implementation)。
是以,我做了如下事:
- 減少了StartsWith()函數的調用,轉而使用更為直接的字元比較。
- 移除了runtime的TEXT()區塊,取而代之的是在外部直接建立。
- 将其中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[] == '/') ||
(PathLen >= && (
((InPath[] == '\\') && (InPath[] == '\\'))
|| (InPath[] == ':' && FChar::IsAlpha(InPath[]))
#if WITH_EDITOR
|| (InPath.StartsWith(RootPrefix))
#endif // WITH_EDITOR
))
);
return !IsRooted;
}
對于.h檔案,添加内容如下:
// Paths.h
private:
#if WITH_EDITOR
static FString RootPrefix;
#endif // WITH_EDITOR
重新編譯後,最終的彙編代碼如下:
F7147B695A mov edx,dword ptr [r8+]
F7147B695E mov rsi,rcx
F7147B6961 test edx,edx
F7147B6963 je FPaths::ConvertRelativePathToFull+h (F7147B6997h)
F7147B6965 dec edx
F7147B6967 je FPaths::ConvertRelativePathToFull+h (F7147B6997h)
F7147B6969 mov rax,qword ptr [r8]
F7147B696C movzx ecx,word ptr [rax]
F7147B696F cmp cx,h
F7147B6973 je FPaths::ConvertRelativePathToFull+A9h (F7147B69D9h)
F7147B6975 cmp edx,
F7147B6978 jb FPaths::ConvertRelativePathToFull+h (F7147B6997h)
F7147B697A cmp cx,Ch
F7147B697E jne FPaths::ConvertRelativePathToFull+h (F7147B6986h)
F7147B6980 cmp word ptr [rax+],cx
F7147B6984 je FPaths::ConvertRelativePathToFull+A9h (F7147B69D9h)
F7147B6986 cmp word ptr [rax+],Ah
F7147B698B jne FPaths::ConvertRelativePathToFull+h (F7147B6997h)
F7147B698D call qword ptr [__imp_iswalpha (F716557B48h)]
F7147B6993 test eax,eax
F7147B6995 jne FPaths::ConvertRelativePathToFull+A9h (F7147B69D9h)
以上的彙編代碼是在non-editor模式中的整個函數的彙編代碼,我相信你我都能同意以上代碼的性能要好得多。
優化這段代碼還帶來了一個很好的副作用:編譯器會将這段邏輯進行inline操作,我們甚至不需要聲明
FORCEINLINE
或者
INLINE
宏!
我的最終測試表明這段代碼的性能快了将近20倍,而代碼資源占用量隻是原來的10%。