Skip to content

Commit

Permalink
sebastienros#451 reduce memory allocations
Browse files Browse the repository at this point in the history
* remove LINQ from hot paths
* use List<T> where possible to get rid of memory allocations
* cache empty completions
  • Loading branch information
lahma committed Jan 2, 2018
1 parent aaf42cc commit 43a3b01
Show file tree
Hide file tree
Showing 14 changed files with 162 additions and 79 deletions.
2 changes: 1 addition & 1 deletion .gitignore
Expand Up @@ -160,4 +160,4 @@ project.lock.json
.build

.idea
BenchmarkDotNet.Artifacts
BenchmarkDotNet.Artifacts*
52 changes: 34 additions & 18 deletions Jint/Engine.cs
@@ -1,6 +1,5 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Esprima;
using Esprima.Ast;
using Jint.Native;
Expand Down Expand Up @@ -408,7 +407,7 @@ public Completion ExecuteStatement(Statement statement)
return _statements.ExecuteForInStatement(statement.As<ForInStatement>());

case Nodes.FunctionDeclaration:
return new Completion(Completion.Normal, null, null);
return Completion.Empty;

case Nodes.IfStatement:
return _statements.ExecuteIfStatement(statement.As<IfStatement>());
Expand Down Expand Up @@ -728,7 +727,12 @@ public JsValue Invoke(JsValue value, object thisObj, object[] arguments)
throw new ArgumentException("Can only invoke functions");
}

return callable.Call(JsValue.FromObject(this, thisObj), arguments.Select(x => JsValue.FromObject(this, x)).ToArray());
var items = new JsValue[arguments.Length];
for (int i = 0; i < arguments.Length; ++i)
{
items[i] = JsValue.FromObject(this, arguments[i]);
}
return callable.Call(JsValue.FromObject(this, thisObj), items);
}

/// <summary>
Expand Down Expand Up @@ -766,7 +770,12 @@ public JsValue GetValue(JsValue scope, string propertyName)
}

// http://www.ecma-international.org/ecma-262/5.1/#sec-10.5
public void DeclarationBindingInstantiation(DeclarationBindingType declarationBindingType, IEnumerable<FunctionDeclaration> functionDeclarations, IEnumerable<VariableDeclaration> variableDeclarations, FunctionInstance functionInstance, JsValue[] arguments)
public void DeclarationBindingInstantiation(
DeclarationBindingType declarationBindingType,
IList<FunctionDeclaration> functionDeclarations,
IList<VariableDeclaration> variableDeclarations,
FunctionInstance functionInstance,
JsValue[] arguments)
{
var env = ExecutionContext.VariableEnvironment.Record;
bool configurableBindings = declarationBindingType == DeclarationBindingType.EvalCode;
Expand All @@ -776,8 +785,9 @@ public void DeclarationBindingInstantiation(DeclarationBindingType declarationBi
{
var argCount = arguments.Length;
var n = 0;
foreach (var argName in functionInstance.FormalParameters)
for (var i = 0; i < functionInstance.FormalParameters.Length; i++)
{
var argName = functionInstance.FormalParameters[i];
n++;
var v = n > argCount ? Undefined.Instance : arguments[n - 1];
var argAlreadyDeclared = env.HasBinding(argName);
Expand All @@ -790,8 +800,9 @@ public void DeclarationBindingInstantiation(DeclarationBindingType declarationBi
}
}

foreach (var f in functionDeclarations)
for (var i = 0; i < functionDeclarations.Count; i++)
{
var f = functionDeclarations[i];
var fn = f.Id.Name;
var fo = Function.CreateFunctionObject(f);
var funcAlreadyDeclared = env.HasBinding(fn);
Expand All @@ -808,12 +819,12 @@ public void DeclarationBindingInstantiation(DeclarationBindingType declarationBi
if (existingProp.Configurable.Value)
{
go.DefineOwnProperty(fn,
new PropertyDescriptor(
value: Undefined.Instance,
writable: true,
enumerable: true,
configurable: configurableBindings
), true);
new PropertyDescriptor(
value: Undefined.Instance,
writable: true,
enumerable: true,
configurable: configurableBindings
), true);
}
else
{
Expand Down Expand Up @@ -854,14 +865,19 @@ public void DeclarationBindingInstantiation(DeclarationBindingType declarationBi
}

// process all variable declarations in the current parser scope
foreach (var d in variableDeclarations.SelectMany(x => x.Declarations))
for (var i = 0; i < variableDeclarations.Count; i++)
{
var dn = d.Id.As<Identifier>().Name;
var varAlreadyDeclared = env.HasBinding(dn);
if (!varAlreadyDeclared)
var variableDeclaration = variableDeclarations[i];
var declarations = (List<VariableDeclarator>) variableDeclaration.Declarations;
foreach (var d in declarations)
{
env.CreateMutableBinding(dn, configurableBindings);
env.SetMutableBinding(dn, Undefined.Instance, strict);
var dn = d.Id.As<Identifier>().Name;
var varAlreadyDeclared = env.HasBinding(dn);
if (!varAlreadyDeclared)
{
env.CreateMutableBinding(dn, configurableBindings);
env.SetMutableBinding(dn, Undefined.Instance, strict);
}
}
}
}
Expand Down
8 changes: 3 additions & 5 deletions Jint/Native/Function/FunctionPrototype.cs
@@ -1,6 +1,4 @@
using System.Collections.Generic;
using System.Linq;
using Jint.Native.Object;
using Jint.Native.Object;
using Jint.Runtime;
using Jint.Runtime.Descriptors;
using Jint.Runtime.Interop;
Expand Down Expand Up @@ -49,7 +47,7 @@ private JsValue Bind(JsValue thisObj, JsValue[] arguments)
var f = new BindFunctionInstance(Engine) {Extensible = true};
f.TargetFunction = thisObj;
f.BoundThis = thisArg;
f.BoundArgs = arguments.Skip(1).ToArray();
f.BoundArgs = arguments.Skip(1);
f.Prototype = Engine.Function.PrototypeObject;

var o = target as FunctionInstance;
Expand Down Expand Up @@ -126,7 +124,7 @@ public JsValue CallImpl(JsValue thisObject, JsValue[] arguments)
throw new JavaScriptException(Engine.TypeError);
}

return func.Call(arguments.At(0), arguments.Length == 0 ? arguments : arguments.Skip(1).ToArray());
return func.Call(arguments.At(0), arguments.Length == 0 ? arguments : arguments.Skip(1));
}

public override JsValue Call(JsValue thisObject, JsValue[] arguments)
Expand Down
19 changes: 17 additions & 2 deletions Jint/Native/Function/ScriptFunctionInstance.cs
@@ -1,4 +1,5 @@
using System.Linq;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using Esprima.Ast;
using Jint.Native.Object;
using Jint.Runtime;
Expand All @@ -22,7 +23,7 @@ public sealed class ScriptFunctionInstance : FunctionInstance, IConstructor
/// <param name="scope"></param>
/// <param name="strict"></param>
public ScriptFunctionInstance(Engine engine, IFunction functionDeclaration, LexicalEnvironment scope, bool strict)
: base(engine, functionDeclaration.Params.Select(x => x.As<Identifier>().Name).ToArray(), scope, strict)
: base(engine, GetParameterNames(functionDeclaration), scope, strict)
{
_functionDeclaration = functionDeclaration;

Expand All @@ -48,6 +49,20 @@ public ScriptFunctionInstance(Engine engine, IFunction functionDeclaration, Lexi
}
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
private static string[] GetParameterNames(IFunction functionDeclaration)
{
var list = (List<INode>) functionDeclaration.Params;
var count = list.Count;
var names = new string[count];
for (var i = 0; i < count; ++i)
{
names[i] = ((Identifier) list[i]).Name;
}

return names;
}

/// <summary>
/// http://www.ecma-international.org/ecma-262/5.1/#sec-13.2.1
/// </summary>
Expand Down
4 changes: 3 additions & 1 deletion Jint/Native/Number/NumberPrototype.cs
Expand Up @@ -12,6 +12,8 @@ namespace Jint.Native.Number
/// </summary>
public sealed class NumberPrototype : NumberInstance
{
private static readonly char[] _numberSeparators = {'.', 'e'};

private NumberPrototype(Engine engine)
: base(engine)
{
Expand Down Expand Up @@ -154,7 +156,7 @@ private JsValue ToPrecision(JsValue thisObj, JsValue[] arguments)

// Get the number of decimals
string str = x.ToString("e23", CultureInfo.InvariantCulture);
int decimals = str.IndexOfAny(new [] { '.', 'e' });
int decimals = str.IndexOfAny(_numberSeparators);
decimals = decimals == -1 ? str.Length : decimals;

p -= decimals;
Expand Down
2 changes: 0 additions & 2 deletions Jint/Native/Object/ObjectInstance.cs
@@ -1,8 +1,6 @@
using System.Collections.Generic;
using Jint.Runtime;
using Jint.Runtime.Descriptors;
using System;
using System.Collections.Specialized;

namespace Jint.Native.Object
{
Expand Down
9 changes: 5 additions & 4 deletions Jint/Native/String/StringPrototype.cs
Expand Up @@ -320,19 +320,20 @@ private JsValue Split(JsValue thisObj, JsValue[] arguments)
}
else
{
var segments = new List<string>();
List<string> segments;
var sep = TypeConverter.ToString(separator);

if (sep == string.Empty || (rx != null && rx.Source == regExpForMatchingAllCharactere)) // for s.split(new RegExp)
{
segments = new List<string>(s.Length);
foreach (var c in s)
{
segments.Add(c.ToString());
segments.Add(TypeConverter.ToString(c));
}
}
else
{
segments = s.Split(new[] {sep}, StringSplitOptions.None).ToList();
segments = new List<string>(s.Split(new[] {sep}, StringSplitOptions.None));
}

for (int i = 0; i < segments.Count && i < limit; i++)
Expand Down Expand Up @@ -731,7 +732,7 @@ private JsValue CharAt(JsValue thisObj, JsValue[] arguments)
{
return "";
}
return s[(int) position].ToString();
return TypeConverter.ToString(s[(int) position]);

}

Expand Down
22 changes: 20 additions & 2 deletions Jint/Runtime/Arguments.cs
@@ -1,4 +1,6 @@
using Jint.Native;
using System;
using System.Runtime.CompilerServices;
using Jint.Native;

namespace Jint.Runtime
{
Expand All @@ -18,14 +20,30 @@ public static JsValue[] From(params JsValue[] o)
/// <param name="index">The index of the parameter to return</param>
/// <param name="undefinedValue">The value to return is the parameter is not provided</param>
/// <returns></returns>
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static JsValue At(this JsValue[] args, int index, JsValue undefinedValue)
{
return args.Length > index ? args[index] : undefinedValue;
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static JsValue At(this JsValue[] args, int index)
{
return At(args, index, Undefined.Instance);
}

[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static JsValue[] Skip(this JsValue[] args, int count)
{
var newLength = args.Length - count;
if (newLength <= 0)
{
return Array.Empty<JsValue>();
}

var array = new JsValue[newLength];
Array.Copy(args, count, array, 0, newLength);
return array;
}
}
}
}
19 changes: 11 additions & 8 deletions Jint/Runtime/Completion.cs
Expand Up @@ -8,11 +8,14 @@ namespace Jint.Runtime
/// </summary>
public class Completion
{
public static string Normal = "normal";
public static string Break = "break";
public static string Continue = "continue";
public static string Return = "return";
public static string Throw = "throw";
public const string Normal = "normal";
public const string Break = "break";
public const string Continue = "continue";
public const string Return = "return";
public const string Throw = "throw";

public static readonly Completion Empty = new Completion(Normal, null, null);
public static readonly Completion EmptyUndefined = new Completion(Normal, Undefined.Instance, null);

public Completion(string type, JsValue value, string identifier)
{
Expand All @@ -21,9 +24,9 @@ public Completion(string type, JsValue value, string identifier)
Identifier = identifier;
}

public string Type { get; private set; }
public JsValue Value { get; private set; }
public string Identifier { get; private set; }
public string Type { get; }
public JsValue Value { get; }
public string Identifier { get; }

public JsValue GetValueOrDefault()
{
Expand Down
6 changes: 3 additions & 3 deletions Jint/Runtime/Environments/DeclarativeEnvironmentRecord.cs
Expand Up @@ -11,7 +11,7 @@ namespace Jint.Runtime.Environments
public sealed class DeclarativeEnvironmentRecord : EnvironmentRecord
{
private readonly Engine _engine;
private readonly IDictionary<string, Binding> _bindings = new Dictionary<string, Binding>();
private readonly Dictionary<string, Binding> _bindings = new Dictionary<string, Binding>();

public DeclarativeEnvironmentRecord(Engine engine) : base(engine)
{
Expand All @@ -27,7 +27,7 @@ public override void CreateMutableBinding(string name, bool canBeDeleted = false
{
_bindings.Add(name, new Binding
{
Value = Undefined.Instance,
Value = Undefined.Instance,
CanBeDeleted = canBeDeleted,
Mutable = true
});
Expand Down Expand Up @@ -80,7 +80,7 @@ public override bool DeleteBinding(string name)
}

_bindings.Remove(name);

return true;
}

Expand Down

0 comments on commit 43a3b01

Please sign in to comment.