UncacheableExpressionsBenchmark.cs 5.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Text;
  6. using BenchmarkDotNet.Attributes;
  7. using Jint.Native;
  8. using Newtonsoft.Json;
  9. using Undefined = Jint.Native.Undefined;
  10. namespace Jint.Benchmark
  11. {
  12. /// <summary>
  13. /// Test case for situation where object is projected via filter and map, Jint deems code as uncacheable.
  14. /// </summary>
  15. [MemoryDiagnoser]
  16. public class UncacheableExpressionsBenchmark
  17. {
  18. private Document doc;
  19. private string targetObject;
  20. private JsValue[] targetJsObject;
  21. private const string script = @"
  22. function output(d) {
  23. var doc = d.SubDocuments.find(function(x){return x.Id==='testing';});
  24. return { Id : d.Id, Deleted : d.Deleted, SubTestId : (doc!==null&&doc!==undefined)?doc.Id:null, Values : d.SubDocuments.map(function(x){return {TargetId:x.TargetId,TargetValue:x.TargetValue,SubDocuments:x.SubDocuments.filter(function(s){return (s!==null&&s!==undefined);}).map(function(s){return {TargetId:s.TargetId,TargetValue:s.TargetValue};})};}) };
  25. }
  26. ";
  27. private Engine engine;
  28. public class Document
  29. {
  30. public string Id { get; set; }
  31. public string TargetId { get; set; }
  32. public decimal TargetValue { get; set; }
  33. public bool Deleted { get; set; }
  34. public IEnumerable<Document> SubDocuments { get; set; }
  35. }
  36. [GlobalSetup]
  37. public void Setup()
  38. {
  39. doc = new Document
  40. {
  41. Deleted = false,
  42. SubDocuments = new List<Document>
  43. {
  44. new Document
  45. {
  46. TargetId = "id1",
  47. SubDocuments = Enumerable.Range(1, 200).Select(x => new Document()).ToList()
  48. },
  49. new Document
  50. {
  51. TargetId = "id2",
  52. SubDocuments = Enumerable.Range(1, 200).Select(x => new Document()).ToList()
  53. }
  54. }
  55. };
  56. using (var stream = new MemoryStream())
  57. {
  58. using (var writer = new StreamWriter(stream))
  59. {
  60. JsonSerializer.CreateDefault().Serialize(writer, doc);
  61. writer.Flush();
  62. var targetObjectJson = Encoding.UTF8.GetString(stream.ToArray());
  63. targetObject = $"var d = {targetObjectJson};";
  64. }
  65. }
  66. CreateEngine();
  67. }
  68. private static void InitializeEngine(Options options)
  69. {
  70. options
  71. .LimitRecursion(64)
  72. .MaxStatements(int.MaxValue)
  73. .Strict()
  74. .LocalTimeZone(TimeZoneInfo.Utc);
  75. }
  76. [Params(500)]
  77. public int N { get; set; }
  78. [Benchmark]
  79. public void Benchmark()
  80. {
  81. var call = engine.GetValue("output").TryCast<ICallable>();
  82. for (int i = 0; i < N; ++i)
  83. {
  84. call.Call(Undefined.Instance, targetJsObject);
  85. }
  86. }
  87. private void CreateEngine()
  88. {
  89. engine = new Engine(InitializeEngine);
  90. engine.Execute(Polyfills);
  91. engine.Execute(script);
  92. engine.Execute(targetObject);
  93. targetJsObject = new[] {engine.GetValue("d")};
  94. }
  95. private const string Polyfills = @"
  96. //https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith
  97. if (!String.prototype.endsWith) {
  98. String.prototype.endsWith = function (searchStr, position) {
  99. if (!(position < this.length))
  100. position = this.length;
  101. else
  102. position |= 0; // round position
  103. return this.substr(position - searchStr.length,
  104. searchStr.length) === searchStr;
  105. };
  106. }
  107. //https://github.com/jsPolyfill/Array.prototype.find/blob/master/find.js
  108. if (!Array.prototype.find) {
  109. Array.prototype.find = Array.prototype.find || function(callback) {
  110. if (this === null) {
  111. throw new TypeError('Array.prototype.find called on null or undefined');
  112. } else if (typeof callback !== 'function') {
  113. throw new TypeError('callback must be a function');
  114. }
  115. var list = Object(this);
  116. // Makes sures is always has an positive integer as length.
  117. var length = list.length >>> 0;
  118. var thisArg = arguments[1];
  119. for (var i = 0; i < length; i++) {
  120. var element = list[i];
  121. if ( callback.call(thisArg, element, i, list) ) {
  122. return element;
  123. }
  124. }
  125. };
  126. }
  127. if (!Array.prototype.fastFilter) {
  128. Array.prototype.fastFilter = function(callback) {
  129. var results = [];
  130. var item;
  131. var len = this.length;
  132. for (var i = 0, len = len; i < len; i++) {
  133. item = this[i];
  134. if (callback(item)) results.push(item);
  135. }
  136. return results;
  137. }
  138. }
  139. if (!Array.prototype.fastMap) {
  140. Array.prototype.fastMap = function(callback) {
  141. var h = [];
  142. var len = this.length;
  143. for (var i = 0, len = len; i < len; i++) {
  144. h.push(callback(this[i]));
  145. }
  146. return h;
  147. }
  148. }
  149. if (!Array.prototype.fastFind) {
  150. Array.prototype.fastFind = function(callback) {
  151. var item;
  152. var len = this.length;
  153. for (var i = 0, len = len; i < len; i++) {
  154. item = this[i];
  155. if (callback(item)) return item;
  156. }
  157. }
  158. }
  159. ";
  160. }
  161. }