using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Diagnosers;
using BenchmarkDotNet.Jobs;
using Jint.Native;
using Newtonsoft.Json;
using Undefined = Jint.Native.Undefined;
namespace Jint.Benchmark
{
///
/// Test case for situation where object is projected via filter and map, Jint deems code as uncacheable.
///
[Config(typeof(Config))]
public class UncacheableExpressionsBenchmark
{
private class Config : ManualConfig
{
public Config()
{
Add(Job.MediumRun);
Add(MemoryDiagnoser.Default);
}
}
private Document doc;
private string targetObject;
private JsValue[] targetJsObject;
private const string script = @"
function output(d) {
var doc = d.SubDocuments.find(function(x){return x.Id==='testing';});
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};})};}) };
}
";
private Engine engine;
public class Document
{
public string Id { get; set; }
public string TargetId { get; set; }
public decimal TargetValue { get; set; }
public bool Deleted { get; set; }
public IEnumerable SubDocuments { get; set; }
}
[GlobalSetup]
public void Setup()
{
doc = new Document
{
Deleted = false,
SubDocuments = new List
{
new Document
{
TargetId = "id1",
SubDocuments = Enumerable.Range(1, 200).Select(x => new Document()).ToList()
},
new Document
{
TargetId = "id2",
SubDocuments = Enumerable.Range(1, 200).Select(x => new Document()).ToList()
}
}
};
using (var stream = new MemoryStream())
{
using (var writer = new StreamWriter(stream))
{
JsonSerializer.CreateDefault().Serialize(writer, doc);
writer.Flush();
var targetObjectJson = Encoding.UTF8.GetString(stream.ToArray());
targetObject = $"var d = {targetObjectJson};";
}
}
CreateEngine();
}
private static void InitializeEngine(Options options)
{
options
.LimitRecursion(64)
.MaxStatements(int.MaxValue)
.Strict()
.LocalTimeZone(TimeZoneInfo.Utc);
}
[Params(500)]
public int N { get; set; }
[Benchmark]
public void Benchmark()
{
var call = engine.GetValue("output").TryCast();
for (int i = 0; i < N; ++i)
{
call.Call(Undefined.Instance, targetJsObject);
}
}
private void CreateEngine()
{
engine = new Engine(InitializeEngine);
engine.Execute(Polyfills);
engine.Execute(script);
engine.Execute(targetObject);
targetJsObject = new[] {engine.GetValue("d")};
}
private const string Polyfills = @"
//https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/endsWith
if (!String.prototype.endsWith) {
String.prototype.endsWith = function (searchStr, position) {
if (!(position < this.length))
position = this.length;
else
position |= 0; // round position
return this.substr(position - searchStr.length,
searchStr.length) === searchStr;
};
}
//https://github.com/jsPolyfill/Array.prototype.find/blob/master/find.js
if (!Array.prototype.find) {
Array.prototype.find = Array.prototype.find || function(callback) {
if (this === null) {
throw new TypeError('Array.prototype.find called on null or undefined');
} else if (typeof callback !== 'function') {
throw new TypeError('callback must be a function');
}
var list = Object(this);
// Makes sures is always has an positive integer as length.
var length = list.length >>> 0;
var thisArg = arguments[1];
for (var i = 0; i < length; i++) {
var element = list[i];
if ( callback.call(thisArg, element, i, list) ) {
return element;
}
}
};
}
if (!Array.prototype.fastFilter) {
Array.prototype.fastFilter = function(callback) {
var results = [];
var item;
var len = this.length;
for (var i = 0, len = len; i < len; i++) {
item = this[i];
if (callback(item)) results.push(item);
}
return results;
}
}
if (!Array.prototype.fastMap) {
Array.prototype.fastMap = function(callback) {
var h = [];
var len = this.length;
for (var i = 0, len = len; i < len; i++) {
h.push(callback(this[i]));
}
return h;
}
}
if (!Array.prototype.fastFind) {
Array.prototype.fastFind = function(callback) {
var item;
var len = this.length;
for (var i = 0, len = len; i < len; i++) {
item = this[i];
if (callback(item)) return item;
}
}
}
";
}
}