Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

【Unite 2017 Tokyo】パフォーマンス向上のためのスクリプトのベストプラクティス(note付き)

960 views

Published on

【Unite 2017 Tokyo】パフォーマンス向上のためのスクリプトのベストプラクティス(https://www.slideshare.net/UnityTechnologiesJapan/unite-2017-tokyo-75841792)のnote付きスライドです。

Published in: Technology
  • Be the first to comment

  • Be the first to like this

【Unite 2017 Tokyo】パフォーマンス向上のためのスクリプトのベストプラクティス(note付き)

  1. 1. 1
  2. 2. 2
  3. 3. 3
  4. 4. 4
  5. 5. The VM is an abstract stack machine. It means we take operands that reside on an evaluation stack. The instruction set consists of instructions that push operands on the abstract evaluation stack. All operands that may be placed on this stack are drawn from a small set of primitive types. Then the IL (our PEM, program executable module) is used by the JIT or is getting AOT compiled 5
  6. 6. ldarg.1 loads an argument (passed as a parameter) onto the stack Ldc.i4.0 Pushes a supplied value of type int32 onto the evaluation stack as an int32 Blt Transfers control to a target instruction if the first value is less than the second value 6
  7. 7. As we can see, we take the variable INPUT and send it to L_0 In the instruction set there only branching and jumping to do if and loops And you can find the IL2CPP cpp code in the directory at the bottom of the slide Or load the IL2CPP project with Xcode. 7
  8. 8. 8
  9. 9. Tasks and Async : can’t interact with most of the Unity API so … will throw exception, like today But, anyway we are introducing the Unity C# Job System that I’m talking about tomorrow. 9
  10. 10. 10
  11. 11. 11
  12. 12. We will support .NET Standard! 12
  13. 13. 13
  14. 14. And please, also REPORT BUGS 14
  15. 15. 15
  16. 16. What is unmanaged code? Unmanaged code is basically code written in C/C++ without the safety net that managed code provides. While managed code is generally great, there are times when you either need to interface directly with other modules / hardware for a specific feature which .Net doesn’t provide. For example, if you want to directly interface with a printer port or peripheral or interface with a package that’s written in C/C++ Note: I’m using ‘native’ and unmanaged code interchangably. 16
  17. 17. Accessing DLL’s from managed code may seem straight-forward and while it generally is, there are some pitfalls. For instance, figuring out where to put the DLL or library. For both Windows and OSX you 17
  18. 18. Unity made that really simple for you 20
  19. 19. Depending on the platform you’re developing for, you may have to make sure that functions adhere to the C ABI (Application Binary Interface). Depending on the platform and the compiler you’re using, you may have to change intrinsics to ensure everything is working correctly. Caveats primarily being exceptions, as discussed in the next slide. 21
  20. 20. Depending on the platform you’re developing for, you may have to make sure that functions adhere to the C ABI (Application Binary Interface). Depending on the platform and the compiler you’re using, you may have to change intrinsics to ensure everything is working correctly. 22
  21. 21. Marshalling is the process of converting parameters for managed code over into unmanaged code. While it’s relatively straightforward for simple types, things get more interesting when data needs to be converted or when memory needs to be allocated. For examples, strings fit this description in that there could be a number of different formats, ranging from ASCII to UTF8 to UFT16. Aside from the unicode conversion, there is also the issues of copying the data over. If you want to ensure memory sticks around when calling unmanaged code, use the c# ‘fixed’ statement. Otherwise, all bets are off Generally, memory is copied around to ensure nothing gets corrupted. For obvious reasons, this process can be slow. 23
  22. 22. Objects are marshalled differently based on their type. For example, a string version may have multiple functions that LOOK the same but actually have different entrypoints to handle different types of text. The reason for this is to be able to handle different variations that all share the same function but handle parameters differently. 24
  23. 23. Objects are marshalled differently based on their type. For example, a string version may have multiple functions that LOOK the same but actually have different entrypoints to handle different types of text. The reason for this is to be able to handle different variations that all share the same function but handle parameters differently. As a rule of thumb, in/out variables and / or structures should be copied back and forth to avoid causing problems. 25
  24. 24. One of the bigger pitfalls of mixing managed / unmanaged code is to make sure you keep track of who owns what. Not doing so may result in strange / unidentifiable behavior or crashes that are going to be hard to track. When you’re using allocations in Unmanaged code, use the same memory pool as managed code. Don’t mix and match. If you want, you can create custom Marshall functions. 26
  25. 25. Unless you have a valid reason to do native / unmanaged code, stick with managed code as it will make your life easier. 27
  26. 26. 28
  27. 27. 29
  28. 28. 30
  29. 29. 31
  30. 30. 32
  31. 31. 33
  32. 32. I’ve heard that foreach is allocating memory, is it true? Let’s have a look…. 34
  33. 33. The compiler is smart enough to transform foreach usage on Arrays to a simple for loop. The decompiled IL looks like the following C# The truth is that The "hidden" allocations that occur when using foreach normally occurs due to boxing (or constructing) the enumerator or the values themselves in the case of value types. So, as we can see, there should be no overhead for either performance or memory allocations when doing a foreach on an Array. 35
  34. 34. Nevertheless, as you do the implementation of your Enumerator, be careful that you don’t do boxing on your enumerator. [internal : https://q.unity3d.com/questions/1465/when-does-using-foreach-in-c-cause-an-allocation.html#] 36
  35. 35. 37
  36. 36. Your collection ends up like this 38
  37. 37. 39
  38. 38. .method private hidebysig instance void Update () cil managed { // Method begins at RVA 0x2164 // Code size 120 (0x78) .maxstack 4 .locals init ( [0] valuetype ClassVsStruct/PointStruct, [1] valuetype ClassVsStruct/PointStruct, [2] class ClassVsStruct/PointClass, [3] valuetype [UnityEngine]UnityEngine.Vector3 ) IL_0000: ldloca.s 0 IL_0002: ldc.i4.3 IL_0003: stfld int32 ClassVsStruct/PointStruct::x IL_0008: ldloca.s 0 IL_000a: ldc.i4.4 IL_000b: stfld int32 ClassVsStruct/PointStruct::y IL_0010: ldloca.s 1 IL_0012: initobj ClassVsStruct/PointStruct IL_0018: ldloca.s 1 IL_001a: ldc.i4.7 IL_001b: stfld int32 ClassVsStruct/PointStruct::x IL_0020: ldloca.s 1 IL_0022: ldc.i4.8 IL_0023: stfld int32 ClassVsStruct/PointStruct::y IL_0028: newobj instance void ClassVsStruct/PointClass::.ctor() IL_002d: stloc.2 40
  39. 39. IL_002e: ldloc.2 IL_002f: ldc.i4.5 IL_0030: stfld int32 ClassVsStruct/PointClass::x IL_0035: ldloc.2 IL_0036: ldc.i4.6 IL_0037: stfld int32 ClassVsStruct/PointClass::y IL_003c: ldarg.0 IL_003d: call instance class [UnityEngine]UnityEngine.GameObject [UnityEngine]UnityEngine.Component::get_gameObject() IL_0042: callvirt instance class [UnityEngine]UnityEngine.Transform [UnityEngine]UnityEngine.GameObject::get_transform() IL_0047: callvirt instance valuetype [UnityEngine]UnityEngine.Vector3 [UnityEngine]UnityEngine.Transform::get_position() IL_004c: stloc.3 IL_004d: ldloca.s 3 IL_004f: ldloca.s 0 IL_0051: ldfld int32 ClassVsStruct/PointStruct::x IL_0056: ldloc.2 IL_0057: ldfld int32 ClassVsStruct/PointClass::x IL_005c: add IL_005d: conv.r4 IL_005e: ldloca.s 0 IL_0060: ldfld int32 ClassVsStruct/PointStruct::y IL_0065: ldloc.2 IL_0066: ldfld int32 ClassVsStruct/PointClass::y IL_006b: add IL_006c: conv.r4 IL_006d: ldc.r4 0.0 IL_0072: call instance void [UnityEngine]UnityEngine.Vector3::Set(float32, float32, float32) IL_0077: ret } // end of method ClassVsStruct::Update 40
  40. 40. 41
  41. 41. 42
  42. 42. So many collections : magic number is per platform 43
  43. 43. Some of the trivial properties are BAD So in some cases, you may even want to cache them… or just set it by yourself 44
  44. 44. Why do I pick the Singleton? Because even if it’s a small example, often we see very simple class/functions be called thousands of time per cycle 45
  45. 45. 46
  46. 46. 47
  47. 47. First we check if the gameobject is null, that means a call to the op_Equalit that takes two UnityEngine.Objects as parameters Then, if it’s not null, we jump at the very end of the function to do ldsfld class SingleGameFind SingletonGameFind::m_instance ldsfld simply Push the value of a static field on the stack What’s on the stack is what we return 48
  48. 48. 49
  49. 49. No more comparison with the gameobject 50
  50. 50. 51
  51. 51. GetCachedPtr takes 0.34ms Op_Inequality takes 0.35ms Really? Nope, that is because what I look at is wrong 52
  52. 52. In the editor, we use a different UnityEngine.dll … so NEVER EVER PROFILE in the editor. 53
  53. 53. In the editor, we use a different UnityEngine.dll … so NEVER EVER PROFILE in the editor. 54
  54. 54. The true result is that, without doing the Callvirt, it’s still about 6 times faster… And I was wondering if using a property was changing anything, not only the IL told me no but the profiler too. No virtcall is ever inlined in C# 55
  55. 55. 56
  56. 56. 57
  57. 57. 58
  58. 58. MonoBehaviour vs class? Check at the blog post from Valentin https://blogs.unity3d.com/2015/12/23/1k-update-calls/ Sealed micro opt of no virt call with the animal / cow example with IL2CPP only Static No diff Unsafe If you use struct and native, no diff LINQ Creates a lot of overhead, avoid Generic Can create a lot of overhead, check IL 59
  59. 59. 60
  60. 60. 61
  61. 61. 62
  62. 62. 63

×