From 23c844b2aa84a65e573dcc023d19b8f5294a8baf Mon Sep 17 00:00:00 2001
From: jhorv <38920027+jhorv@users.noreply.github.com>
Date: Sat, 11 Mar 2023 15:05:48 -0500
Subject: [PATCH] Misc performance tweaks (#4509)

* use Array.Empty() where instead of allocating new zero-length arrays

* structure for loops in a way that the JIT will elide array/Span bounds checking

* avoiding function calls in for loop condition tests

* avoid LINQ in a hot path

* conform with code style

* fix mistake in GetNextWaitingObject()

* fix GetNextWaitingObject() possibility of returning null if all list items have TimePoint == long.MaxValue

* make GetNextWaitingObject() behave FIFO behavior for multiple items with the same TimePoint
---
 ARMeilleure/CodeGen/Arm64/CodeGenContext.cs   |  2 +-
 .../RegisterAllocators/LinearScanAllocator.cs |  9 ++------
 ARMeilleure/Decoders/DecoderHelper.cs         |  4 ++--
 ARMeilleure/Decoders/OpCodeTable.cs           |  6 ++---
 Ryujinx.Audio/Common/AudioDeviceSession.cs    |  4 +++-
 Ryujinx.Ava/UI/Windows/IconColorPicker.cs     |  2 +-
 Ryujinx.Graphics.Vulkan/Queries/Counters.cs   |  2 +-
 Ryujinx.Graphics.Vulkan/SpecInfo.cs           |  5 ++--
 Ryujinx.Graphics.Vulkan/Window.cs             |  5 ++--
 Ryujinx.HLE/HOS/Ipc/IpcHandleDesc.cs          |  4 ++--
 Ryujinx.HLE/HOS/Ipc/IpcMessage.cs             |  5 ++--
 Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs | 23 +++++++++++++++++--
 .../Time/TimeZone/TimeZoneContentManager.cs   |  2 +-
 Ryujinx.Input.SDL2/SDL2GamepadDriver.cs       |  4 +++-
 .../MockVirtualMemoryManager.cs               |  2 +-
 15 files changed, 48 insertions(+), 31 deletions(-)

diff --git a/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs b/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs
index 1ddde0c196..cebfbde121 100644
--- a/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs
+++ b/ARMeilleure/CodeGen/Arm64/CodeGenContext.cs
@@ -265,7 +265,7 @@ namespace ARMeilleure.CodeGen.Arm64
             }
             else
             {
-                relocInfo = new RelocInfo(new RelocEntry[0]);
+                relocInfo = new RelocInfo(Array.Empty<RelocEntry>());
             }
 
             return (code, relocInfo);
diff --git a/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs b/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs
index 6ea62c28b9..d80157afb4 100644
--- a/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs
+++ b/ARMeilleure/CodeGen/RegisterAllocators/LinearScanAllocator.cs
@@ -433,16 +433,11 @@ namespace ARMeilleure.CodeGen.RegisterAllocators
 
         private static int GetHighestValueIndex(Span<int> span)
         {
-            int highest = span[0];
-
-            if (highest == int.MaxValue)
-            {
-                return 0;
-            }
+            int highest = int.MinValue;
 
             int selected = 0;
 
-            for (int index = 1; index < span.Length; index++)
+            for (int index = 0; index < span.Length; index++)
             {
                 int current = span[index];
 
diff --git a/ARMeilleure/Decoders/DecoderHelper.cs b/ARMeilleure/Decoders/DecoderHelper.cs
index 38f98c39cc..5227e6a19c 100644
--- a/ARMeilleure/Decoders/DecoderHelper.cs
+++ b/ARMeilleure/Decoders/DecoderHelper.cs
@@ -17,7 +17,7 @@ namespace ARMeilleure.Decoders
         {
             uint[] tbl = new uint[256];
 
-            for (int idx = 0; idx < 256; idx++)
+            for (int idx = 0; idx < tbl.Length; idx++)
             {
                 tbl[idx] = ExpandImm8ToFP32((uint)idx);
             }
@@ -29,7 +29,7 @@ namespace ARMeilleure.Decoders
         {
             ulong[] tbl = new ulong[256];
 
-            for (int idx = 0; idx < 256; idx++)
+            for (int idx = 0; idx < tbl.Length; idx++)
             {
                 tbl[idx] = ExpandImm8ToFP64((ulong)idx);
             }
diff --git a/ARMeilleure/Decoders/OpCodeTable.cs b/ARMeilleure/Decoders/OpCodeTable.cs
index 54abb1418f..8464ce556f 100644
--- a/ARMeilleure/Decoders/OpCodeTable.cs
+++ b/ARMeilleure/Decoders/OpCodeTable.cs
@@ -1301,7 +1301,7 @@ namespace ARMeilleure.Decoders
         {
             List<InstInfo>[] temp = new List<InstInfo>[FastLookupSize];
 
-            for (int index = 0; index < FastLookupSize; index++)
+            for (int index = 0; index < temp.Length; index++)
             {
                 temp[index] = new List<InstInfo>();
             }
@@ -1311,7 +1311,7 @@ namespace ARMeilleure.Decoders
                 int mask  = ToFastLookupIndex(inst.Mask);
                 int value = ToFastLookupIndex(inst.Value);
 
-                for (int index = 0; index < FastLookupSize; index++)
+                for (int index = 0; index < temp.Length; index++)
                 {
                     if ((index & mask) == value)
                     {
@@ -1320,7 +1320,7 @@ namespace ARMeilleure.Decoders
                 }
             }
 
-            for (int index = 0; index < FastLookupSize; index++)
+            for (int index = 0; index < temp.Length; index++)
             {
                 table[index] = temp[index].ToArray();
             }
diff --git a/Ryujinx.Audio/Common/AudioDeviceSession.cs b/Ryujinx.Audio/Common/AudioDeviceSession.cs
index 07b0a89886..0191f7ccdc 100644
--- a/Ryujinx.Audio/Common/AudioDeviceSession.cs
+++ b/Ryujinx.Audio/Common/AudioDeviceSession.cs
@@ -400,7 +400,9 @@ namespace Ryujinx.Audio.Common
         {
             uint bufferIndex = (_releasedBufferIndex - _bufferReleasedCount) % Constants.AudioDeviceBufferCountMax;
 
-            for (int i = 0; i < GetTotalBufferCount(); i++)
+            uint totalBufferCount = GetTotalBufferCount();
+
+            for (int i = 0; i < totalBufferCount; i++)
             {
                 if (_buffers[bufferIndex].BufferTag == bufferTag)
                 {
diff --git a/Ryujinx.Ava/UI/Windows/IconColorPicker.cs b/Ryujinx.Ava/UI/Windows/IconColorPicker.cs
index 9dca83eb6e..a4c6287f30 100644
--- a/Ryujinx.Ava/UI/Windows/IconColorPicker.cs
+++ b/Ryujinx.Ava/UI/Windows/IconColorPicker.cs
@@ -125,7 +125,7 @@ namespace Ryujinx.Ava.UI.Windows
 
         public static Bgra32[] GetBuffer(Image<Bgra32> image)
         {
-            return image.TryGetSinglePixelSpan(out var data) ? data.ToArray() : new Bgra32[0];
+            return image.TryGetSinglePixelSpan(out var data) ? data.ToArray() : Array.Empty<Bgra32>();
         }
 
         private static int GetColorScore(Dictionary<int, int> dominantColorBin, int maxHitCount, PaletteColor color)
diff --git a/Ryujinx.Graphics.Vulkan/Queries/Counters.cs b/Ryujinx.Graphics.Vulkan/Queries/Counters.cs
index 7113d0601a..d9d65062f7 100644
--- a/Ryujinx.Graphics.Vulkan/Queries/Counters.cs
+++ b/Ryujinx.Graphics.Vulkan/Queries/Counters.cs
@@ -17,7 +17,7 @@ namespace Ryujinx.Graphics.Vulkan.Queries
 
             _counterQueues = new CounterQueue[count];
 
-            for (int index = 0; index < count; index++)
+            for (int index = 0; index < _counterQueues.Length; index++)
             {
                 CounterType type = (CounterType)index;
                 _counterQueues[index] = new CounterQueue(gd, device, pipeline, type);
diff --git a/Ryujinx.Graphics.Vulkan/SpecInfo.cs b/Ryujinx.Graphics.Vulkan/SpecInfo.cs
index 83a34cde01..4d226f615a 100644
--- a/Ryujinx.Graphics.Vulkan/SpecInfo.cs
+++ b/Ryujinx.Graphics.Vulkan/SpecInfo.cs
@@ -29,7 +29,7 @@ namespace Ryujinx.Graphics.Vulkan
 
             uint structSize = 0;
 
-            for (int i = 0; i < count; ++i)
+            for (int i = 0; i < Map.Length; ++i)
             {
                 var typeSize = SizeOf(description[i].Type);
                 Map[i] = new SpecializationMapEntry(description[i].Id, structSize, typeSize);
@@ -46,11 +46,10 @@ namespace Ryujinx.Graphics.Vulkan
         // For advanced mapping with overlapping or staggered fields
         public SpecDescription(SpecializationMapEntry[] map)
         {
-            int count = map.Length;
             Map = map;
 
             uint structSize = 0;
-            for (int i = 0; i < count; ++i)
+            for (int i = 0; i < map.Length; ++i)
             {
                 structSize = Math.Max(structSize, map[i].Offset + (uint)map[i].Size);
             }
diff --git a/Ryujinx.Graphics.Vulkan/Window.cs b/Ryujinx.Graphics.Vulkan/Window.cs
index 5d6def3a98..075d1b303a 100644
--- a/Ryujinx.Graphics.Vulkan/Window.cs
+++ b/Ryujinx.Graphics.Vulkan/Window.cs
@@ -60,10 +60,9 @@ namespace Ryujinx.Graphics.Vulkan
         private void RecreateSwapchain()
         {
             var oldSwapchain = _swapchain;
-            int imageCount = _swapchainImageViews.Length;
             _vsyncModeChanged = false;
 
-            for (int i = 0; i < imageCount; i++)
+            for (int i = 0; i < _swapchainImageViews.Length; i++)
             {
                 _swapchainImageViews[i].Dispose();
             }
@@ -147,7 +146,7 @@ namespace Ryujinx.Graphics.Vulkan
 
             _swapchainImageViews = new Auto<DisposableImageView>[imageCount];
 
-            for (int i = 0; i < imageCount; i++)
+            for (int i = 0; i < _swapchainImageViews.Length; i++)
             {
                 _swapchainImageViews[i] = CreateSwapchainImageView(_swapchainImages[i], surfaceFormat.Format);
             }
diff --git a/Ryujinx.HLE/HOS/Ipc/IpcHandleDesc.cs b/Ryujinx.HLE/HOS/Ipc/IpcHandleDesc.cs
index 439590e0c5..e6ed46138e 100644
--- a/Ryujinx.HLE/HOS/Ipc/IpcHandleDesc.cs
+++ b/Ryujinx.HLE/HOS/Ipc/IpcHandleDesc.cs
@@ -49,12 +49,12 @@ namespace Ryujinx.HLE.HOS.Ipc
 
         public static IpcHandleDesc MakeCopy(params int[] handles)
         {
-            return new IpcHandleDesc(handles, new int[0]);
+            return new IpcHandleDesc(handles, Array.Empty<int>());
         }
 
         public static IpcHandleDesc MakeMove(params int[] handles)
         {
-            return new IpcHandleDesc(new int[0], handles);
+            return new IpcHandleDesc(Array.Empty<int>(), handles);
         }
 
         public byte[] GetBytes()
diff --git a/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs b/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs
index 09e237fe54..55044da404 100644
--- a/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs
+++ b/Ryujinx.HLE/HOS/Ipc/IpcMessage.cs
@@ -1,3 +1,4 @@
+using System;
 using System.Collections.Generic;
 using System.Diagnostics;
 using System.IO;
@@ -132,7 +133,7 @@ namespace Ryujinx.HLE.HOS.Ipc
                 word0 |= (ReceiveBuff.Count  & 0xf) << 24;
                 word0 |= (ExchangeBuff.Count & 0xf) << 28;
 
-                byte[] handleData = new byte[0];
+                byte[] handleData = Array.Empty<byte>();
 
                 if (HandleDesc != null)
                 {
@@ -202,7 +203,7 @@ namespace Ryujinx.HLE.HOS.Ipc
                 word0 |= (ReceiveBuff.Count & 0xf) << 24;
                 word0 |= (ExchangeBuff.Count & 0xf) << 28;
 
-                byte[] handleData = new byte[0];
+                byte[] handleData = Array.Empty<byte>();
 
                 if (HandleDesc != null)
                 {
diff --git a/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs b/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs
index 020048f4e0..030a314f7e 100644
--- a/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs
+++ b/Ryujinx.HLE/HOS/Kernel/Common/KTimeManager.cs
@@ -1,7 +1,6 @@
 using Ryujinx.Common;
 using System;
 using System.Collections.Generic;
-using System.Linq;
 using System.Threading;
 
 namespace Ryujinx.HLE.HOS.Kernel.Common
@@ -86,7 +85,7 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
                     {
                         Interlocked.Exchange(ref _enforceWakeupFromSpinWait, 0);
 
-                        next = _waitingObjects.OrderBy(x => x.TimePoint).FirstOrDefault();
+                        next = GetNextWaitingObject();
                     }
 
                     if (next != null)
@@ -140,6 +139,26 @@ namespace Ryujinx.HLE.HOS.Kernel.Common
             }
         }
 
+        private WaitingObject GetNextWaitingObject()
+        {
+            WaitingObject selected = null;
+            
+            long lowestTimePoint = long.MaxValue;
+
+            for (int index = _waitingObjects.Count - 1; index >= 0; index--)
+            {
+                WaitingObject current = _waitingObjects[index];
+
+                if (current.TimePoint <= lowestTimePoint)
+                {
+                    selected = current;
+                    lowestTimePoint = current.TimePoint;
+                }
+            }
+
+            return selected;
+        }
+
         public static long ConvertNanosecondsToMilliseconds(long time)
         {
             time /= 1000000;
diff --git a/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs b/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs
index 69ed56d45b..9367024e4c 100644
--- a/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs
+++ b/Ryujinx.HLE/HOS/Services/Time/TimeZone/TimeZoneContentManager.cs
@@ -233,7 +233,7 @@ namespace Ryujinx.HLE.HOS.Services.Time.TimeZone
                 // If the location name is too long, error out.
                 if (locationName.Length > 0x24)
                 {
-                    outLocationNameArray = new string[0];
+                    outLocationNameArray = Array.Empty<string>();
 
                     return ResultCode.LocationNameTooLong;
                 }
diff --git a/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs b/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs
index b20a76b843..d4086a1050 100644
--- a/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs
+++ b/Ryujinx.Input.SDL2/SDL2GamepadDriver.cs
@@ -27,7 +27,9 @@ namespace Ryujinx.Input.SDL2
             SDL2Driver.Instance.OnJoystickDisconnected += HandleJoyStickDisconnected;
 
             // Add already connected gamepads
-            for (int joystickIndex = 0; joystickIndex < SDL_NumJoysticks(); joystickIndex++)
+            int numJoysticks = SDL_NumJoysticks();
+
+            for (int joystickIndex = 0; joystickIndex < numJoysticks; joystickIndex++)
             {
                 HandleJoyStickConnected(joystickIndex, SDL_JoystickGetDeviceInstanceID(joystickIndex));
             }
diff --git a/Ryujinx.Memory.Tests/MockVirtualMemoryManager.cs b/Ryujinx.Memory.Tests/MockVirtualMemoryManager.cs
index 6729f4a36f..ef81a46159 100644
--- a/Ryujinx.Memory.Tests/MockVirtualMemoryManager.cs
+++ b/Ryujinx.Memory.Tests/MockVirtualMemoryManager.cs
@@ -78,7 +78,7 @@ namespace Ryujinx.Memory.Tests
 
         IEnumerable<MemoryRange> IVirtualMemoryManager.GetPhysicalRegions(ulong va, ulong size)
         {
-            return NoMappings ? new MemoryRange[0] : new MemoryRange[] { new MemoryRange(va, size) };
+            return NoMappings ? Array.Empty<MemoryRange>() : new MemoryRange[] { new MemoryRange(va, size) };
         }
 
         public bool IsMapped(ulong va)