UncacheableExpressionsBenchmark.cs 5.8 KB

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