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】パフォーマンス向上のためのスクリプトのベストプラクティス


Published on

講演者:フランシス・デュランシー(Unity Technologies)




Published in: Technology
  • Dating direct: ❶❶❶ ❶❶❶
    Are you sure you want to  Yes  No
    Your message goes here
  • Dating for everyone is here: ❤❤❤ ❤❤❤
    Are you sure you want to  Yes  No
    Your message goes here

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

  1. 1. C# Tips and Tricks
  2. 2. Francis Duranceau Lead Field Engineer, Unity
  3. 3. Plan • The C# compiler • .NET 4.6 • Marshalling • Understanding Boxing, Foreach, Stack vs. Heap allocation • GameManager Singleton pattern
  4. 4. C# compiler
  5. 5. C# compiler • The VM is an abstract stack machine • Then, we compile • Source file goes through • Tree builder that results into an • Abstract syntax tree that goes into the • Attribute evaluator that turns into an • Attributed tree that then feeds the • Tree walker that finally generates the • IL
  6. 6. C# compiler int SimpleCondition(int input) { if(input >= 0) return input * 2; else return -input; }
  7. 7. C# compiler <projectDir>/Temp/StagingArea/IL2Cpp/il2cppOutput/Bulk_Assembly-CSharp_*.cpp
  8. 8. .NET 4.6
  9. 9. .NET 4.6 Most notably : • Async • Tasks • DataContracts • Cryptography • Networking
  10. 10. .NET 4.6 -> C# 6.0 • Null-Conditional Operator • Auto-Property Initializers • Nameof Expressions • Primary Constructors • Expression Bodied Functions and Properties
  11. 11. .NET 4.6 -> C# 6.0
  12. 12. After .NET 4.6...
  13. 13. .NET 4.6 FAQ • What platforms does this affect? All of them, but in different ways: - Editor and Standalone use the new version of Mono when this option is enabled. - All console platforms will only be able to use IL2CPP when targeting the new .NET version. - iOS and WebGL will continue to be IL2CPP only. - Android will continue to support both Mono and IL2CPP. - Other platforms are still undergoing work to support either new Mono or IL2CPP • What about IL2CPP? IL2CPP fully supports the new .NET 4.6 APIs and features.
  14. 14. .NET 4.6 FAQ • What about the a new GC? The newer Mono garbage collector (SGen) requires additional work in Unity and will follow once we have stabilized the new runtime and class libraries. • Why are my builds larger with the new .NET version? The .NET 4.6 class libraries are quite larger than our current .NET 3.5 class libraries. We are actively working on improving the managed linker to reduce size further.
  15. 15. Marshalling
  16. 16. What is ‘Unmanaged’ Code? • As the name implies, unmanaged code means YOU are responsible for a host of things, that are usually taken care of for you when using managed code. For example: • Memory management • Thread Synchronization • Security • Life-time control of objects • Etc.
  17. 17. Using Unmanaged Code Access managed through DLL / Libraries [DllImport (“YourCode”)] followed by a function name, which needs to be static and extern Place a plugin directory alongside the ‘Assets’ folder in your project and place the library there If you get a “DllNotFoundException” make sure the path is correct and your library is present in the Plugins directory.
  18. 18. DLL in Unity Put your plugins in : • Assets/Plugins • Assets/Plugins/x86 • Assets/Plugins/x86_64 Once you have create / compiled and place your DLL / library in the right place, you can call it directly from your managed code.
  19. 19. Invoking Unmanaged Code • Generally speaking, you just call the method associated with the DLLImport tag. • Using GetProcAddress(), the specific function is looked up and executed. • Unfortunately, things aren’t that simple (surprise!) • C ABI is used for most calls. • Makes it near impossible to call functions that don’t adhere to this, like C++. • Again, ABI’s are platform specific. • Use either __stdcall and __cdecl for VC++ or __attribute__((stdcall)) and • __attribute__((cdecl)) for GCC. • If you need to invoke C++ code, use ‘extern “C”’ to ensure the calling convention is adhered to.
  20. 20. Exceptions • Runtime Exceptions can happen and unmanaged code needs to be able to deal with it. • Unfortunately, C doesn’t support exceptions • C++ does to some extent but has a different mechanism. • Don’t cross the streams and let exceptions propagate between managed and unmanaged code or you run the risk of things going bad
  21. 21. Marshalling • Marshalling is the process of ‘converting’ parameters from managed to unmanaged space. • For simple types, this just becomes a straight copy (byte, int, floats, etc) • Sometimes called ‘blitting’ • Bool, Strings, Arrays are a little more complex…. • Depends on what type of string it is: • Ascii, UTF-8, UTF-16, etc. • Memory boundaries need to be kept separate! • Don’t keep references around to managed data! • Data is usually released after call, with obvious consequences. • Possible to lock a memory area by using the C# ’fixed’ statement. • Generally, memory is copied around!
  22. 22. Marshalling (Cnt’d) • Strings, Classes and Structs • As mentioned earlier, strings are handled different than blittable data types. • CLR doesn’t just look for single function but function based on the type. I.e. Different functions for different objects passed in. • In order to facilitate this, you can ‘MarshallAs’ and describe your own datatype, as follows: [DllImport (”somedll")] private static extern void Foo ( [MarshalAs(UnmanagedType.LPStr)] string ansiString, [MarshalAs(UnmanagedType.LPWStr)] string unicodeString, [MarshalAs(UnmanagedType.LPTStr)] string platformString );
  23. 23. Marshalling (Cnt’d) • Structs and classes can’t be passed by value, only by reference. • Since it’s managed memory, anything that’s copied may move / change. • Generally a bad thing to do anyway! • Structs and classes differ by alignment. • Structs are sequentially aligned by default (as you’d expect) • Classes may or may not be laid out in memory the way you defined them! • If you must have them sequential, use the [StructLayout (LayoutKind.Sequential)] tag. • Return values should be copied back into managed memory.
  24. 24. Memory Management / Custom Marshalling • Memory managed owned by CLI will be managed by CLI • I.e. a reference to an object passed down to unmanaged code will be reclaimed by the CLI. • Both managed and unmanaged memory allocations come from the same pool. • Use Marshall.AllocCoTaskMem() and Marshall.FreeCoTaskMem() • Rather than having built-in Marshalling, you can use create custom ones. • Use ‘MarshalAs’ • Remember that you’ll need to write implementations for all variations of objects passed in!
  25. 25. Bottom Line • Don’t overlook the impact Marshalling has on your code. • You’re copying memory around! • Make sure you know who owns what. I.e. The CG doesn’t know what you own / use. • Unless you have a valid reason for using unmanaged code, stick to managed code.
  26. 26. Example of marshalling struct Boss { char* name; int health; }; int SumBossHealth(Boss* bosses, int size) { int sum = 0; for (int i = 0; i < size; ++i) { sum += bosses[i].health; } return sum; } bool IsBossDead(Boss b) { return == 0; }
  27. 27. Example of marshalling [DllImport("__Internal")] [return: MarshalAs(UnmanagedType.U1)] private extern static bool IsBossDead(Boss b); [DllImport("__Internal")] private extern static int SumBossHealth(Boss[] bosses, int size);
  28. 28. Example of marshalling Boss[] bosses = {new Boss("First Boss", 25), new Boss("Second Boss", 45)}; Debug.Log (string.Format ("Marshaling a non-blittable struct: {0}", IsBossDead (new Boss("Final Boss", 100)))); Debug.Log(string.Format("Marshaling an array by reference: {0}", SumBossHealth (bosses, bosses.Length)));
  29. 29. Best practices UnderstandingBoxing, Foreach,Stack vs. Heap allocation
  30. 30. Best practices - Boxing using UnityEngine; using System.Collections; public class InputAxis : MonoBehaviour { void Update () { float x = Input.GetAxis("Horizontal"); Debug.Log(x); }
  31. 31. Best practices extern "C" void InputAxis_Update_m397189571 (InputAxis_t277341211 * __this, const MethodInfo* method) { [...] float V_0 = 0.0f; { IL2CPP_RUNTIME_CLASS_INIT(Input_t1785128008_il2cpp_TypeInfo_var); float L_0 = Input_GetAxis_m2098048324(NULL /*static, unused*/, _stringLiteral855845486, /*hidden argument*/NULL); V_0 = L_0; float L_1 = V_0; float L_2 = L_1; Il2CppObject * L_3 = Box(Single_t2076509932_il2cpp_TypeInfo_var, &L_2); IL2CPP_RUNTIME_CLASS_INIT(Debug_t1368543263_il2cpp_TypeInfo_var); Debug_Log_m920475918(NULL /*static, unused*/, L_3, /*hidden argument*/NULL); return; } }
  32. 32. Best practices - Foreach int ForEachArray (int[] items) { var total = 0; foreach (var item in items) total += item; return total; }
  33. 33. Best practices - Foreach int ForEachArray(int[] items) { int num = 0; for (int i = 0; i < items.Length; i++) { int num2 = items[i]; num += num2; } return num; }
  34. 34. Best practices – Foreach on collections To avoid allocations when using foreach over a collection the following criteria need to be met: - For collections of value types, the collection and enumerator implement the generic interfaces IEnumerable<T> and IEnumerator<T>. - The enumerator implementation is a struct not a class. - The collection has a public method named GetEnumerator whose return type is the enumerator struct. The BCL types System.Collections.Generic.List<T>, System.Collections.Generic.Dictionary<T>, and System.Collections.Generic.HashSet<T> all follow the guidelines above and should be safe to perform foreach statements on.
  35. 35. Best practices – Foreach on collections int ForEachCustom (CustomCollection items) { var total = 0; foreach (int item in items) total += item; return total; } Specify the type explicitely -> public class Enumerator : IEnumerator<int>, not just an Enumerator on Systems.Objects (by default). Use a struct and not a class so your when you do new in GetEnumerator, it goes on the stack and not the heap -> return new Enumerator (this);
  36. 36. Best practices – Foreach on collections class CustomCollection : IEnumerable<int> { private readonly int[] _items; public CustomCollection(int[] items) { _items = items; } public Enumerator GetEnumerator () { return new Enumerator (this); } IEnumerator<int> IEnumerable<int>.GetEnumerator () { return GetEnumerator (); } IEnumerator IEnumerable.GetEnumerator () { return GetEnumerator (); } public struct Enumerator : IEnumerator<int> { private readonly CustomCollection _collection; private int _index; [...] } }
  37. 37. Best practices public class ClassVsStruct : MonoBehaviour { struct PointStruct { public int x; public int y; } class PointClass { public int x; public int y; } void Update() { PointStruct ps; ps.x = 3; ps.y = 4; PointStruct ps2 = new PointStruct(); ps2.x = 7; ps2.y = 8; PointClass pc = new PointClass(); pc.x = 5; pc.y = 6; gameObject.transform.position.Set( ps.x + pc.x, ps.y + pc.y, 0); } }
  38. 38. .method private hidebysig instance void Update () cil managed { .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 IL_002e: ldloc.2 IL_002f: ldc.i4.5
  39. 39. Best practices • Strings are immutable and will make copies • Use String.Builder • Use a hash to avoid Strings static readonly int material_Color = Shader.PropertyToID(“_Color”); static readonly int anim_Attack = Animator.StringToHash(“attack”); material.SetColor(material_Color, Color.white); animator.SetTrigger(anim_Attack);
  40. 40. The GC.Collect myth • There’s a myth saying that when you want to call GC.Collect, you should do it 6 times. • Another myth says it’s 7 times. • Is that really true?
  41. 41. The GC.Collect myth • On some platforms the GC can decommit memory from virtual memory • And that happens after so many collections without some pages being needed • But it can never be used by anything other than GC because it is still reserved … that address range is not available for any other allocator in the process • tldr; There isn’t much benefits. Unless you do something wrong and want to be nice to other applications running on that OS.
  42. 42. Best practices really does get { return new Vector3(0,0,0); }
  43. 43. GameManager pattern
  44. 44. GameManager pattern • In this use case, there is a very important lesson to learn. But to show you the lesson, I will do a very common mistake… • The Singleton is a very good candidate that can be called thousands of times per frame • Let’s look at one way to implement it.
  45. 45. GameManager pattern
  46. 46. GameManager pattern 0.34ms 0.35ms
  47. 47. GameManager pattern • static bool IsNativeObjectAlive(UnityEngine.Object o) • { • #if !UNITY_EDITOR return o.GetCachedPtr() != IntPtr.Zero; #else if (o.GetCachedPtr() != IntPtr.Zero) return true; if (o is MonoBehaviour || o is ScriptableObject) return false; return DoesObjectWithInstanceIDExist(o.GetInstanceID()); #endif }
  48. 48. GameManager pattern using UnityEngine.Profiling; public class SingletonCaller : MonoBehaviour { CustomSampler samplerFind; [...] void Start() { samplerFind = CustomSampler.Create("CallSingletonGameFind"); } [...] void Update() { samplerFind.Begin(); CallSingletonGameFind(); samplerFind.End(); } }
  49. 49. GameManager pattern
  50. 50. Never profile a game running in the editor.
  51. 51. ? operator string str = null; void Test1() { str?.ToUpper(); } void Test2() { if(str != null) str.ToUpper(); }
  52. 52. ? operator
  53. 53. What about… • MonoBehaviour vs class? • Sealed • Static • Unsafe • LINQ • Generic
  54. 54. Finally…
  55. 55. Conclusion • Some C# keywords are hiding overhead and allocations • Prefer POD and struct of classes • int, float, struct • NOT class, NOT strings • Check the resulting IL • But more importantly than anything else …
  56. 56. Only profile if the profiler tells you to do so
  57. 57. Thank you!