From 27f4b8f10c7d68c4318056236ab0e884a9fd5ae2 Mon Sep 17 00:00:00 2001 From: RamType0 Date: Mon, 31 Aug 2020 15:34:57 +0900 Subject: [PATCH] Add benchmarks for Unity using Performance testing API 2.3.1-preview https://docs.unity3d.com/Packages/com.unity.test-framework.performance@2.3/manual/index.html --- src/UniTask/Assets/Tests/Benchmarks.meta | 8 + .../Assets/Tests/Benchmarks/QueueCheck.cs | 505 ++++++++++++++++++ .../Tests/Benchmarks/QueueCheck.cs.meta | 11 + .../UniTask.Tests.Benchmarks.asmdef | 23 + .../UniTask.Tests.Benchmarks.asmdef.meta | 7 + 5 files changed, 554 insertions(+) create mode 100644 src/UniTask/Assets/Tests/Benchmarks.meta create mode 100644 src/UniTask/Assets/Tests/Benchmarks/QueueCheck.cs create mode 100644 src/UniTask/Assets/Tests/Benchmarks/QueueCheck.cs.meta create mode 100644 src/UniTask/Assets/Tests/Benchmarks/UniTask.Tests.Benchmarks.asmdef create mode 100644 src/UniTask/Assets/Tests/Benchmarks/UniTask.Tests.Benchmarks.asmdef.meta diff --git a/src/UniTask/Assets/Tests/Benchmarks.meta b/src/UniTask/Assets/Tests/Benchmarks.meta new file mode 100644 index 0000000..62ed076 --- /dev/null +++ b/src/UniTask/Assets/Tests/Benchmarks.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b36298f734c18f94ba41c1bbf3603297 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/UniTask/Assets/Tests/Benchmarks/QueueCheck.cs b/src/UniTask/Assets/Tests/Benchmarks/QueueCheck.cs new file mode 100644 index 0000000..5f292df --- /dev/null +++ b/src/UniTask/Assets/Tests/Benchmarks/QueueCheck.cs @@ -0,0 +1,505 @@ +using NUnit.Framework; +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; +using System.Threading; +using Unity.PerformanceTesting; + +public class QueueCheck +{ + Node node1 = new Node(); + Node node2 = new Node(); + RefNode refNode1 = new RefNode(); + RefNode refNode2 = new RefNode(); + Queue q1 = new Queue(); + Stack s1 = new Stack(); + ConcurrentQueue cq = new ConcurrentQueue(); + ConcurrentStack cs = new ConcurrentStack(); + static TaskPool pool; + static TaskPoolRefNode poolRefNode; + static TaskPoolEqualNull poolEqualNull; + static TaskPoolClass poolClass = new TaskPoolClass(); + static TaskPoolWithoutSize poolWithoutSize; + static TaskPoolWithoutLock poolWithoutLock; + [Test,Performance] + public void RunBenchmark() + { + + var methods = new Action[] + { + Queue, + QueueLock, + Stack, + StackLock, + ConcurrentQueue, + ConcurrentStack, + TaskPool, + TaskPoolRefNode, + TaskPoolEqualNull, + TaskPoolClass, + TaskPoolWithoutSize, + TaskPoolWithoutLock, + }; + foreach (var method in methods) + { + Measure.Method(()=> + { + for (int i = 0; i < 10000; i++) + { + method(); + } + }).SampleGroup(method.Method.Name).Run(); + + } + + } + + public void Queue() + { + q1.Enqueue(node1); + q1.Enqueue(node1); + q1.Dequeue(); + q1.Dequeue(); + } + + + public void QueueLock() + { + lock (q1) { q1.Enqueue(node1); } + lock (q1) { q1.Enqueue(node1); } + lock (q1) { q1.Dequeue(); } + lock (q1) { q1.Dequeue(); } + } + + + public void Stack() + { + s1.Push(node1); + s1.Push(node2); + s1.Pop(); + s1.Pop(); + } + + + public void StackLock() + { + lock (s1) { s1.Push(node1); } + lock (s1) { s1.Push(node2); } + lock (s1) { s1.Pop(); } + lock (s1) { s1.Pop(); } + } + + + public void ConcurrentQueue() + { + cq.Enqueue(node1); + cq.Enqueue(node1); + cq.TryDequeue(out _); + cq.TryDequeue(out _); + } + + + public void ConcurrentStack() + { + cs.Push(node1); + cs.Push(node2); + cs.TryPop(out _); + cs.TryPop(out _); + } + + + public void TaskPool() + { + pool.TryPush(node1); + pool.TryPush(node2); + pool.TryPop(out _); + pool.TryPop(out _); + } + + public void TaskPoolRefNode() + { + poolRefNode.TryPush(refNode1); + poolRefNode.TryPush(refNode2); + poolRefNode.TryPop(out _); + poolRefNode.TryPop(out _); + } + + + public void TaskPoolEqualNull() + { + poolEqualNull.TryPush(node1); + poolEqualNull.TryPush(node2); + poolEqualNull.TryPop(out _); + poolEqualNull.TryPop(out _); + } + + + public void TaskPoolClass() + { + poolClass.TryPush(node1); + poolClass.TryPush(node2); + poolClass.TryPop(out _); + poolClass.TryPop(out _); + } + + + public void TaskPoolWithoutSize() + { + poolWithoutSize.TryPush(node1); + poolWithoutSize.TryPush(node2); + poolWithoutSize.TryPop(out _); + poolWithoutSize.TryPop(out _); + } + + + public void TaskPoolWithoutLock() + { + poolWithoutLock.TryPush(node1); + poolWithoutLock.TryPush(node2); + poolWithoutLock.TryPop(out _); + poolWithoutLock.TryPop(out _); + } +} + +public sealed class Node : ITaskPoolNode +{ + public Node NextNode { get; set; } +} + +public interface ITaskPoolNode +{ + T NextNode { get; set; } +} + +public sealed class RefNode :ITaskPoolRefNode +{ + RefNode nextNode; + public ref RefNode NextNode => ref nextNode; +} + +public interface ITaskPoolRefNode +{ + ref T NextNode { get; } +} + + +// mutable struct, don't mark readonly. +[StructLayout(LayoutKind.Auto)] +public struct TaskPoolWithoutLock + where T : class, ITaskPoolNode +{ + int size; + T root; + + public int Size => size; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryPop(out T result) + { + //if (Interlocked.CompareExchange(ref gate, 1, 0) == 0) + { + var v = root; + if (!(v is null)) + { + root = v.NextNode; + v.NextNode = null; + size--; + result = v; + // Volatile.Write(ref gate, 0); + return true; + } + + //Volatile.Write(ref gate, 0); + } + result = default; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryPush(T item) + { + //if (Interlocked.CompareExchange(ref gate, 1, 0) == 0) + { + //if (size < TaskPool.MaxPoolSize) + { + item.NextNode = root; + root = item; + size++; + // Volatile.Write(ref gate, 0); + return true; + } + //else + { + // Volatile.Write(ref gate, 0); + } + } + //return false; + } +} + +[StructLayout(LayoutKind.Auto)] +public struct TaskPool + where T : class, ITaskPoolNode +{ + int gate; + int size; + T root; + + public int Size => size; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryPop(out T result) + { + if (Interlocked.CompareExchange(ref gate, 1, 0) == 0) + { + var v = root; + if (!(v is null)) + { + root = v.NextNode; + v.NextNode = null; + size--; + result = v; + Volatile.Write(ref gate, 0); + return true; + } + + Volatile.Write(ref gate, 0); + } + result = default; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryPush(T item) + { + if (Interlocked.CompareExchange(ref gate, 1, 0) == 0) + { + //if (size < TaskPool.MaxPoolSize) + { + item.NextNode = root; + root = item; + size++; + Volatile.Write(ref gate, 0); + return true; + } + //else + { + // Volatile.Write(ref gate, 0); + } + } + return false; + } +} +[StructLayout(LayoutKind.Auto)] +public struct TaskPoolRefNode + where T : class, ITaskPoolRefNode +{ + int gate; + int size; + T root; + + public int Size => size; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryPop(out T result) + { + if (Interlocked.CompareExchange(ref gate, 1, 0) == 0) + { + var v = root; + if (!(v is null)) + { + ref var nextNode = ref v.NextNode; + root = nextNode; + nextNode = null; + size--; + result = v; + Volatile.Write(ref gate, 0); + return true; + } + + Volatile.Write(ref gate, 0); + } + result = default; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryPush(T item) + { + if (Interlocked.CompareExchange(ref gate, 1, 0) == 0) + { + //if (size < TaskPool.MaxPoolSize) + { + item.NextNode = root; + root = item; + size++; + Volatile.Write(ref gate, 0); + return true; + } + //else + { + // Volatile.Write(ref gate, 0); + } + } + return false; + } +} + +[StructLayout(LayoutKind.Auto)] +public struct TaskPoolEqualNull + where T : class, ITaskPoolNode +{ + int gate; + int size; + T root; + + public int Size => size; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryPop(out T result) + { + if (Interlocked.CompareExchange(ref gate, 1, 0) == 0) + { + var v = root; + if (v != null) + { + root = v.NextNode; + v.NextNode = null; + size--; + result = v; + Volatile.Write(ref gate, 0); + return true; + } + + Volatile.Write(ref gate, 0); + } + result = default; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryPush(T item) + { + if (Interlocked.CompareExchange(ref gate, 1, 0) == 0) + { + //if (size < TaskPool.MaxPoolSize) + { + item.NextNode = root; + root = item; + size++; + Volatile.Write(ref gate, 0); + return true; + } + //else + { + // Volatile.Write(ref gate, 0); + } + } + return false; + } +} + +public class TaskPoolClass + where T : class, ITaskPoolNode +{ + int gate; + int size; + T root; + + public int Size => size; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryPop(out T result) + { + if (Interlocked.CompareExchange(ref gate, 1, 0) == 0) + { + var v = root; + if (!(v is null)) + { + root = v.NextNode; + v.NextNode = null; + size--; + result = v; + Volatile.Write(ref gate, 0); + return true; + } + + Volatile.Write(ref gate, 0); + } + result = default; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryPush(T item) + { + if (Interlocked.CompareExchange(ref gate, 1, 0) == 0) + { + //if (size < TaskPool.MaxPoolSize) + { + item.NextNode = root; + root = item; + size++; + Volatile.Write(ref gate, 0); + return true; + } + //else + { + // Volatile.Write(ref gate, 0); + } + } + return false; + } +} + +[StructLayout(LayoutKind.Auto)] +public struct TaskPoolWithoutSize + where T : class, ITaskPoolNode +{ + int gate; + T root; + + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryPop(out T result) + { + if (Interlocked.CompareExchange(ref gate, 1, 0) == 0) + { + var v = root; + if (!(v is null)) + { + root = v.NextNode; + v.NextNode = null; + result = v; + Volatile.Write(ref gate, 0); + return true; + } + + Volatile.Write(ref gate, 0); + } + result = default; + return false; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryPush(T item) + { + if (Interlocked.CompareExchange(ref gate, 1, 0) == 0) + { + //if (size < TaskPool.MaxPoolSize) + { + item.NextNode = root; + root = item; + Volatile.Write(ref gate, 0); + return true; + } + //else + { + // Volatile.Write(ref gate, 0); + } + } + return false; + } +} \ No newline at end of file diff --git a/src/UniTask/Assets/Tests/Benchmarks/QueueCheck.cs.meta b/src/UniTask/Assets/Tests/Benchmarks/QueueCheck.cs.meta new file mode 100644 index 0000000..9eef9b8 --- /dev/null +++ b/src/UniTask/Assets/Tests/Benchmarks/QueueCheck.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6bf2be54b4d55ce41bcb7765951184e3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/src/UniTask/Assets/Tests/Benchmarks/UniTask.Tests.Benchmarks.asmdef b/src/UniTask/Assets/Tests/Benchmarks/UniTask.Tests.Benchmarks.asmdef new file mode 100644 index 0000000..db02005 --- /dev/null +++ b/src/UniTask/Assets/Tests/Benchmarks/UniTask.Tests.Benchmarks.asmdef @@ -0,0 +1,23 @@ +{ + "name": "UniTask.Tests.Benchmarks", + "references": [ + "UniTask", + "UniTask.DOTween", + "UniTask.Addressables", + "UniTask.Linq", + "UnityEngine.TestRunner", + "Unity.PerformanceTesting", + "Unity.PerformanceTesting.Tests.Runtime" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll" + ], + "autoReferenced": false, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/src/UniTask/Assets/Tests/Benchmarks/UniTask.Tests.Benchmarks.asmdef.meta b/src/UniTask/Assets/Tests/Benchmarks/UniTask.Tests.Benchmarks.asmdef.meta new file mode 100644 index 0000000..19664d4 --- /dev/null +++ b/src/UniTask/Assets/Tests/Benchmarks/UniTask.Tests.Benchmarks.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 11cdcd58147dff84b8ffb7ccedf9cd9d +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: