![](https://i-blog.csdnimg.cn/blog_migrate/cbb10598223d1ae474acb6d6e757252c.png)
il2cpp
This is the fourth blog post in the IL2CPP Internals series. In this post, we will look at how il2cpp.exe generates C++ code for method calls in managed code. Specifically, we will investigate six different types of method calls:
这是IL2CPP Internals系列中的第四篇博客文章。 在本文中,我们将研究il2cpp.exe如何为托管代码中的方法调用生成C ++代码。 具体来说,我们将研究六种不同类型的方法调用:
In each case, we will focus on what the generated C++ code is doing and, specifically, on how much those instructions will cost.
在每种情况下,我们都将专注于生成的C ++代码的功能,尤其是这些指令的成本。
As with all of the posts in this series, we will be exploring code that is subject to change and, in fact, is likely to change in a newer version of Unity. However, the concepts should remain the same. Please take everything discussed in this series as implementation details. We like to expose and discuss details like this when it is possible though!
与本系列的所有文章一样,我们将探索可能会更改的代码,实际上,在新版本的Unity中,代码可能会更改。 但是,概念应保持不变。 请把本系列中讨论的所有内容作为实现细节。 我们希望尽可能地公开和讨论这样的细节!
Setup
建立
I’ll be using Unity version 5.0.1p4. I’ll run the editor on Windows, and build for the WebGL platform. I’m building with the “Development Player” option enabled, and the “Enable Exceptions” option set to a value of “Full”.
我将使用Unity版本5.0.1p4。 我将在Windows上运行该编辑器,并为WebGL平台构建。 我正在启用“ Development Player”选项的情况下进行构建,并且“ Enable Exceptions”选项设置为“ Full”。
I’ll build with a single script file, modified from the last post so that we can see the different types of method calls. The script starts with an interface and class definition:
我将使用一个脚本文件进行构建,该脚本文件是在上一篇文章中进行了修改的,以便我们可以看到不同类型的方法调用。 该脚本以接口和类定义开头:
1
2
3
4
5
6
7
8
9
|
interface Interface {
int MethodOnInterface(string question);
}
class Important : Interface {
public int Method(string question) { return 42; }
public int MethodOnInterface(string question) { return 42; }
public static int StaticMethod(string question) { return 42; }
}
|
1
2
3
4
5
6
7
8
9
|
interface Interface {
int MethodOnInterface ( string question ) ;
}
class Important : Interface {
public int Method ( string question ) { return 42 ; }
public int MethodOnInterface ( string question ) { return 42 ; }
public static int StaticMethod ( string question ) { return 42 ; }
}
|
Then we have a constant field and a delegate type, both used later in the code:
然后,我们有一个常量字段和一个委托类型,它们都将在后面的代码中使用:
1
2
3
|
private const string question = "What is the answer to the ultimate question of life, the universe, and everything?";
private delegate int ImportantMethodDelegate(string question);
|
1
2
3
|
private const string question = "What is the answer to the ultimate question of life, the universe, and everything?" ;
private delegate int ImportantMethodDelegate ( string question ) ;
|
Finally, these are the methods we are interested in exploring (plus the obligatory Start
method, which has no content here):
最后,以下是我们感兴趣的方法(以及强制性的Start
方法,此处无内容):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
private void CallDirectly() {
var important = ImportantFactory();
important.Method(question);
}
private void CallStaticMethodDirectly() {
Important.StaticMethod(question);
}
private void CallViaDelegate() {
var important = ImportantFactory();
ImportantMethodDelegate indirect = important.Method;
indirect(question);
}
private void CallViaRuntimeDelegate() {
var important = ImportantFactory();
var runtimeDelegate = Delegate.CreateDelegate(typeof (ImportantMethodDelegate), important, "Method");
runtimeDelegate.DynamicInvoke(question);
}
private void CallViaInterface() {
Interface importantViaInterface = new Important();
importantViaInterface.MethodOnInterface(question);
}
private void CallViaReflection() {
var important = ImportantFactory();
var methodInfo = typeof(Important).GetMethod("Method");
methodInfo.Invoke(important, new object[] {question});
}
private static Important ImportantFactory() {
var important = new Important();
return important;
}
void Start () {}
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
private void CallDirectly ( ) {
var important = ImportantFactory ( ) ;
important . Method ( question ) ;
}
private void CallStaticMethodDirectly ( ) {
Important . StaticMethod ( question ) ;
}
private void CallViaDelegate ( ) {
var important = ImportantFactory ( ) ;
ImportantMethodDelegate indirect = important . Method ;
indirect ( question ) ;
}
private void CallViaRuntimeDelegate ( ) {
var important = ImportantFactory ( ) ;
var runtimeDelegate = Delegate . CreateDelegate ( typeof ( ImportantMethodDelegate ) , important , "Method" ) ;
runtimeDelegate . DynamicInvoke ( question ) ;
}
private void CallViaInterface ( ) {
Interface importantViaInterface = new Important ( ) ;
importantViaInterface . MethodOnInterface ( question ) ;
}
private void CallViaReflection ( ) {
var important = ImportantFactory ( ) ;
var methodInfo = typeof ( Important ) . GetMethod ( "Method" ) ;
methodInfo . Invoke ( important , new object [ ] { question } ) ;
}
private static Important ImportantFactory ( ) {
var important = new Important ( ) ;
return important ;
}
void Start ( ) { }
|
With all that defined, let’s get started. Recall that the generated C++ code will be located in the Temp\StagingArea\Data\il2cppOutput directory in the project (as long as the editor remains open). And don’t forget to generate Ctags on the generated code, to help navigate it.
完成所有定义后,我们开始吧。 回想一下,生成的C ++代码将位于项目中的Temp \ StagingArea \ Data \ il2cppOutput目录中(只要编辑器保持打开状态)。 并且不要忘记在生成的代码上生成Ctags ,以帮助进行导航。
Calling a method directly
直接调用方法
The simplest (and fastest, as we will see) way to call a method, is to call it directly. Here is the generated code for the CallDirectly
method:
调用方法最简单(也是最快,我们将看到)的方法是直接调用它。 这是CallDirectly
方法的生成代码:
1
2
3
4
5
|
Important_t1 * L_0 = HelloWorld_ImportantFactory_m15(NULL /*static, unused*/, /*hidden argument*/&HelloWorld_ImportantFactory_m15_MethodInfo);
V_0 = L_0;
Important_t1 * L_1 = V_0;
NullCheck(L_1);
Important_Method_m1(L_1, (String_t*) &_stringLiteral1, /*hidden argument*/&Important_Method_m1_MethodInfo);
|
1
2
3
4
5
|
Important_t1 * L_0 = HelloWorld_ImportantFactory_m15 ( NULL /*static, unused*/ , /*hidden argument*/ &HelloWorld_ImportantFactory_m15_MethodInfo ) ;
V_0 = L_0 ;
Important_t1 * L_1 = V_0 ;
NullCheck ( L_1 ) ;
Important_Method_m1 ( L_1 , ( String_t * ) &_stringLiteral1 , /*hidden argument*/ &Important_Method_m1_MethodInfo ) ;
|
The last line is the actual method call. Note that it does nothing special, just calls a free function defined in the C++ code. Recall from the earlier post about generated code, that il2cpp.exe generates all methods as C++ free functions. The IL2CPP scripting backend does not use C++ member functions or virtual functions for any generated code. It follows then that calling a static method directory should be similar. Here is the generated code from the CallStaticMethodDirectly
method:
最后一行是实际的方法调用。 请注意,它没有什么特别的,只是调用C ++代码中定义的自由函数。 回想一下先前关于生成代码的文章 ,il2cpp.exe将所有方法生成为C ++自由函数。 IL2CPP脚本编写后端不对任何生成的代码使用C ++成员函数或虚函数。 随之而来的是,调用静态方法目录应该相似。 这是从CallStaticMethodDirectly
方法生成的代码:
1 |
Important_StaticMethod_m3(NULL /*static, unused*/, (String_t*) &_stringLiteral1, /*hidden argument*/&Important_StaticMethod_m3_MethodInfo);
|
1 |
Important_StaticMethod_m3 ( NULL /*static, unused*/ , ( String_t * ) &_stringLiteral1 , /*hidden argument*/ &Important_StaticMethod_m3_MethodInfo ) ;
|
We could say there is less overhead calling a static method, since we don’t need to create and initialize an object instance. However, the method call itself is exactly the same, a call to a C++ free function. The only difference here is that the first argument is always passed with a value of NULL
.
我们可以说调用静态方法的开销较小,因为我们不需要创建和初始化对象实例。 但是,方法调用本身完全相同,即对C ++自由函数的调用。 唯一的区别是,第一个参数始终以NULL
值传递。
Since the difference between calls to static and instance methods is so minimal, we’ll focus on instance methods only for the rest of this post, but the information applies to static methods as well.
由于对静态方法和实例方法的调用之间的差异非常小,因此在本文的其余部分中,我们将仅关注实例方法,但该信息也适用于静态方法。
Calling a method via a compile-time delegate
通过编译时委托调用方法
What happens with a slightly more exotic method call, like an indirect call via delegate? We’ll first look at a what I’ll call a compile-time delegate, meaning that we know at compile time which method will be called on which object instance. The code for this type of call is in the CallViaDelegate
method. It looks like this in the generated code:
稍微有点奇怪的方法调用(例如通过委托进行的间接调用)会发生什么? 我们首先来看一个所谓的编译时委托,这意味着我们在编译时知道将在哪个对象实例上调用哪个方法。 此类调用的代码在CallViaDelegate
方法中。 在生成的代码中看起来像这样:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// Get the object instance used to call the method.
Important_t1 * L_0 = HelloWorld_ImportantFactory_m15(NULL /*static, unused*/, /*hidden argument*/&HelloWorld_ImportantFactory_m15_MethodInfo);
V_0 = L_0;
Important_t1 * L_1 = V_0;
// Create the delegate.
IntPtr_t L_2 = { &Important_Method_m1_MethodInfo };
ImportantMethodDelegate_t4 * L_3 = (ImportantMethodDelegate_t4 *)il2cpp_codegen_object_new (InitializedTypeInfo(&ImportantMethodDelegate_t4_il2cpp_TypeInfo));
ImportantMethodDelegate__ctor_m4(L_3, L_1, L_2, /*hidden argument*/&ImportantMethodDelegate__ctor_m4_MethodInfo);
V_1 = L_3;
ImportantMethodDelegate_t4 * L_4 = V_1;
// Call the method
NullCheck(L_4);
VirtFuncInvoker1< int32_t, String_t* >::Invoke(&ImportantMethodDelegate_Invoke_m5_MethodInfo, L_4, (String_t*) &_stringLiteral1);
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
// Get the object instance used to call the method.
Important_t1 * L_0 = HelloWorld_ImportantFactory_m15 ( NULL /*static, unused*/ , /*hidden argument*/ &HelloWorld_ImportantFactory_m15_MethodInfo ) ;
V_0 = L_0 ;
Important_t1 * L_1 = V_0 ;
// Create the delegate.
IntPtr_t L_2 = { &Important_Method_m1_MethodInfo } ;
ImportantMethodDelegate_t4 * L_3 = ( ImportantMethodDelegate_t4 * ) il2cpp_codegen_object_new ( InitializedTypeInfo ( &ImportantMethodDelegate_t4_il2cpp_TypeInfo ) ) ;
ImportantMethodDelegate__ctor_m4 ( L_3 , L_1 , L_2 , /*hidden argument*/ &ImportantMethodDelegate__ctor_m4_MethodInfo ) ;
V_1 = L_3 ;
ImportantMethodDelegate_t4 * L_4 = V_1 ;
// Call the method
NullCheck ( L_4 ) ;
VirtFuncInvoker1 < int32_t , String_t * > :: Invoke ( &ImportantMethodDelegate_Invoke_m5_MethodInfo , L_4 , ( String_t * ) &_stringLiteral1 ) ;
|
I’ve added a few comments to indicate the different parts of the generated code.
我添加了一些注释以指示所生成代码的不同部分。
Note that the actual method called here is not part of the generated code. The method VirtFuncInvoker1<int32_t, String_t*>::Invoke
is located in the GeneratedVirtualInvokers.h file. This file is generated by il2cpp.exe, but it doesn’t come from any IL code. Instead, il2cpp.exe creates this file based on the usage of virtual functions that return a value (VirtFuncInvokerN
) and those that don’t (VirtActionInvokerN
), where N is the number of arguments to the method.
请注意,此处调用的实际方法不属于所生成代码的一部分。 方法VirtFuncInvoker1<int32_t, String_t*>::Invoke
位于GeneratedVirtualInvokers.h文件中。 该文件由il2cpp.exe生成,但是它不是来自任何IL代码。 相反,il2cpp.exe会根据虚拟函数的使用来创建此文件,这些虚拟函数返回一个值( VirtFuncInvoker N
)和那些不返回值的虚拟函数( VirtActionInvoker N
),其中N是该方法的参数数目。
The Invoke
method here looks like this:
这里的Invoke
方法如下所示:
1
2
3
4
5
6
7
8
9
10
11
|
template <typename R, typename T1>
struct VirtFuncInvoker1
{
typedef R (*Func)(void*, T1, MethodInfo*);
static inline R Invoke (MethodInfo* method, void* obj, T1 p1)
{
VirtualInvokeData data = il2cpp::vm::Runtime::GetVirtualInvokeData (method, obj);
return ((Func)data.methodInfo->method)(data.target, p1, data.methodInfo);
}
};
|
1
2
3
4
5
6
7
8
9
10
11
|
template < typename R , typename T1 >
struct VirtFuncInvoker1
{
typedef R ( * Func ) ( void * , T1 , MethodInfo * ) ;
static inline R Invoke ( MethodInfo * method , void * obj , T1 p1 )
{
VirtualInvokeData data = il2cpp :: vm :: Runtime :: GetVirtualInvokeData ( method , obj ) ;
return ( ( Func ) data . methodInfo -> method ) ( data . target , p1 , data . methodInfo ) ;
}
} ;
|
The call into libil2cpp GetVirtualInvokeData
looks up a virtual method in the vtable struct generated based on the managed code, then it makes a call to that method.
调用libil2cpp GetVirtualInvokeData
在基于托管代码生成的vtable结构中查找虚拟方法,然后调用该方法。
Why don’t we used C++11 variadic templates to implement these VirtFuncInvokerN
methods? This looks like a situation begging for variadic templates, and indeed it is. However, the C++ code generated by il2cpp.exe has to work with some C++ compilers which don’t yet support all C++ 11 features, including variadic templates. In this case at least, we did not think that forking the generated code for C++11 compilers was worth the additional complexity.
我们为什么不使用C ++ 11 可变参数模板来实现这些VirtFuncInvokerN
方法? 这看起来像是乞求可变参数模板的情况,确实如此。 但是,il2cpp.exe生成的C ++代码必须与某些尚不支持所有C ++ 11功能(包括可变参数模板)的C ++编译器一起使用。 至少在这种情况下,我们认为为C ++ 11编译器分叉生成的代码不值得增加额外的复杂性。
But why is this a virtual method call? Aren’t we calling an instance method in the C# code? Recall that we are calling the instance method via a C# delegate. Look again at the generated code above. The actual method we are going to call is passed in via the MethodInfo*
(method metadata) argument: ImportantMethodDelegate_Invoke_m5_MethodInfo
. If we search for the method named “ImportantMethodDelegate_Invoke_m5” in the generated code, we see that the call is actually to the managed Invoke
method on the ImportantMethodDelegate
type. This is a virtual method, so we need to make a virtual call. It is this ImportantMethodDelegate_Invoke_m5
function which will actually make the call to the method named Method
in the C# code.
但是,为什么这是一个虚拟方法调用? 我们不是在C#代码中调用实例方法吗? 回想一下,我们正在通过C#委托调用实例方法。 再次查看上面生成的代码。 我们要调用的实际方法是通过MethodInfo*
(方法元数据)参数传递的: ImportantMethodDelegate_Invoke_m5_MethodInfo
。 如果我们在生成的代码中搜索名为“ ImportantMethodDelegate_Invoke_m5”的方法,则会看到该调用实际上是对ImportantMethodDelegate
类型的托管Invoke
方法的。 这是一种虚拟方法,因此我们需要进行虚拟调用。 正是这个ImportantMethodDelegate_Invoke_m5
函数实际上将调用C#代码中名为Method
。
Wow, that was certainly a mouth-full. By making what looks like a simple change to the C# code, we’ve now gone from a single call to a C++ free function to multiple function calls, plus a table lookup. Calling a method via a delegate is significantly more costly than calling the same method directly.
哇,那肯定是满嘴的。 通过对C#代码进行简单的更改,我们现在已经从单个调用变为C ++自由函数,再到多个函数调用以及一个表查找。 通过委托调用方法比直接调用相同方法的成本高得多。
Note that in the process of looking at a delegate method call, we’ve also seen how a call via a virtual method works.
请注意,在查看委托方法调用的过程中,我们还看到了通过虚拟方法进行的调用的工作方式。
Calling a method via an interface
通过接口调用方法
It’s also possible to call a method in C# via an interface. This call is implemented by il2cpp.exe similar to a virtual method call:
也可以通过接口在C#中调用方法。 该调用由il2cpp.exe实现,类似于虚拟方法调用:
1
2
3
4
5
6
|
Important_t1 * L_0 = (Important_t1 *)il2cpp_codegen_object_new (InitializedTypeInfo(&Important_t1_il2cpp_TypeInfo));
Important__ctor_m0(L_0, /*hidden argument*/&Important__ctor_m0_MethodInfo);
V_0 = L_0;
Object_t * L_1 = V_0;
NullCheck(L_1);
InterfaceFuncInvoker1< int32_t, String_t* >::Invoke(&Interface_MethodOnInterface_m22_MethodInfo, L_1, (String_t*) &_stringLiteral1);
|
1
2
3
4
5
6
|
Important_t1 * L_0 = ( Important_t1 * ) il2cpp_codegen_object_new ( InitializedTypeInfo ( &Important_t1_il2cpp_TypeInfo ) ) ;
Important__ctor_m0 ( L_0 , /*hidden argument*/ &Important__ctor_m0_MethodInfo ) ;
V_0 = L_0 ;
Object_t * L_1 = V_0 ;
NullCheck ( L_1 ) ;
InterfaceFuncInvoker1 < int32_t , String_t * > :: Invoke ( &Interface_MethodOnInterface_m22_MethodInfo , L_1 , ( String_t * ) &_stringLiteral1 ) ;
|
Note the actual method call here is done via the InterfaceFuncInvoker1::Invoke
function, which is in the GeneratedInterfaceInvokers.h file. Like the VirtFuncInvoker1
class the InterfaceFuncInvoker1
class does a lookup in a vtable via the il2cpp::vm::Runtime::GetInterfaceInvokeData
function in libil2cpp.
请注意,此处的实际方法调用是通过InterfaceFuncInvoker1::Invoke
函数完成的,该函数位于GeneratedInterfaceInvokers.h文件中。 类似于Virt FuncInvoker1
类, Interface FuncInvoker1
类通过il2cpp::vm::Runtime::GetInterfaceInvokeData
函数在vtable中进行查找。
Why does an interface method call need to use a different API in libil2cpp from a virtual method call? Note that the call to InterfaceFuncInvoker1::Invoke
is passing not only the method to call and its arguments, but also the interface to call that method on (L_1
in this case). The vtable for each type is stored so that interface methods are written at a specific offset. Therefore, il2cpp.exe needs to provide the interface in order to determine which method to call.
为什么接口方法调用需要在libil2cpp中使用与虚拟方法调用不同的API? 请注意,对InterfaceFuncInvoker1::Invoke
不仅传递了要调用的方法及其参数,而且还传递了要调用该方法的接口(在本例中为L_1
)。 存储每种类型的vtable,以便以特定的偏移量写入接口方法。 因此,il2cpp.exe需要提供接口以确定要调用的方法。
The bottom line here is that calling a virtual method and calling a method via an interface have effectively the same overhead in IL2CPP.
最重要的是,在IL2CPP中,调用虚拟方法和通过接口调用方法实际上具有相同的开销。
Calling a method via a run-time delegate
通过运行时委托调用方法
Another way to use a delegate is to create it at runtime via the Delegate.CreateDelegate
method. This approach is similar to a compile-time delegate, except that it be modified at runtime in a few more ways. We pay for that flexibility with an additional function call. Here is the generated code:
使用委托的另一种方法是在运行时通过Delegate.CreateDelegate
方法创建Delegate.CreateDelegate
。 该方法类似于编译时委托,只是在运行时以其他几种方式对其进行了修改。 我们通过额外的函数调用来支付这种灵活性。 这是生成的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
// Get the object instance used to call the method.
Important_t1 * L_0 = HelloWorld_ImportantFactory_m15(NULL /*static, unused*/, /*hidden argument*/&HelloWorld_ImportantFactory_m15_MethodInfo);
V_0 = L_0;
// Create the delegate.
IL2CPP_RUNTIME_CLASS_INIT(InitializedTypeInfo(&Type_t_il2cpp_TypeInfo));
Type_t * L_1 = Type_GetTypeFromHandle_m19(NULL /*static, unused*/, LoadTypeToken(&ImportantMethodDelegate_t4_0_0_0), /*hidden argument*/&Type_GetTypeFromHandle_m19_MethodInfo);
Important_t1 * L_2 = V_0;
Delegate_t12 * L_3 = Delegate_CreateDelegate_m20(NULL /*static, unused*/, L_1, L_2, (String_t*) &_stringLiteral2, /*hidden argument*/&Delegate_CreateDelegate_m20_MethodInfo);
V_1 = L_3;
Delegate_t12 * L_4 = V_1;
// Call the method
ObjectU5BU5D_t9* L_5 = ((ObjectU5BU5D_t9*)SZArrayNew(ObjectU5BU5D_t9_il2cpp_TypeInfo_var, 1));
NullCheck(L_5);
IL2CPP_ARRAY_BOUNDS_CHECK(L_5, 0);
ArrayElementTypeCheck (L_5, (String_t*) &_stringLiteral1);
*((Object_t **)(Object_t **)SZArrayLdElema(L_5, 0)) = (Object_t *)(String_t*) &_stringLiteral1;
NullCheck(L_4);
Delegate_DynamicInvoke_m21(L_4, L_5, /*hidden argument*/&Delegate_DynamicInvoke_m21_MethodInfo);
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
// Get the object instance used to call the method.
Important_t1 * L_0 = HelloWorld_ImportantFactory_m15 ( NULL /*static, unused*/ , /*hidden argument*/ &HelloWorld_ImportantFactory_m15_MethodInfo ) ;
V_0 = L_0 ;
// Create the delegate.
IL2CPP_RUNTIME_CLASS_INIT ( InitializedTypeInfo ( &Type_t_il2cpp_TypeInfo ) ) ;
Type_t * L_1 = Type_GetTypeFromHandle_m19 ( NULL /*static, unused*/ , LoadTypeToken ( &ImportantMethodDelegate_t4_0_0_0 ) , /*hidden argument*/ &Type_GetTypeFromHandle_m19_MethodInfo ) ;
Important_t1 * L_2 = V_0 ;
Delegate_t12 * L_3 = Delegate_CreateDelegate_m20 ( NULL /*static, unused*/ , L_1 , L_2 , ( String_t * ) &_stringLiteral2 , /*hidden argument*/ &Delegate_CreateDelegate_m20_MethodInfo ) ;
V_1 = L_3 ;
Delegate_t12 * L_4 = V_1 ;
// Call the method
ObjectU5BU5D_t9 * L_5 = ( ( ObjectU5BU5D_t9 * ) SZArrayNew ( ObjectU5BU5D_t9_il2cpp_TypeInfo_var , 1 ) ) ;
NullCheck ( L_5 ) ;
IL2CPP_ARRAY_BOUNDS_CHECK ( L_5 , 0 ) ;
ArrayElementTypeCheck ( L_5 , ( String_t * ) &_stringLiteral1 ) ;
* ( ( Object_t * * ) ( Object_t * * ) SZArrayLdElema ( L_5 , 0 ) ) = ( Object_t * ) ( String_t * ) &_stringLiteral1 ;
NullCheck ( L_4 ) ;
Delegate_DynamicInvoke_m21 ( L_4 , L_5 , /*hidden argument*/ &Delegate_DynamicInvoke_m21_MethodInfo ) ;
|
This delegate requires a good bit of code for creation and initialization. But the method call itself has even more overhead, too. First we need to create an array to hold the method arguments, then call the DynamicInvoke
method on the Delegate
instance. If we follow that method in the generated code, we can see that it calls the VirtFuncInvoker1::Invoke
function, just as the compile-time delegate does. So this delegate requires one more function call then the compile-time delegate does, plus two lookups in a vtable, instead of just one.
该委托需要大量的代码来创建和初始化。 但是方法调用本身也有更多的开销。 首先,我们需要创建一个数组来保存方法参数,然后在Delegate
实例上调用DynamicInvoke
方法。 如果在生成的代码中遵循该方法,则可以看到它调用了VirtFuncInvoker1::Invoke
函数,就像编译时委托一样。 因此,此委托需要比编译时委托多的一个函数调用,以及在vtable中进行两次查找,而不只是一次。
Calling a method via reflection
通过反射调用方法
The most costly way to call a method is, not surprisingly, via reflection. Let’s look at the generated code for the CallViaReflection
method:
毫不奇怪,调用方法的最昂贵方法是通过反射。 让我们看看为CallViaReflection
方法生成的代码:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
// Get the object instance used to call the method.
Important_t1 * L_0 = HelloWorld_ImportantFactory_m15(NULL /*static, unused*/, /*hidden argument*/&HelloWorld_ImportantFactory_m15_MethodInfo);
V_0 = L_0;
// Get the method metadata from the type via reflection.
IL2CPP_RUNTIME_CLASS_INIT(InitializedTypeInfo(&Type_t_il2cpp_TypeInfo));
Type_t * L_1 = Type_GetTypeFromHandle_m19(NULL /*static, unused*/, LoadTypeToken(&Important_t1_0_0_0), /*hidden argument*/&Type_GetTypeFromHandle_m19_MethodInfo);
NullCheck(L_1);
MethodInfo_t * L_2 = (MethodInfo_t *)VirtFuncInvoker1< MethodInfo_t *, String_t* >::Invoke(&Type_GetMethod_m23_MethodInfo, L_1, (String_t*) &_stringLiteral2);
V_1 = L_2;
MethodInfo_t * L_3 = V_1;
// Call the method.
Important_t1 * L_4 = V_0;
ObjectU5BU5D_t9* L_5 = ((ObjectU5BU5D_t9*)SZArrayNew(ObjectU5BU5D_t9_il2cpp_TypeInfo_var, 1));
NullCheck(L_5);
IL2CPP_ARRAY_BOUNDS_CHECK(L_5, 0);
ArrayElementTypeCheck (L_5, (String_t*) &_stringLiteral1);
*((Object_t **)(Object_t **)SZArrayLdElema(L_5, 0)) = (Object_t *)(String_t*) &_stringLiteral1;
NullCheck(L_3);
VirtFuncInvoker2< Object_t *, Object_t *, ObjectU5BU5D_t9* >::Invoke(&MethodBase_Invoke_m24_MethodInfo, L_3, L_4, L_5);
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
|
// Get the object instance used to call the method.
Important_t1 * L_0 = HelloWorld_ImportantFactory_m15 ( NULL /*static, unused*/ , /*hidden argument*/ &HelloWorld_ImportantFactory_m15_MethodInfo ) ;
V_0 = L_0 ;
// Get the method metadata from the type via reflection.
IL2CPP_RUNTIME_CLASS_INIT ( InitializedTypeInfo ( &Type_t_il2cpp_TypeInfo ) ) ;
Type_t * L_1 = Type_GetTypeFromHandle_m19 ( NULL /*static, unused*/ , LoadTypeToken ( &Important_t1_0_0_0 ) , /*hidden argument*/ &Type_GetTypeFromHandle_m19_MethodInfo ) ;
NullCheck ( L_1 ) ;
MethodInfo_t * L_2 = ( MethodInfo_t * ) VirtFuncInvoker1 < MethodInfo_t * , String_t * > :: Invoke ( &Type_GetMethod_m23_MethodInfo , L_1 , ( String_t * ) &_stringLiteral2 ) ;
V_1 = L_2 ;
MethodInfo_t * L_3 = V_1 ;
// Call the method.
Important_t1 * L_4 = V_0 ;
ObjectU5BU5D_t9 * L_5 = ( ( ObjectU5BU5D_t9 * ) SZArrayNew ( ObjectU5BU5D_t9_il2cpp_TypeInfo_var , 1 ) ) ;
NullCheck ( L_5 ) ;
IL2CPP_ARRAY_BOUNDS_CHECK ( L_5 , 0 ) ;
ArrayElementTypeCheck ( L_5 , ( String_t * ) &_stringLiteral1 ) ;
* ( ( Object_t * * ) ( Object_t * * ) SZArrayLdElema ( L_5 , 0 ) ) = ( Object_t * ) ( String_t * ) &_stringLiteral1 ;
NullCheck ( L_3 ) ;
VirtFuncInvoker2 < Object_t * , Object_t * , ObjectU5BU5D_t9 * > :: Invoke ( &MethodBase_Invoke_m24_MethodInfo , L_3 , L_4 , L_5 ) ;
|
As in the case of the runtime delegate, we need to spend some time creating an array for the arguments to the method. Then we make a virtual method call to MethodBase::Invoke
(the MethodBase_Invoke_m24
function). This function in turn invokes another virtual function, before we finally get to the actual method call!
与运行时委托的情况一样,我们需要花一些时间为该方法的参数创建一个数组。 然后,我们对MethodBase::Invoke
( MethodBase_Invoke_m24
函数)进行虚拟方法调用。 在我们最终进入实际的方法调用之前,该函数依次调用了另一个虚拟函数!
Conclusion
结论
While this is no substitute for actual profiling and measurement, we can get some insight about the overhead of any given method invocation by looking at how the generated C++ code is used for different types of method calls. Specifically, it is clear that we want to avoid calls via run-time delegates and reflection, if at all possible. As always, the best advice about making performance improvements is to measure early and often with profiling tools.
尽管这不能替代实际的性能分析和测量,但通过查看生成的C ++代码如何用于不同类型的方法调用,我们可以对任何给定方法调用的开销有一些了解。 具体来说,很明显,我们希望尽可能避免通过运行时委托和反射进行调用。 与往常一样,有关提高性能的最佳建议是尽早进行测量,并且通常使用性能分析工具进行测量。
We’re always looking for ways to optimize the code generated by il2cpp.exe, so it is likely that these method calls will look different in a later version of Unity.
我们一直在寻找优化il2cpp.exe生成的代码的方法,因此这些方法调用在Unity的更高版本中看起来可能会有所不同。
Next time we’ll delve deeper in to method implementations and see how we share the implementation of generic methods to minimize generated code and executable size.
下次,我们将深入研究方法的实现,并了解如何共享通用方法的实现,以最大程度地减少生成的代码和可执行文件的大小。
翻译自: https://blogs.unity3d.com/2015/06/03/il2cpp-internals-method-calls/
il2cpp