PtcProfiler.cs 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. using ARMeilleure.State;
  2. using Ryujinx.Common;
  3. using Ryujinx.Common.Logging;
  4. using System;
  5. using System.Buffers.Binary;
  6. using System.Collections.Concurrent;
  7. using System.Collections.Generic;
  8. using System.Diagnostics;
  9. using System.IO;
  10. using System.IO.Compression;
  11. using System.Runtime.CompilerServices;
  12. using System.Runtime.InteropServices;
  13. using System.Threading;
  14. using static ARMeilleure.Translation.PTC.PtcFormatter;
  15. namespace ARMeilleure.Translation.PTC
  16. {
  17. public static class PtcProfiler
  18. {
  19. private const string OuterHeaderMagicString = "Pohd\0\0\0\0";
  20. private const uint InternalVersion = 1866; //! Not to be incremented manually for each change to the ARMeilleure project.
  21. private const int SaveInterval = 30; // Seconds.
  22. private const CompressionLevel SaveCompressionLevel = CompressionLevel.Fastest;
  23. private static readonly System.Timers.Timer _timer;
  24. private static readonly ulong _outerHeaderMagic;
  25. private static readonly ManualResetEvent _waitEvent;
  26. private static readonly object _lock;
  27. private static bool _disposed;
  28. private static Hash128 _lastHash;
  29. internal static Dictionary<ulong, FuncProfile> ProfiledFuncs { get; private set; }
  30. internal static bool Enabled { get; private set; }
  31. public static ulong StaticCodeStart { internal get; set; }
  32. public static ulong StaticCodeSize { internal get; set; }
  33. static PtcProfiler()
  34. {
  35. _timer = new System.Timers.Timer((double)SaveInterval * 1000d);
  36. _timer.Elapsed += PreSave;
  37. _outerHeaderMagic = BinaryPrimitives.ReadUInt64LittleEndian(EncodingCache.UTF8NoBOM.GetBytes(OuterHeaderMagicString).AsSpan());
  38. _waitEvent = new ManualResetEvent(true);
  39. _lock = new object();
  40. _disposed = false;
  41. ProfiledFuncs = new Dictionary<ulong, FuncProfile>();
  42. Enabled = false;
  43. }
  44. internal static void AddEntry(ulong address, ExecutionMode mode, bool highCq)
  45. {
  46. if (IsAddressInStaticCodeRange(address))
  47. {
  48. Debug.Assert(!highCq);
  49. lock (_lock)
  50. {
  51. ProfiledFuncs.TryAdd(address, new FuncProfile(mode, highCq: false));
  52. }
  53. }
  54. }
  55. internal static void UpdateEntry(ulong address, ExecutionMode mode, bool highCq)
  56. {
  57. if (IsAddressInStaticCodeRange(address))
  58. {
  59. Debug.Assert(highCq);
  60. lock (_lock)
  61. {
  62. Debug.Assert(ProfiledFuncs.ContainsKey(address));
  63. ProfiledFuncs[address] = new FuncProfile(mode, highCq: true);
  64. }
  65. }
  66. }
  67. internal static bool IsAddressInStaticCodeRange(ulong address)
  68. {
  69. return address >= StaticCodeStart && address < StaticCodeStart + StaticCodeSize;
  70. }
  71. internal static ConcurrentQueue<(ulong address, FuncProfile funcProfile)> GetProfiledFuncsToTranslate(TranslatorCache<TranslatedFunction> funcs)
  72. {
  73. var profiledFuncsToTranslate = new ConcurrentQueue<(ulong address, FuncProfile funcProfile)>();
  74. foreach (var profiledFunc in ProfiledFuncs)
  75. {
  76. if (!funcs.ContainsKey(profiledFunc.Key))
  77. {
  78. profiledFuncsToTranslate.Enqueue((profiledFunc.Key, profiledFunc.Value));
  79. }
  80. }
  81. return profiledFuncsToTranslate;
  82. }
  83. internal static void ClearEntries()
  84. {
  85. ProfiledFuncs.Clear();
  86. ProfiledFuncs.TrimExcess();
  87. }
  88. internal static void PreLoad()
  89. {
  90. _lastHash = default;
  91. string fileNameActual = string.Concat(Ptc.CachePathActual, ".info");
  92. string fileNameBackup = string.Concat(Ptc.CachePathBackup, ".info");
  93. FileInfo fileInfoActual = new FileInfo(fileNameActual);
  94. FileInfo fileInfoBackup = new FileInfo(fileNameBackup);
  95. if (fileInfoActual.Exists && fileInfoActual.Length != 0L)
  96. {
  97. if (!Load(fileNameActual, false))
  98. {
  99. if (fileInfoBackup.Exists && fileInfoBackup.Length != 0L)
  100. {
  101. Load(fileNameBackup, true);
  102. }
  103. }
  104. }
  105. else if (fileInfoBackup.Exists && fileInfoBackup.Length != 0L)
  106. {
  107. Load(fileNameBackup, true);
  108. }
  109. }
  110. private static bool Load(string fileName, bool isBackup)
  111. {
  112. using (FileStream compressedStream = new(fileName, FileMode.Open))
  113. using (DeflateStream deflateStream = new(compressedStream, CompressionMode.Decompress, true))
  114. {
  115. OuterHeader outerHeader = DeserializeStructure<OuterHeader>(compressedStream);
  116. if (!outerHeader.IsHeaderValid())
  117. {
  118. InvalidateCompressedStream(compressedStream);
  119. return false;
  120. }
  121. if (outerHeader.Magic != _outerHeaderMagic)
  122. {
  123. InvalidateCompressedStream(compressedStream);
  124. return false;
  125. }
  126. if (outerHeader.InfoFileVersion != InternalVersion)
  127. {
  128. InvalidateCompressedStream(compressedStream);
  129. return false;
  130. }
  131. if (outerHeader.Endianness != Ptc.GetEndianness())
  132. {
  133. InvalidateCompressedStream(compressedStream);
  134. return false;
  135. }
  136. using (MemoryStream stream = new MemoryStream())
  137. {
  138. Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L);
  139. try
  140. {
  141. deflateStream.CopyTo(stream);
  142. }
  143. catch
  144. {
  145. InvalidateCompressedStream(compressedStream);
  146. return false;
  147. }
  148. Debug.Assert(stream.Position == stream.Length);
  149. stream.Seek(0L, SeekOrigin.Begin);
  150. Hash128 expectedHash = DeserializeStructure<Hash128>(stream);
  151. Hash128 actualHash = XXHash128.ComputeHash(GetReadOnlySpan(stream));
  152. if (actualHash != expectedHash)
  153. {
  154. InvalidateCompressedStream(compressedStream);
  155. return false;
  156. }
  157. ProfiledFuncs = Deserialize(stream);
  158. Debug.Assert(stream.Position == stream.Length);
  159. _lastHash = actualHash;
  160. }
  161. }
  162. long fileSize = new FileInfo(fileName).Length;
  163. Logger.Info?.Print(LogClass.Ptc, $"{(isBackup ? "Loaded Backup Profiling Info" : "Loaded Profiling Info")} (size: {fileSize} bytes, profiled functions: {ProfiledFuncs.Count}).");
  164. return true;
  165. }
  166. private static Dictionary<ulong, FuncProfile> Deserialize(Stream stream)
  167. {
  168. return DeserializeDictionary<ulong, FuncProfile>(stream, (stream) => DeserializeStructure<FuncProfile>(stream));
  169. }
  170. private static ReadOnlySpan<byte> GetReadOnlySpan(MemoryStream memoryStream)
  171. {
  172. return new(memoryStream.GetBuffer(), (int)memoryStream.Position, (int)memoryStream.Length - (int)memoryStream.Position);
  173. }
  174. private static void InvalidateCompressedStream(FileStream compressedStream)
  175. {
  176. compressedStream.SetLength(0L);
  177. }
  178. private static void PreSave(object source, System.Timers.ElapsedEventArgs e)
  179. {
  180. _waitEvent.Reset();
  181. string fileNameActual = string.Concat(Ptc.CachePathActual, ".info");
  182. string fileNameBackup = string.Concat(Ptc.CachePathBackup, ".info");
  183. FileInfo fileInfoActual = new FileInfo(fileNameActual);
  184. if (fileInfoActual.Exists && fileInfoActual.Length != 0L)
  185. {
  186. File.Copy(fileNameActual, fileNameBackup, true);
  187. }
  188. Save(fileNameActual);
  189. _waitEvent.Set();
  190. }
  191. private static void Save(string fileName)
  192. {
  193. int profiledFuncsCount;
  194. OuterHeader outerHeader = new OuterHeader();
  195. outerHeader.Magic = _outerHeaderMagic;
  196. outerHeader.InfoFileVersion = InternalVersion;
  197. outerHeader.Endianness = Ptc.GetEndianness();
  198. outerHeader.SetHeaderHash();
  199. using (MemoryStream stream = new MemoryStream())
  200. {
  201. Debug.Assert(stream.Seek(0L, SeekOrigin.Begin) == 0L && stream.Length == 0L);
  202. stream.Seek((long)Unsafe.SizeOf<Hash128>(), SeekOrigin.Begin);
  203. lock (_lock)
  204. {
  205. Serialize(stream, ProfiledFuncs);
  206. profiledFuncsCount = ProfiledFuncs.Count;
  207. }
  208. Debug.Assert(stream.Position == stream.Length);
  209. stream.Seek((long)Unsafe.SizeOf<Hash128>(), SeekOrigin.Begin);
  210. Hash128 hash = XXHash128.ComputeHash(GetReadOnlySpan(stream));
  211. stream.Seek(0L, SeekOrigin.Begin);
  212. SerializeStructure(stream, hash);
  213. if (hash == _lastHash)
  214. {
  215. return;
  216. }
  217. using (FileStream compressedStream = new(fileName, FileMode.OpenOrCreate))
  218. using (DeflateStream deflateStream = new(compressedStream, SaveCompressionLevel, true))
  219. {
  220. try
  221. {
  222. SerializeStructure(compressedStream, outerHeader);
  223. stream.WriteTo(deflateStream);
  224. _lastHash = hash;
  225. }
  226. catch
  227. {
  228. compressedStream.Position = 0L;
  229. _lastHash = default;
  230. }
  231. if (compressedStream.Position < compressedStream.Length)
  232. {
  233. compressedStream.SetLength(compressedStream.Position);
  234. }
  235. }
  236. }
  237. long fileSize = new FileInfo(fileName).Length;
  238. if (fileSize != 0L)
  239. {
  240. Logger.Info?.Print(LogClass.Ptc, $"Saved Profiling Info (size: {fileSize} bytes, profiled functions: {profiledFuncsCount}).");
  241. }
  242. }
  243. private static void Serialize(Stream stream, Dictionary<ulong, FuncProfile> profiledFuncs)
  244. {
  245. SerializeDictionary(stream, profiledFuncs, (stream, structure) => SerializeStructure(stream, structure));
  246. }
  247. [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 29*/)]
  248. private struct OuterHeader
  249. {
  250. public ulong Magic;
  251. public uint InfoFileVersion;
  252. public bool Endianness;
  253. public Hash128 HeaderHash;
  254. public void SetHeaderHash()
  255. {
  256. Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
  257. HeaderHash = XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader).Slice(0, Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>()));
  258. }
  259. public bool IsHeaderValid()
  260. {
  261. Span<OuterHeader> spanHeader = MemoryMarshal.CreateSpan(ref this, 1);
  262. return XXHash128.ComputeHash(MemoryMarshal.AsBytes(spanHeader).Slice(0, Unsafe.SizeOf<OuterHeader>() - Unsafe.SizeOf<Hash128>())) == HeaderHash;
  263. }
  264. }
  265. [StructLayout(LayoutKind.Sequential, Pack = 1/*, Size = 5*/)]
  266. internal struct FuncProfile
  267. {
  268. public ExecutionMode Mode;
  269. public bool HighCq;
  270. public FuncProfile(ExecutionMode mode, bool highCq)
  271. {
  272. Mode = mode;
  273. HighCq = highCq;
  274. }
  275. }
  276. internal static void Start()
  277. {
  278. if (Ptc.State == PtcState.Enabled ||
  279. Ptc.State == PtcState.Continuing)
  280. {
  281. Enabled = true;
  282. _timer.Enabled = true;
  283. }
  284. }
  285. public static void Stop()
  286. {
  287. Enabled = false;
  288. if (!_disposed)
  289. {
  290. _timer.Enabled = false;
  291. }
  292. }
  293. internal static void Wait()
  294. {
  295. _waitEvent.WaitOne();
  296. }
  297. public static void Dispose()
  298. {
  299. if (!_disposed)
  300. {
  301. _disposed = true;
  302. _timer.Elapsed -= PreSave;
  303. _timer.Dispose();
  304. Wait();
  305. _waitEvent.Dispose();
  306. }
  307. }
  308. }
  309. }