PtcProfiler.cs 13 KB

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