aboutsummaryrefslogtreecommitdiff
path: root/src/ARMeilleure/Translation/PTC/PtcProfiler.cs
diff options
context:
space:
mode:
Diffstat (limited to 'src/ARMeilleure/Translation/PTC/PtcProfiler.cs')
-rw-r--r--src/ARMeilleure/Translation/PTC/PtcProfiler.cs421
1 files changed, 421 insertions, 0 deletions
diff --git a/src/ARMeilleure/Translation/PTC/PtcProfiler.cs b/src/ARMeilleure/Translation/PTC/PtcProfiler.cs
new file mode 100644
index 00000000..391e29c7
--- /dev/null
+++ b/src/ARMeilleure/Translation/PTC/PtcProfiler.cs
@@ -0,0 +1,421 @@
+using ARMeilleure.State;
+using Ryujinx.Common;
+using Ryujinx.Common.Logging;
+using Ryujinx.Common.Memory;
+using System;
+using System.Buffers.Binary;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Diagnostics;
+using System.IO;
+using System.IO.Compression;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Threading;
+
+using static ARMeilleure.Translation.PTC.PtcFormatter;
+
+namespace ARMeilleure.Translation.PTC
+{
+ class PtcProfiler
+ {
+ private const string OuterHeaderMagicString = "Pohd\0\0\0\0";
+
+ private const uint InternalVersion = 1866; //! Not to be incremented manually for each change to the ARMeilleure project.
+
+ private const int SaveInterval = 30; // Seconds.
+
+ private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest;
+
+ private readonly Ptc _ptc;
+
+ private readonly System.Timers.Timer _timer;
+
+ private readonly ulong _outerHeaderMagic;
+
+ private readonly ManualResetEvent _waitEvent;
+
+ private readonly object _lock;
+
+ private bool _disposed;
+
+ private Hash128 _lastHash;
+
+ public Dictionary<ulong, FuncProfile> ProfiledFuncs { get; private set; }
+
+ public bool Enabled { get; private set; }
+
+ public ulong StaticCodeStart { get; set; }
+ public ulong StaticCodeSize { get; set; }
+
+ public PtcProfiler(Ptc ptc)
+ {
+ _ptc = ptc;
+
+ _timer = new System.Timers.Timer((double)SaveInterval * 1000d);
+ _timer.Elapsed += PreSave;
+
+ _outerHeaderMagic = BinaryPrimitives.ReadUInt64LittleEndian(EncodingCache.UTF8NoBOM.GetBytes(OuterHeaderMagicString).AsSpan());
+
+ _waitEvent = new ManualResetEvent(true);
+
+ _lock = new object();
+
+ _disposed = false;
+
+ ProfiledFuncs = new Dictionary<ulong, FuncProfile>();
+
+ Enabled = false;
+ }
+
+ public void AddEntry(ulong address, ExecutionMode mode, bool highCq)
+ {
+ if (IsAddressInStaticCodeRange(address))
+ {
+ Debug.Assert(!highCq);
+
+ lock (_lock)
+ {
+ ProfiledFuncs.TryAdd(address, new FuncProfile(mode, highCq: false));
+ }
+ }
+ }
+
+ public void UpdateEntry(ulong address, ExecutionMode mode, bool highCq)
+ {
+ if (IsAddressInStaticCodeRange(address))
+ {
+ Debug.Assert(highCq);
+
+ lock (_lock)
+ {
+ Debug.Assert(ProfiledFuncs.ContainsKey(address));
+
+ ProfiledFuncs[address] = new FuncProfile(mode, highCq: true);
+ }
+ }
+ }
+
+ public bool IsAddressInStaticCodeRange(ulong address)
+ {
+ return address >= StaticCodeStart && address < StaticCodeStart + StaticCodeSize;
+ }
+
+ public ConcurrentQueue<(ulong address, FuncProfile funcProfile)> GetProfiledFuncsToTranslate(TranslatorCache<TranslatedFunction> funcs)
+ {
+ var profiledFuncsToTranslate = new ConcurrentQueue<(ulong address, FuncProfile funcProfile)>();
+
+ foreach (var profiledFunc in ProfiledFuncs)
+ {
+ if (!funcs.ContainsKey(profiledFunc.Key))
+ {
+ profiledFuncsToTranslate.Enqueue((profiledFunc.Key, profiledFunc.Value));
+ }
+ }
+
+ return profiledFuncsToTranslate;
+ }
+
+ public void ClearEntries()
+ {
+ ProfiledFuncs.Clear();
+ ProfiledFuncs.TrimExcess();
+ }
+
+ public void PreLoad()
+ {
+ _lastHash = default;
+
+ string fileNameActual = $"{_ptc.CachePathActual}.info";
+ string fileNameBackup = $"{_ptc.CachePathBackup}.info";
+
+ FileInfo fileInfoActual = new FileInfo(fileNameActual);
+ FileInfo fileInfoBackup = new FileInfo(fileNameBackup);
+
+ if (fileInfoActual.Exists && fileInfoActual.Length != 0L)
+ {
+ if (!Load(fileNameActual, false))
+ {
+ if (fileInfoBackup.Exists && fileInfoBackup.Length != 0L)
+ {
+ Load(fileNameBackup, true);
+ }
+ }
+ }
+ else if (fileInfoBackup.Exists && fileInfoBackup.Length != 0L)
+ {
+ Load(fileNameBackup, true);
+ }
+ }
+
+ private bool Load(string fileName, bool isBackup)
+ {
+ using (FileStream compressedStream = new(fileName, FileMode.Open))
+ using (DeflateStream deflateStream = new(compressedStream, CompressionMode.Decompress, true))
+ {
+ OuterHeader outerHeader = DeserializeStructure<OuterHeader>(compressedStream);
+
+ if (!outerHeader.IsHeaderValid())
+ {
+ InvalidateCompressedStream(compressedStream);
+
+ return false;
+ }
+
+ if (outerHeader.Magic != _outerHeaderMagic)
+ {
+ InvalidateCompressedStream(compressedStream);
+
+ return false;
+ }
+
+ if (outerHeader.InfoFileVersion != InternalVersion)
+ {
+ InvalidateCompressedStream(compressedStream);
+
+ return false;
+ }
+
+ if (outerHeader.Endianness != Ptc.GetEndianness())
+ {
+ InvalidateCompressedStream(compressedStream);
+
+ return false;
+ }
+
+ using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
+ {
+ Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L);
+
+ try
+ {
+ deflateStream.CopyTo(stream);
+ }
+ catch
+ {
+ InvalidateCompressedStream(compressedStream);
+
+ return false;
+ }
+
+ Debug.Assert(stream.Position == stream.Length);
+
+ stream.Seek(0L, SeekOrigin.Begin);
+
+ Hash128 expectedHash = DeserializeStructure<Hash128>(stream);
+
+ Hash128 actualHash = XXHash128.ComputeHash(GetReadOnlySpan(stream));
+
+ if (actualHash != expectedHash)
+ {
+ InvalidateCompressedStream(compressedStream);
+
+ return false;
+ }
+
+ ProfiledFuncs = Deserialize(stream);
+
+ Debug.Assert(stream.Position == stream.Length);
+
+ _lastHash = actualHash;
+ }
+ }
+
+ long fileSize = new FileInfo(fileName).Length;
+
+ Logger.Info?.Print(LogClass.Ptc, $"{(isBackup ? "Loaded Backup Profiling Info" : "Loaded Profiling Info")} (size: {fileSize} bytes, profiled functions: {ProfiledFuncs.Count}).");
+
+ return true;
+ }
+
+ private static Dictionary<ulong, FuncProfile> Deserialize(Stream stream)
+ {
+ return DeserializeDictionary<ulong, FuncProfile>(stream, (stream) => DeserializeStructure<FuncProfile>(stream));
+ }
+
+ private ReadOnlySpan<byte> GetReadOnlySpan(MemoryStream memoryStream)
+ {
+ return new(memoryStream.GetBuffer(), (int)memoryStream.Position, (int)memoryStream.Length - (int)memoryStream.Position);
+ }
+
+ private void InvalidateCompressedStream(FileStream compressedStream)
+ {
+ compressedStream.SetLength(0L);
+ }
+
+ private void PreSave(object source, System.Timers.ElapsedEventArgs e)
+ {
+ _waitEvent.Reset();
+
+ string fileNameActual = $"{_ptc.CachePathActual}.info";
+ string fileNameBackup = $"{_ptc.CachePathBackup}.info";
+
+ FileInfo fileInfoActual = new FileInfo(fileNameActual);
+
+ if (fileInfoActual.Exists && fileInfoActual.Length != 0L)
+ {
+ File.Copy(fileNameActual, fileNameBackup, true);
+ }
+
+ Save(fileNameActual);
+
+ _waitEvent.Set();
+ }
+
+ private void Save(string fileName)
+ {
+ int profiledFuncsCount;
+
+ OuterHeader outerHeader = new OuterHeader();
+
+ outerHeader.Magic = _outerHeaderMagic;
+
+ outerHeader.InfoFileVersion = InternalVersion;
+ outerHeader.Endianness = Ptc.GetEndianness();
+
+ outerHeader.SetHeaderHash();
+
+ using (MemoryStream stream = MemoryStreamManager.Shared.GetStream())
+ {
+ Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L);
+
+ stream.Seek((long)Unsafe.SizeOf<Hash128>(), SeekOrigin.Begin);
+
+ lock (_lock)
+ {
+ Serialize(stream, ProfiledFuncs);
+
+ profiledFuncsCount = ProfiledFuncs.Count;
+ }
+
+ Debug.Assert(stream.Position == stream.Length);
+
+ stream.Seek((long)Unsafe.SizeOf<Hash128>(), SeekOrigin.Begin);
+ Hash128 hash = XXHash128.ComputeHash(GetReadOnlySpan(stream));
+
+ stream.Seek(0L, SeekOrigin.Begin);
+ SerializeStructure(stream, hash);
+
+ if (hash == _lastHash)
+ {
+ return;
+ }
+
+ using (FileStream compressedStream = new(fileName, FileMode.OpenOrCreate))
+ using (DeflateStream deflateStream = new(compressedStream, SaveCompressionLevel, true))
+ {
+ try
+ {
+ SerializeStructure(compressedStream, outerHeader);
+
+ stream.WriteTo(deflateStream);
+
+ _lastHash = hash;
+ }
+ catch
+ {
+ compressedStream.Position = 0L;
+
+ _lastHash = default;
+ }
+
+ if (compressedStream.Position < compressedStream.Length)
+ {
+ compressedStream.SetLength(compressedStream.Position);
+ }
+ }
+ }
+
+ long fileSize = new FileInfo(fileName).Length;
+
+ if (fileSize != 0L)
+ {
+ Logger.Info?.Print(LogClass.Ptc, $"Saved Profiling Info (size: {fileSize} bytes, profiled functions: {profiledFuncsCount}).");
+ }
+ }
+
+ private void Serialize(Stream stream, Dictionary<ulong, FuncProfile> profiledFuncs)
+ {
+ SerializeDictionary(stream, profiledFuncs, (stream, structure) => SerializeStructure(stream, structure));
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 29*/)]
+ private struct OuterHeader
+ {
+ public ulong Magic;
+
+ public uint InfoFileVersion;
+
+ public bool Endianness;
+
+ public Hash128 HeaderHash;
+
+ public void SetHeaderHash()
+ {
+ Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
+
+ HeaderHash = XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader).Slice(0, Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>()));
+ }
+
+ public bool IsHeaderValid()
+ {
+ Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
+
+ return XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader).Slice(0, Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>())) == HeaderHash;
+ }
+ }
+
+ [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 5*/)]
+ public struct FuncProfile
+ {
+ public ExecutionMode Mode;
+ public bool HighCq;
+
+ public FuncProfile(ExecutionMode mode, bool highCq)
+ {
+ Mode = mode;
+ HighCq = highCq;
+ }
+ }
+
+ public void Start()
+ {
+ if (_ptc.State == PtcState.Enabled ||
+ _ptc.State == PtcState.Continuing)
+ {
+ Enabled = true;
+
+ _timer.Enabled = true;
+ }
+ }
+
+ public void Stop()
+ {
+ Enabled = false;
+
+ if (!_disposed)
+ {
+ _timer.Enabled = false;
+ }
+ }
+
+ public void Wait()
+ {
+ _waitEvent.WaitOne();
+ }
+
+ public void Dispose()
+ {
+ if (!_disposed)
+ {
+ _disposed = true;
+
+ _timer.Elapsed -= PreSave;
+ _timer.Dispose();
+
+ Wait();
+ _waitEvent.Dispose();
+ }
+ }
+ }
+} \ No newline at end of file