Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Function call performance improvements #703

Merged
merged 6 commits into from
Jan 20, 2020
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
88 changes: 13 additions & 75 deletions Jint.Benchmark/UncacheableExpressionsBenchmark.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,18 @@ public class UncacheableExpressionsBenchmark
private string targetObject;
private JsValue[] targetJsObject;

private const string script = @"
private const string NonArrowFunctionScript = @"
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 const string ArrowFunctionScript = @"
function output(d) {
var doc = d.SubDocuments.find(x => x.Id==='testing');
return { Id : d.Id, Deleted : d.Deleted, SubTestId : (doc!==null&&doc!==undefined)?doc.Id:null, Values : d.SubDocuments.map(x => ({ TargetId:x.TargetId,TargetValue:x.TargetValue,SubDocuments:x.SubDocuments.filter(s => (s!==null&&s!==undefined)).map(s => ({ TargetId: s.TargetId, TargetValue: s.TargetValue}))})) };
}
";

private Engine engine;
Expand Down Expand Up @@ -72,7 +79,7 @@ public void Setup()
}
}

CreateEngine();
CreateEngine(Arrow ? ArrowFunctionScript : NonArrowFunctionScript);
}

private static void InitializeEngine(Options options)
Expand All @@ -87,6 +94,9 @@ private static void InitializeEngine(Options options)
[Params(500)]
public int N { get; set; }

[Params(true, false)]
public bool Arrow { get; set; }

[Benchmark]
public void Benchmark()
{
Expand All @@ -97,84 +107,12 @@ public void Benchmark()
}
}

private void CreateEngine()
private void CreateEngine(string script)
{
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;
}
}
}

";
}
}
3 changes: 3 additions & 0 deletions Jint.Tests.Ecma/Jint.Tests.Ecma.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,4 +13,7 @@
<PackageReference Include="xunit.runner.console" Version="2.4.1" />
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
</ItemGroup>
<ItemGroup>
<None Remove="TestCases\ch*\**" />
</ItemGroup>
</Project>
4 changes: 4 additions & 0 deletions Jint.Tests.Test262/Jint.Tests.Test262.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -14,4 +14,8 @@
<PackageReference Include="xunit.runner.console" Version="2.4.1" />
<DotNetCliToolReference Include="dotnet-xunit" Version="2.3.1" />
</ItemGroup>
<ItemGroup>
<None Remove="harness\**" />
<None Remove="test\**" />
</ItemGroup>
</Project>
39 changes: 39 additions & 0 deletions Jint/Collections/DictionarySlim.Enumerator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
// Copyright (c) Ben A Adams. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections;
using System.Collections.Generic;
using Jint.Collections.Maps;

namespace Jint.Collections
{
internal partial class DictionarySlim<TValue>
{
public struct Enumerator : IEnumerator<KeyValuePair<Key, TValue>>
{
private readonly Map<TValue> _map;
private int index;
private KeyValuePair<Key, TValue> _current;

internal Enumerator(Map<TValue> map)
{
_map = map;
index = -1;
_current = default;
}

public KeyValuePair<Key, TValue> Current => _current;

public bool MoveNext() => _map.TryGetNext(ref index, out _current);

public void Dispose()
{
}

object IEnumerator.Current => _current;

void IEnumerator.Reset() => throw new NotSupportedException();
}
}
}
41 changes: 41 additions & 0 deletions Jint/Collections/DictionarySlim.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright (c) Ben A Adams. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Generic;
using Jint.Collections.Maps;

namespace Jint.Collections
{
internal partial class DictionarySlim<TValue>
{
private Map<TValue> _map = Map<TValue>.Empty;

public TValue this[Key key]
{
get => _map.TryGetValue(key, out var value) ? value : throw new KeyNotFoundException();
set => _map = _map.Set(key, value);
}

public ICollection<Key> Keys => _map.Keys;

public ICollection<TValue> Values => _map.Values;

public int Count => _map.Count;

public bool IsReadOnly => false;

public void Clear() => _map = Map<TValue>.Empty;

public bool ContainsKey(Key key) => _map.TryGetValue(key, out _);

public bool TryGetValue(Key key, out TValue value) => _map.TryGetValue(key, out value);

public bool Remove(Key key)
{
_map = _map.TryRemove(key, out var success);
return success;
}

public Enumerator GetEnumerator() => new Enumerator(_map);
}
}
46 changes: 46 additions & 0 deletions Jint/Collections/Maps/Map.Empty.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// Copyright (c) Ben A Adams. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System.Collections.Generic;

namespace Jint.Collections.Maps
{
internal abstract partial class Map<TValue>
{
// Instance without any key/value pairs. Used as a singleton.
private sealed class EmptyMap : Map<TValue>
{
public override int Count => 0;

public override Map<TValue> Set(Key key, TValue value)
{
// Create a new one-element map to store the key/value pair
return new OneElementKeyedMap(key, value);
}

public override bool TryGetValue(Key key, out TValue value)
{
// Nothing here
value = default;
return false;
}

public override Map<TValue> TryRemove(Key key, out bool success)
{
// Nothing to remove
success = false;
return this;
}

public override bool TryGetNext(ref int index, out KeyValuePair<Key, TValue> value)
{
value = default;
return false;
}

public override ICollection<Key> Keys => new Key[0];

public override ICollection<TValue> Values => new TValue[0];
}
}
}
118 changes: 118 additions & 0 deletions Jint/Collections/Maps/Map.Many.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
// Copyright (c) Ben A Adams. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.

using System;
using System.Collections.Generic;
using System.Diagnostics;

namespace Jint.Collections.Maps
{
internal abstract partial class Map<TValue>
{
// Instance with any number of key/value pairs.
private sealed class ManyElementKeyedMap : Map<TValue>
{
private readonly Dictionary<Key, TValue> _dictionary;

public override int Count => _dictionary.Count;

public ManyElementKeyedMap(int capacity)
{
_dictionary = new Dictionary<Key, TValue>(capacity);
}

public override Map<TValue> Set(Key key, TValue value)
{
_dictionary[key] = value;
return this;
}

public override Map<TValue> TryRemove(Key key, out bool success)
{
int count = _dictionary.Count;
// If the key is contained in this map, we're going to create a new map that's one pair smaller.
if (_dictionary.ContainsKey(key))
{
// If the new count would be within range of a multi map instead of a many map,
// downgrade to the many map, which uses less memory and is faster to access.
// Otherwise, just create a new many map that's missing this key.
if (count == MultiElementKeyedMap.MaxMultiElements + 1)
{
var multi = new MultiElementKeyedMap(MultiElementKeyedMap.MaxMultiElements);
int index = 0;
foreach (KeyValuePair<Key, TValue> pair in _dictionary)
{
if (key != pair.Key)
{
multi.UnsafeStore(index++, pair.Key, pair.Value);
}
}
Debug.Assert(index == MultiElementKeyedMap.MaxMultiElements);
success = true;
return multi;
}
else
{
var map = new ManyElementKeyedMap(count - 1);
foreach (KeyValuePair<Key, TValue> pair in _dictionary)
{
if (key != pair.Key)
{
map[pair.Key] = pair.Value;
}
}
Debug.Assert(_dictionary.Count == count - 1);
success = true;
return map;
}
}

// The key wasn't in the map, so there's nothing to change.
// Just return this instance.
success = false;
return this;
}

public override bool TryGetValue(Key key, out TValue value) => _dictionary.TryGetValue(key, out value);

public TValue this[Key key]
{
get => _dictionary[key];
set => _dictionary[key] = value;
}

public override bool TryGetNext(ref int index, out KeyValuePair<Key, TValue> value) => throw new NotSupportedException();

public override DictionarySlim<TValue>.Enumerator GetEnumerator() => new DictionarySlim<TValue>.Enumerator(new ManyElementKeyedMapEnumerator(this));


public override ICollection<Key> Keys => _dictionary.Keys;
public override ICollection<TValue> Values => _dictionary.Values;

private class ManyElementKeyedMapEnumerator : Map<TValue>
{
private Dictionary<Key, TValue>.Enumerator _enumerator;

public ManyElementKeyedMapEnumerator(ManyElementKeyedMap map)
{
_enumerator = map._dictionary.GetEnumerator();
}

public override bool TryGetNext(ref int index, out KeyValuePair<Key, TValue> value)
{
var success = _enumerator.MoveNext();
value = success ? _enumerator.Current : default;

return success;
}

public override int Count => throw new NotSupportedException();
public override Map<TValue> Set(Key key, TValue value) => throw new NotSupportedException();
public override bool TryGetValue(Key key, out TValue value) => throw new NotSupportedException();
public override Map<TValue> TryRemove(Key key, out bool success) => throw new NotSupportedException();
public override ICollection<Key> Keys => throw new NotSupportedException();
public override ICollection<TValue> Values => throw new NotSupportedException();
}
}
}
}