forked from Mirror/Ryujinx
90163087a0
* PPTC vs. giant ExeFS. * InternalVersion = 2168 * Add new heuristic algorithm for calculating the number of threads for parallel translations that also takes into account the user's free physical memory and not just the number of CPU cores. * Nit. * Add an outer Header structure and add the hashes for both this new structure and the existing "inner" Header structure. * InternalVersion = 2169
402 lines
No EOL
12 KiB
C#
402 lines
No EOL
12 KiB
C#
using ARMeilleure.State;
|
|
using Ryujinx.Common.Logging;
|
|
using System;
|
|
using System.Collections.Concurrent;
|
|
using System.Collections.Generic;
|
|
using System.Diagnostics;
|
|
using System.IO;
|
|
using System.IO.Compression;
|
|
using System.Runtime.InteropServices;
|
|
using System.Security.Cryptography;
|
|
using System.Threading;
|
|
|
|
using static ARMeilleure.Translation.PTC.PtcFormatter;
|
|
|
|
namespace ARMeilleure.Translation.PTC
|
|
{
|
|
public static class PtcProfiler
|
|
{
|
|
private const string HeaderMagic = "Phd";
|
|
|
|
private const uint InternalVersion = 1713; //! 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 static readonly System.Timers.Timer _timer;
|
|
|
|
private static readonly ManualResetEvent _waitEvent;
|
|
|
|
private static readonly object _lock;
|
|
|
|
private static bool _disposed;
|
|
|
|
private static byte[] _lastHash;
|
|
|
|
internal static Dictionary<ulong, FuncProfile> ProfiledFuncs { get; private set; }
|
|
|
|
internal static bool Enabled { get; private set; }
|
|
|
|
public static ulong StaticCodeStart { internal get; set; }
|
|
public static ulong StaticCodeSize { internal get; set; }
|
|
|
|
static PtcProfiler()
|
|
{
|
|
_timer = new System.Timers.Timer((double)SaveInterval * 1000d);
|
|
_timer.Elapsed += PreSave;
|
|
|
|
_waitEvent = new ManualResetEvent(true);
|
|
|
|
_lock = new object();
|
|
|
|
_disposed = false;
|
|
|
|
ProfiledFuncs = new Dictionary<ulong, FuncProfile>();
|
|
|
|
Enabled = false;
|
|
}
|
|
|
|
internal static void AddEntry(ulong address, ExecutionMode mode, bool highCq)
|
|
{
|
|
if (IsAddressInStaticCodeRange(address))
|
|
{
|
|
Debug.Assert(!highCq);
|
|
|
|
lock (_lock)
|
|
{
|
|
ProfiledFuncs.TryAdd(address, new FuncProfile(mode, highCq: false));
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
internal static bool IsAddressInStaticCodeRange(ulong address)
|
|
{
|
|
return address >= StaticCodeStart && address < StaticCodeStart + StaticCodeSize;
|
|
}
|
|
|
|
internal static ConcurrentQueue<(ulong address, ExecutionMode mode, bool highCq)> GetProfiledFuncsToTranslate(ConcurrentDictionary<ulong, TranslatedFunction> funcs)
|
|
{
|
|
var profiledFuncsToTranslate = new ConcurrentQueue<(ulong address, ExecutionMode mode, bool highCq)>();
|
|
|
|
foreach (var profiledFunc in ProfiledFuncs)
|
|
{
|
|
ulong address = profiledFunc.Key;
|
|
|
|
if (!funcs.ContainsKey(address))
|
|
{
|
|
profiledFuncsToTranslate.Enqueue((address, profiledFunc.Value.Mode, profiledFunc.Value.HighCq));
|
|
}
|
|
}
|
|
|
|
return profiledFuncsToTranslate;
|
|
}
|
|
|
|
internal static void ClearEntries()
|
|
{
|
|
ProfiledFuncs.Clear();
|
|
ProfiledFuncs.TrimExcess();
|
|
}
|
|
|
|
internal static void PreLoad()
|
|
{
|
|
_lastHash = Array.Empty<byte>();
|
|
|
|
string fileNameActual = string.Concat(Ptc.CachePathActual, ".info");
|
|
string fileNameBackup = string.Concat(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 static bool Load(string fileName, bool isBackup)
|
|
{
|
|
using (FileStream compressedStream = new FileStream(fileName, FileMode.Open))
|
|
using (DeflateStream deflateStream = new DeflateStream(compressedStream, CompressionMode.Decompress, true))
|
|
using (MemoryStream stream = new MemoryStream())
|
|
using (MD5 md5 = MD5.Create())
|
|
{
|
|
try
|
|
{
|
|
deflateStream.CopyTo(stream);
|
|
}
|
|
catch
|
|
{
|
|
InvalidateCompressedStream(compressedStream);
|
|
|
|
return false;
|
|
}
|
|
|
|
int hashSize = md5.HashSize / 8;
|
|
|
|
stream.Seek(0L, SeekOrigin.Begin);
|
|
|
|
byte[] currentHash = new byte[hashSize];
|
|
stream.Read(currentHash, 0, hashSize);
|
|
|
|
byte[] expectedHash = md5.ComputeHash(stream);
|
|
|
|
if (!CompareHash(currentHash, expectedHash))
|
|
{
|
|
InvalidateCompressedStream(compressedStream);
|
|
|
|
return false;
|
|
}
|
|
|
|
stream.Seek((long)hashSize, SeekOrigin.Begin);
|
|
|
|
Header header = ReadHeader(stream);
|
|
|
|
if (header.Magic != HeaderMagic)
|
|
{
|
|
InvalidateCompressedStream(compressedStream);
|
|
|
|
return false;
|
|
}
|
|
|
|
if (header.InfoFileVersion != InternalVersion)
|
|
{
|
|
InvalidateCompressedStream(compressedStream);
|
|
|
|
return false;
|
|
}
|
|
|
|
try
|
|
{
|
|
ProfiledFuncs = Deserialize(stream);
|
|
}
|
|
catch
|
|
{
|
|
ProfiledFuncs = new Dictionary<ulong, FuncProfile>();
|
|
|
|
InvalidateCompressedStream(compressedStream);
|
|
|
|
return false;
|
|
}
|
|
|
|
_lastHash = expectedHash;
|
|
}
|
|
|
|
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 bool CompareHash(ReadOnlySpan<byte> currentHash, ReadOnlySpan<byte> expectedHash)
|
|
{
|
|
return currentHash.SequenceEqual(expectedHash);
|
|
}
|
|
|
|
private static Header ReadHeader(Stream stream)
|
|
{
|
|
using (BinaryReader headerReader = new BinaryReader(stream, EncodingCache.UTF8NoBOM, true))
|
|
{
|
|
Header header = new Header();
|
|
|
|
header.Magic = headerReader.ReadString();
|
|
|
|
header.InfoFileVersion = headerReader.ReadUInt32();
|
|
|
|
return header;
|
|
}
|
|
}
|
|
|
|
private static Dictionary<ulong, FuncProfile> Deserialize(Stream stream)
|
|
{
|
|
return DeserializeDictionary<ulong, FuncProfile>(stream, (stream) => DeserializeStructure<FuncProfile>(stream));
|
|
}
|
|
|
|
private static void InvalidateCompressedStream(FileStream compressedStream)
|
|
{
|
|
compressedStream.SetLength(0L);
|
|
}
|
|
|
|
private static void PreSave(object source, System.Timers.ElapsedEventArgs e)
|
|
{
|
|
_waitEvent.Reset();
|
|
|
|
string fileNameActual = string.Concat(Ptc.CachePathActual, ".info");
|
|
string fileNameBackup = string.Concat(Ptc.CachePathBackup, ".info");
|
|
|
|
FileInfo fileInfoActual = new FileInfo(fileNameActual);
|
|
|
|
if (fileInfoActual.Exists && fileInfoActual.Length != 0L)
|
|
{
|
|
File.Copy(fileNameActual, fileNameBackup, true);
|
|
}
|
|
|
|
Save(fileNameActual);
|
|
|
|
_waitEvent.Set();
|
|
}
|
|
|
|
private static void Save(string fileName)
|
|
{
|
|
int profiledFuncsCount;
|
|
|
|
using (MemoryStream stream = new MemoryStream())
|
|
using (MD5 md5 = MD5.Create())
|
|
{
|
|
int hashSize = md5.HashSize / 8;
|
|
|
|
stream.Seek((long)hashSize, SeekOrigin.Begin);
|
|
|
|
WriteHeader(stream);
|
|
|
|
lock (_lock)
|
|
{
|
|
Serialize(stream, ProfiledFuncs);
|
|
|
|
profiledFuncsCount = ProfiledFuncs.Count;
|
|
}
|
|
|
|
stream.Seek((long)hashSize, SeekOrigin.Begin);
|
|
byte[] hash = md5.ComputeHash(stream);
|
|
|
|
stream.Seek(0L, SeekOrigin.Begin);
|
|
stream.Write(hash, 0, hashSize);
|
|
|
|
if (CompareHash(hash, _lastHash))
|
|
{
|
|
return;
|
|
}
|
|
|
|
using (FileStream compressedStream = new FileStream(fileName, FileMode.OpenOrCreate))
|
|
using (DeflateStream deflateStream = new DeflateStream(compressedStream, SaveCompressionLevel, true))
|
|
{
|
|
try
|
|
{
|
|
stream.WriteTo(deflateStream);
|
|
|
|
_lastHash = hash;
|
|
}
|
|
catch
|
|
{
|
|
compressedStream.Position = 0L;
|
|
|
|
_lastHash = Array.Empty<byte>();
|
|
}
|
|
|
|
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 static void WriteHeader(Stream stream)
|
|
{
|
|
using (BinaryWriter headerWriter = new BinaryWriter(stream, EncodingCache.UTF8NoBOM, true))
|
|
{
|
|
headerWriter.Write((string)HeaderMagic); // Header.Magic
|
|
|
|
headerWriter.Write((uint)InternalVersion); // Header.InfoFileVersion
|
|
}
|
|
}
|
|
|
|
private static void Serialize(Stream stream, Dictionary<ulong, FuncProfile> profiledFuncs)
|
|
{
|
|
SerializeDictionary(stream, profiledFuncs, (stream, structure) => SerializeStructure(stream, structure));
|
|
}
|
|
|
|
private struct Header
|
|
{
|
|
public string Magic;
|
|
|
|
public uint InfoFileVersion;
|
|
}
|
|
|
|
[StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 5*/)]
|
|
internal struct FuncProfile
|
|
{
|
|
public ExecutionMode Mode;
|
|
public bool HighCq;
|
|
|
|
public FuncProfile(ExecutionMode mode, bool highCq)
|
|
{
|
|
Mode = mode;
|
|
HighCq = highCq;
|
|
}
|
|
}
|
|
|
|
internal static void Start()
|
|
{
|
|
if (Ptc.State == PtcState.Enabled ||
|
|
Ptc.State == PtcState.Continuing)
|
|
{
|
|
Enabled = true;
|
|
|
|
_timer.Enabled = true;
|
|
}
|
|
}
|
|
|
|
public static void Stop()
|
|
{
|
|
Enabled = false;
|
|
|
|
if (!_disposed)
|
|
{
|
|
_timer.Enabled = false;
|
|
}
|
|
}
|
|
|
|
internal static void Wait()
|
|
{
|
|
_waitEvent.WaitOne();
|
|
}
|
|
|
|
public static void Dispose()
|
|
{
|
|
if (!_disposed)
|
|
{
|
|
_disposed = true;
|
|
|
|
_timer.Elapsed -= PreSave;
|
|
_timer.Dispose();
|
|
|
|
Wait();
|
|
_waitEvent.Dispose();
|
|
}
|
|
}
|
|
}
|
|
} |