PtcProfiler.cs 13 KB

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