Browse Source

#451 fix string.split and array.join performance (#462)

* String.split should not to create new list
* Array.join should use StringBuilder
* add dromaeo benchmarks
Marko Lahma 7 years ago
parent
commit
a3c5cb18bf

+ 74 - 0
Jint.Benchmark/DromaeoBenchmark.cs

@@ -0,0 +1,74 @@
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Linq;
+using BenchmarkDotNet.Attributes;
+using BenchmarkDotNet.Configs;
+using BenchmarkDotNet.Jobs;
+using Jint;
+
+namespace Esprima.Benchmark
+{
+    [Config(typeof(Config))]
+    [MemoryDiagnoser]
+    public class DromaeoBenchmark
+    {
+        private class Config : ManualConfig
+        {
+            public Config()
+            {
+                // if Jint array performance gets better we can go towards defaul 16/16
+                Add(Job.ShortRun.WithInvocationCount(4).WithUnrollFactor(4));
+            }
+        }
+
+        private static readonly Dictionary<string, string> files = new Dictionary<string, string>
+        {
+            {"dromaeo-3d-cube", null},
+            {"dromaeo-core-eval", null},
+            {"dromaeo-object-array", null},
+            {"dromaeo-object-regexp", null},
+            {"dromaeo-object-string", null},
+            {"dromaeo-string-base64", null}
+        };
+
+        private Engine engine;
+
+        [GlobalSetup]
+        public void Setup()
+        {
+            foreach (var fileName in files.Keys.ToList())
+            {
+                files[fileName] = File.ReadAllText($"Scripts/dromaeo/{fileName}.js");
+            }
+
+            engine = new Engine()
+                .SetValue("log", new Action<object>(Console.WriteLine))
+                .SetValue("assert", new Action<bool>(b => { }));
+
+            engine.Execute(@"
+var startTest = function () { };
+var test = function (name, fn) { fn(); };
+var endTest = function () { };
+var prep = function (fn) { fn(); };
+");
+        }
+
+        [ParamsSource(nameof(FileNames))]
+        public string FileName { get; set; }
+
+        public IEnumerable<string> FileNames()
+        {
+            foreach (var entry in files)
+            {
+                yield return entry.Key;
+            }
+        }
+
+        [Benchmark]
+        public void Run()
+        {
+            engine.Execute(files[FileName]);
+        }
+    }
+}

+ 1 - 0
Jint.Benchmark/Jint.Benchmark.csproj

@@ -14,6 +14,7 @@
     <GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
     <GenerateAssemblyFileVersionAttribute>false</GenerateAssemblyFileVersionAttribute>
   </PropertyGroup>
   </PropertyGroup>
   <ItemGroup>
   <ItemGroup>
+    <None Include=".\Scripts\**" CopyToOutputDirectory="PreserveNewest" />
     <None Include="..\Jint.Tests.CommonScripts\Scripts\**" CopyToOutputDirectory="PreserveNewest" LinkBase="SunSpider" />
     <None Include="..\Jint.Tests.CommonScripts\Scripts\**" CopyToOutputDirectory="PreserveNewest" LinkBase="SunSpider" />
   </ItemGroup>
   </ItemGroup>
   <ItemGroup>
   <ItemGroup>

+ 332 - 0
Jint.Benchmark/Scripts/dromaeo/dromaeo-3d-cube.js

@@ -0,0 +1,332 @@
+// 3D Cube Rotation
+// http://www.speich.net/computer/moztesting/3d.htm
+// Created by Simon Speich
+
+var Q = new Array();
+var MTrans = new Array();	// transformation matrix
+var MQube = new Array();	// position information of qube
+var I = new Array();			// entity matrix
+var Origin = new Object();
+var Testing = new Object();
+var LoopTimer;
+
+var DisplArea = new Object();
+DisplArea.Width = 300;
+DisplArea.Height = 300;
+
+function DrawLine(From, To) {
+    var x1 = From.V[0];
+    var x2 = To.V[0];
+    var y1 = From.V[1];
+    var y2 = To.V[1];
+    var dx = Math.abs(x2 - x1);
+    var dy = Math.abs(y2 - y1);
+    var x = x1;
+    var y = y1;
+    var IncX1, IncY1;
+    var IncX2, IncY2;
+    var Den;
+    var Num;
+    var NumAdd;
+    var NumPix;
+
+    if (x2 >= x1) { IncX1 = 1; IncX2 = 1; }
+    else { IncX1 = -1; IncX2 = -1; }
+    if (y2 >= y1) { IncY1 = 1; IncY2 = 1; }
+    else { IncY1 = -1; IncY2 = -1; }
+    if (dx >= dy) {
+        IncX1 = 0;
+        IncY2 = 0;
+        Den = dx;
+        Num = dx / 2;
+        NumAdd = dy;
+        NumPix = dx;
+    }
+    else {
+        IncX2 = 0;
+        IncY1 = 0;
+        Den = dy;
+        Num = dy / 2;
+        NumAdd = dx;
+        NumPix = dy;
+    }
+
+    NumPix = Math.round(Q.LastPx + NumPix);
+
+    var i = Q.LastPx;
+    for (; i < NumPix; i++) {
+        Num += NumAdd;
+        if (Num >= Den) {
+            Num -= Den;
+            x += IncX1;
+            y += IncY1;
+        }
+        x += IncX2;
+        y += IncY2;
+    }
+    Q.LastPx = NumPix;
+}
+
+function CalcCross(V0, V1) {
+    var Cross = new Array();
+    Cross[0] = V0[1] * V1[2] - V0[2] * V1[1];
+    Cross[1] = V0[2] * V1[0] - V0[0] * V1[2];
+    Cross[2] = V0[0] * V1[1] - V0[1] * V1[0];
+    return Cross;
+}
+
+function CalcNormal(V0, V1, V2) {
+    var A = new Array(); var B = new Array();
+    for (var i = 0; i < 3; i++) {
+        A[i] = V0[i] - V1[i];
+        B[i] = V2[i] - V1[i];
+    }
+    A = CalcCross(A, B);
+    var Length = Math.sqrt(A[0] * A[0] + A[1] * A[1] + A[2] * A[2]);
+    for (var i = 0; i < 3; i++) A[i] = A[i] / Length;
+    A[3] = 1;
+    return A;
+}
+
+function CreateP(X, Y, Z) {
+    this.V = [X, Y, Z, 1];
+}
+
+// mulitplies two matrices
+function MMulti(M1, M2) {
+    var M = [[], [], [], []];
+    var i = 0;
+    var j = 0;
+    for (; i < 4; i++) {
+        j = 0;
+        for (; j < 4; j++) M[i][j] = M1[i][0] * M2[0][j] + M1[i][1] * M2[1][j] + M1[i][2] * M2[2][j] + M1[i][3] * M2[3][j];
+    }
+    return M;
+}
+
+//multiplies matrix with vector
+function VMulti(M, V) {
+    var Vect = new Array();
+    var i = 0;
+    for (; i < 4; i++) Vect[i] = M[i][0] * V[0] + M[i][1] * V[1] + M[i][2] * V[2] + M[i][3] * V[3];
+    return Vect;
+}
+
+function VMulti2(M, V) {
+    var Vect = new Array();
+    var i = 0;
+    for (; i < 3; i++) Vect[i] = M[i][0] * V[0] + M[i][1] * V[1] + M[i][2] * V[2];
+    return Vect;
+}
+
+// add to matrices
+function MAdd(M1, M2) {
+    var M = [[], [], [], []];
+    var i = 0;
+    var j = 0;
+    for (; i < 4; i++) {
+        j = 0;
+        for (; j < 4; j++) M[i][j] = M1[i][j] + M2[i][j];
+    }
+    return M;
+}
+
+function Translate(M, Dx, Dy, Dz) {
+    var T = [
+	[1, 0, 0, Dx],
+	[0, 1, 0, Dy],
+	[0, 0, 1, Dz],
+	[0, 0, 0, 1]
+    ];
+    return MMulti(T, M);
+}
+
+function RotateX(M, Phi) {
+    var a = Phi;
+    a *= Math.PI / 180;
+    var Cos = Math.cos(a);
+    var Sin = Math.sin(a);
+    var R = [
+	[1, 0, 0, 0],
+	[0, Cos, -Sin, 0],
+	[0, Sin, Cos, 0],
+	[0, 0, 0, 1]
+    ];
+    return MMulti(R, M);
+}
+
+function RotateY(M, Phi) {
+    var a = Phi;
+    a *= Math.PI / 180;
+    var Cos = Math.cos(a);
+    var Sin = Math.sin(a);
+    var R = [
+	[Cos, 0, Sin, 0],
+	[0, 1, 0, 0],
+	[-Sin, 0, Cos, 0],
+	[0, 0, 0, 1]
+    ];
+    return MMulti(R, M);
+}
+
+function RotateZ(M, Phi) {
+    var a = Phi;
+    a *= Math.PI / 180;
+    var Cos = Math.cos(a);
+    var Sin = Math.sin(a);
+    var R = [
+	[Cos, -Sin, 0, 0],
+	[Sin, Cos, 0, 0],
+	[0, 0, 1, 0],
+	[0, 0, 0, 1]
+    ];
+    return MMulti(R, M);
+}
+
+function DrawQube() {
+    // calc current normals
+    var CurN = new Array();
+    var i = 5;
+    Q.LastPx = 0;
+    for (; i > -1; i--) CurN[i] = VMulti2(MQube, Q.Normal[i]);
+    if (CurN[0][2] < 0) {
+        if (!Q.Line[0]) { DrawLine(Q[0], Q[1]); Q.Line[0] = true; };
+        if (!Q.Line[1]) { DrawLine(Q[1], Q[2]); Q.Line[1] = true; };
+        if (!Q.Line[2]) { DrawLine(Q[2], Q[3]); Q.Line[2] = true; };
+        if (!Q.Line[3]) { DrawLine(Q[3], Q[0]); Q.Line[3] = true; };
+    }
+    if (CurN[1][2] < 0) {
+        if (!Q.Line[2]) { DrawLine(Q[3], Q[2]); Q.Line[2] = true; };
+        if (!Q.Line[9]) { DrawLine(Q[2], Q[6]); Q.Line[9] = true; };
+        if (!Q.Line[6]) { DrawLine(Q[6], Q[7]); Q.Line[6] = true; };
+        if (!Q.Line[10]) { DrawLine(Q[7], Q[3]); Q.Line[10] = true; };
+    }
+    if (CurN[2][2] < 0) {
+        if (!Q.Line[4]) { DrawLine(Q[4], Q[5]); Q.Line[4] = true; };
+        if (!Q.Line[5]) { DrawLine(Q[5], Q[6]); Q.Line[5] = true; };
+        if (!Q.Line[6]) { DrawLine(Q[6], Q[7]); Q.Line[6] = true; };
+        if (!Q.Line[7]) { DrawLine(Q[7], Q[4]); Q.Line[7] = true; };
+    }
+    if (CurN[3][2] < 0) {
+        if (!Q.Line[4]) { DrawLine(Q[4], Q[5]); Q.Line[4] = true; };
+        if (!Q.Line[8]) { DrawLine(Q[5], Q[1]); Q.Line[8] = true; };
+        if (!Q.Line[0]) { DrawLine(Q[1], Q[0]); Q.Line[0] = true; };
+        if (!Q.Line[11]) { DrawLine(Q[0], Q[4]); Q.Line[11] = true; };
+    }
+    if (CurN[4][2] < 0) {
+        if (!Q.Line[11]) { DrawLine(Q[4], Q[0]); Q.Line[11] = true; };
+        if (!Q.Line[3]) { DrawLine(Q[0], Q[3]); Q.Line[3] = true; };
+        if (!Q.Line[10]) { DrawLine(Q[3], Q[7]); Q.Line[10] = true; };
+        if (!Q.Line[7]) { DrawLine(Q[7], Q[4]); Q.Line[7] = true; };
+    }
+    if (CurN[5][2] < 0) {
+        if (!Q.Line[8]) { DrawLine(Q[1], Q[5]); Q.Line[8] = true; };
+        if (!Q.Line[5]) { DrawLine(Q[5], Q[6]); Q.Line[5] = true; };
+        if (!Q.Line[9]) { DrawLine(Q[6], Q[2]); Q.Line[9] = true; };
+        if (!Q.Line[1]) { DrawLine(Q[2], Q[1]); Q.Line[1] = true; };
+    }
+    Q.Line = [false, false, false, false, false, false, false, false, false, false, false, false];
+    Q.LastPx = 0;
+}
+
+function Loop() {
+    if (Testing.LoopCount > Testing.LoopMax) return;
+    var TestingStr = String(Testing.LoopCount);
+    while (TestingStr.length < 3) TestingStr = "0" + TestingStr;
+    MTrans = Translate(I, -Q[8].V[0], -Q[8].V[1], -Q[8].V[2]);
+    MTrans = RotateX(MTrans, 1);
+    MTrans = RotateY(MTrans, 3);
+    MTrans = RotateZ(MTrans, 5);
+    MTrans = Translate(MTrans, Q[8].V[0], Q[8].V[1], Q[8].V[2]);
+    MQube = MMulti(MTrans, MQube);
+    var i = 8;
+    for (; i > -1; i--) {
+        Q[i].V = VMulti(MTrans, Q[i].V);
+    }
+    DrawQube();
+    Testing.LoopCount++;
+    Loop();
+}
+
+function Init(CubeSize) {
+    // init/reset vars
+    Origin.V = [150, 150, 20, 1];
+    Testing.LoopCount = 0;
+    Testing.LoopMax = 50;
+    Testing.TimeMax = 0;
+    Testing.TimeAvg = 0;
+    Testing.TimeMin = 0;
+    Testing.TimeTemp = 0;
+    Testing.TimeTotal = 0;
+    Testing.Init = false;
+
+    // transformation matrix
+    MTrans = [
+	[1, 0, 0, 0],
+	[0, 1, 0, 0],
+	[0, 0, 1, 0],
+	[0, 0, 0, 1]
+    ];
+
+    // position information of qube
+    MQube = [
+	[1, 0, 0, 0],
+	[0, 1, 0, 0],
+	[0, 0, 1, 0],
+	[0, 0, 0, 1]
+    ];
+
+    // entity matrix
+    I = [
+	[1, 0, 0, 0],
+	[0, 1, 0, 0],
+	[0, 0, 1, 0],
+	[0, 0, 0, 1]
+    ];
+
+    // create qube
+    Q[0] = new CreateP(-CubeSize, -CubeSize, CubeSize);
+    Q[1] = new CreateP(-CubeSize, CubeSize, CubeSize);
+    Q[2] = new CreateP(CubeSize, CubeSize, CubeSize);
+    Q[3] = new CreateP(CubeSize, -CubeSize, CubeSize);
+    Q[4] = new CreateP(-CubeSize, -CubeSize, -CubeSize);
+    Q[5] = new CreateP(-CubeSize, CubeSize, -CubeSize);
+    Q[6] = new CreateP(CubeSize, CubeSize, -CubeSize);
+    Q[7] = new CreateP(CubeSize, -CubeSize, -CubeSize);
+
+    // center of gravity
+    Q[8] = new CreateP(0, 0, 0);
+
+    // anti-clockwise edge check
+    Q.Edge = [[0, 1, 2], [3, 2, 6], [7, 6, 5], [4, 5, 1], [4, 0, 3], [1, 5, 6]];
+
+    // calculate squad normals
+    Q.Normal = new Array();
+    for (var i = 0; i < Q.Edge.length; i++) Q.Normal[i] = CalcNormal(Q[Q.Edge[i][0]].V, Q[Q.Edge[i][1]].V, Q[Q.Edge[i][2]].V);
+
+    // line drawn ?
+    Q.Line = [false, false, false, false, false, false, false, false, false, false, false, false];
+
+    // create line pixels
+    Q.NumPx = 9 * 2 * CubeSize;
+    for (var i = 0; i < Q.NumPx; i++) new CreateP(0, 0, 0);
+
+    MTrans = Translate(MTrans, Origin.V[0], Origin.V[1], Origin.V[2]);
+    MQube = MMulti(MTrans, MQube);
+
+    var i = 0;
+    for (; i < 9; i++) {
+        Q[i].V = VMulti(MTrans, Q[i].V);
+    }
+    DrawQube();
+    Testing.Init = true;
+    Loop();
+}
+
+startTest("dromaeo-3d-cube", '979cd0f1');
+
+test("Rotate 3D Cube", function () {
+    Init(20);
+});
+
+endTest();

+ 27 - 0
Jint.Benchmark/Scripts/dromaeo/dromaeo-core-eval.js

@@ -0,0 +1,27 @@
+startTest("dromaeo-core-eval", 'efec1da2');
+
+// Try to force real results
+var ret, tmp;
+
+// The commands that we'll be evaling
+var cmd = 'var str="";for(var i=0;i<1000;i++){str += "a";}ret = str;';
+
+// TESTS: eval()
+var num = 4;
+
+prep(function () {
+    tmp = cmd;
+
+    for (var n = 0; n < num; n++)
+        tmp += tmp;
+});
+
+test("Normal eval", function () {
+    eval(tmp);
+});
+
+test("new Function", function () {
+    (new Function(tmp))();
+});
+
+endTest();

+ 56 - 0
Jint.Benchmark/Scripts/dromaeo/dromaeo-object-array.js

@@ -0,0 +1,56 @@
+startTest("dromaeo-object-array", 'bde4f5f4');
+
+var ret = [], tmp, num = 500;
+var i = 1024;
+
+// TESTS: Array Building
+
+test("Array Construction, []", function () {
+    for (var j = 0; j < i * 15; j++) {
+        ret = [];
+        ret.length = i;
+    }
+});
+
+test("Array Construction, new Array()", function () {
+    for (var j = 0; j < i * 10; j++)
+        ret = new Array(i);
+});
+
+test("Array Construction, unshift", function () {
+    ret = [];
+    for (var j = 0; j < i; j++)
+        ret.unshift(j);
+});
+
+test("Array Construction, splice", function () {
+    ret = [];
+    for (var j = 0; j < i; j++)
+        ret.splice(0, 0, j);
+});
+
+test("Array Deconstruction, shift", function () {
+    var a = ret.slice();
+    for (var j = 0; j < i; j++)
+        tmp = a.shift();
+});
+
+test("Array Deconstruction, splice", function () {
+    var a = ret.slice();
+    for (var j = 0; j < i; j++)
+        tmp = a.splice(0, 1);
+});
+
+test("Array Construction, push", function () {
+    ret = [];
+    for (var j = 0; j < i * 25; j++)
+        ret.push(j);
+});
+
+test("Array Deconstruction, pop", function () {
+    var a = ret.slice();
+    for (var j = 0; j < i * 25; j++)
+        tmp = a.pop();
+});
+
+endTest();

+ 375 - 0
Jint.Benchmark/Scripts/dromaeo/dromaeo-object-regexp.js

@@ -0,0 +1,375 @@
+startTest("dromaeo-object-regexp", '812dde38');
+
+// Try to force real results
+var str = [], tmp, ret, re, testStrings = [];
+var i = 65536;
+
+function randomChar() {
+    return String.fromCharCode((25 * Math.random()) + 97);
+}
+
+for (var i = 0; i < 16384; i++)
+    str.push(randomChar());
+
+str = str.join("");
+str += str;
+str += str;
+
+function generateTestStrings(count) {
+    var t, nest;
+    if (testStrings.length >= count)
+        return testStrings.slice(0, count);
+    for (var i = testStrings.length; i < count; i++) {
+        // Make all tested strings different
+        t = randomChar() + str + randomChar();
+        nest = Math.floor(4 * Math.random());
+        for (var j = 0; j < nest; j++) {
+            t = randomChar() + t + randomChar();
+        }
+        // Try to minimize benchmark order dependencies by
+        // exercising the strings
+        for (var j = 0; j < t.length; j += 100) {
+            ret = t[j];
+            ret = t.substring(j, j + 100);
+        }
+        testStrings[i] = t;
+    }
+    return testStrings;
+}
+
+// TESTS: split
+
+prep(function () {
+    // It's impossible to specify empty regexp by simply
+    // using two slashes as this will be interpreted as a
+    // comment start. See note to ECMA-262 5th 7.8.5.
+    re = /(?:)/;
+    tmp = generateTestStrings(30);
+});
+
+test("Compiled Object Empty Split", function () {
+    for (var i = 0; i < 30; i++)
+        ret = tmp[i].split(re);
+});
+
+prep(function () {
+    re = /a/;
+    tmp = generateTestStrings(30);
+});
+
+test("Compiled Object Char Split", function () {
+    for (var i = 0; i < 30; i++)
+        ret = tmp[i].split(re);
+});
+
+prep(function () {
+    re = /.*/;
+    tmp = generateTestStrings(100);
+});
+
+test("Compiled Object Variable Split", function () {
+    for (var i = 0; i < 100; i++)
+        ret = tmp[i].split(re);
+});
+
+// TESTS: Compiled RegExps
+
+prep(function () {
+    re = /aaaaaaaaaa/g;
+    tmp = generateTestStrings(100);
+});
+
+test("Compiled Match", function () {
+    for (var i = 0; i < 100; i++)
+        ret = tmp[i].match(re);
+});
+
+prep(function () {
+    tmp = generateTestStrings(100);
+});
+
+test("Compiled Test", function () {
+    for (var i = 0; i < 100; i++)
+        ret = re.test(tmp[i]);
+});
+
+prep(function () {
+    tmp = generateTestStrings(100);
+});
+
+test("Compiled Empty Replace", function () {
+    for (var i = 0; i < 50; i++)
+        ret = tmp[i].replace(re, "");
+});
+
+prep(function () {
+    tmp = generateTestStrings(50);
+});
+
+test("Compiled 12 Char Replace", function () {
+    for (var i = 0; i < 50; i++)
+        ret = tmp[i].replace(re, "asdfasdfasdf");
+});
+
+prep(function () {
+    re = new RegExp("aaaaaaaaaa", "g");
+    tmp = generateTestStrings(100);
+});
+
+test("Compiled Object Match", function () {
+    for (var i = 0; i < 100; i++)
+        ret = tmp[i].match(re);
+});
+
+prep(function () {
+    tmp = generateTestStrings(100);
+});
+
+test("Compiled Object Test", function () {
+    for (var i = 0; i < 100; i++)
+        ret = re.test(tmp[i]);
+});
+
+prep(function () {
+    tmp = generateTestStrings(50);
+});
+
+test("Compiled Object Empty Replace", function () {
+    for (var i = 0; i < 50; i++)
+        ret = tmp[i].replace(re, "");
+});
+
+prep(function () {
+    tmp = generateTestStrings(50);
+});
+
+test("Compiled Object 12 Char Replace", function () {
+    for (var i = 0; i < 50; i++)
+        ret = tmp[i].replace(re, "asdfasdfasdf");
+});
+
+prep(function () {
+    tmp = generateTestStrings(50);
+});
+
+test("Compiled Object 12 Char Replace Function", function () {
+    for (var i = 0; i < 50; i++)
+        ret = tmp[i].replace(re, function (all) {
+            return "asdfasdfasdf";
+        });
+});
+
+// TESTS: Variable Length
+
+prep(function () {
+    re = /a.*a/;
+    tmp = generateTestStrings(100);
+});
+
+test("Compiled Variable Match", function () {
+    for (var i = 0; i < 100; i++)
+        ret = tmp[i].match(re);
+});
+
+prep(function () {
+    tmp = generateTestStrings(100);
+});
+
+test("Compiled Variable Test", function () {
+    for (var i = 0; i < 100; i++)
+        ret = re.test(tmp[i]);
+});
+
+prep(function () {
+    tmp = generateTestStrings(50);
+});
+
+test("Compiled Variable Empty Replace", function () {
+    for (var i = 0; i < 50; i++)
+        ret = tmp[i].replace(re, "");
+});
+
+prep(function () {
+    tmp = generateTestStrings(50);
+});
+
+test("Compiled Variable 12 Char Replace", function () {
+    for (var i = 0; i < 50; i++)
+        ret = tmp[i].replace(re, "asdfasdfasdf");
+});
+
+prep(function () {
+    re = new RegExp("aaaaaaaaaa", "g");
+    tmp = generateTestStrings(100);
+});
+
+test("Compiled Variable Object Match", function () {
+    for (var i = 0; i < 100; i++)
+        ret = tmp[i].match(re);
+});
+
+prep(function () {
+    tmp = generateTestStrings(100);
+});
+
+test("Compiled Variable Object Test", function () {
+    for (var i = 0; i < 100; i++)
+        ret = re.test(tmp[i]);
+});
+
+prep(function () {
+    tmp = generateTestStrings(50);
+});
+
+test("Compiled Variable Object Empty Replace", function () {
+    for (var i = 0; i < 50; i++)
+        ret = tmp[i].replace(re, "");
+});
+
+prep(function () {
+    tmp = generateTestStrings(50);
+});
+
+test("Compiled Variable Object 12 Char Replace", function () {
+    for (var i = 0; i < 50; i++)
+        ret = tmp[i].replace(re, "asdfasdfasdf");
+});
+
+prep(function () {
+    tmp = generateTestStrings(50);
+});
+
+test("Compiled Variable Object 12 Char Replace Function", function () {
+    for (var i = 0; i < 50; i++)
+        ret = tmp[i].replace(re, function (all) {
+            return "asdfasdfasdf";
+        });
+});
+
+// TESTS: Capturing
+
+prep(function () {
+    re = /aa(b)aa/g;
+    tmp = generateTestStrings(100);
+});
+
+test("Compiled Capture Match", function () {
+    for (var i = 0; i < 100; i++)
+        ret = tmp[i].match(re);
+});
+
+prep(function () {
+    tmp = generateTestStrings(50);
+});
+
+test("Compiled Capture Replace", function () {
+    for (var i = 0; i < 50; i++)
+        ret = tmp[i].replace(re, "asdfasdfasdf");
+});
+
+prep(function () {
+    tmp = generateTestStrings(50);
+});
+
+test("Compiled Capture Replace with Capture", function () {
+    for (var i = 0; i < 50; i++)
+        ret = tmp[i].replace(re, "asdf\\1asdfasdf");
+});
+
+prep(function () {
+    tmp = generateTestStrings(50);
+});
+
+test("Compiled Capture Replace with Capture Function", function () {
+    for (var i = 0; i < 50; i++)
+        ret = tmp[i].replace(re, function (all, capture) {
+            return "asdf" + capture + "asdfasdf";
+        });
+});
+
+prep(function () {
+    tmp = generateTestStrings(50);
+});
+
+test("Compiled Capture Replace with Upperase Capture Function", function () {
+    for (var i = 0; i < 50; i++)
+        ret = tmp[i].replace(re, function (all, capture) {
+            return capture.toUpperCase();
+        });
+});
+
+// TESTS: Uncompiled RegExps
+
+prep(function () {
+    tmp = generateTestStrings(100);
+});
+
+test("Uncompiled Match", function () {
+    for (var i = 0; i < 100; i++)
+        ret = tmp[i].match(/aaaaaaaaaa/g);
+});
+
+prep(function () {
+    tmp = generateTestStrings(100);
+});
+
+test("Uncompiled Test", function () {
+    for (var i = 0; i < 100; i++)
+        ret = (/aaaaaaaaaa/g).test(tmp[i]);
+});
+
+prep(function () {
+    tmp = generateTestStrings(50);
+});
+
+test("Uncompiled Empty Replace", function () {
+    for (var i = 0; i < 50; i++)
+        ret = tmp[i].replace(/aaaaaaaaaa/g, "");
+});
+
+prep(function () {
+    tmp = generateTestStrings(50);
+});
+
+test("Uncompiled 12 Char Replace", function () {
+    for (var i = 0; i < 50; i++)
+        ret = tmp[i].replace(/aaaaaaaaaa/g, "asdfasdfasdf");
+});
+
+prep(function () {
+    tmp = generateTestStrings(100);
+});
+
+test("Uncompiled Object Match", function () {
+    for (var i = 0; i < 100; i++)
+        ret = tmp[i].match(new RegExp("aaaaaaaaaa", "g"));
+});
+
+prep(function () {
+    tmp = generateTestStrings(100);
+});
+
+test("Uncompiled Object Test", function () {
+    for (var i = 0; i < 100; i++)
+        ret = (new RegExp("aaaaaaaaaa", "g")).test(tmp[i]);
+});
+
+prep(function () {
+    tmp = generateTestStrings(50);
+});
+
+test("Uncompiled Object Empty Replace", function () {
+    for (var i = 0; i < 50; i++)
+        ret = tmp[i].replace(new RegExp("aaaaaaaaaa", "g"), "");
+});
+
+prep(function () {
+    tmp = generateTestStrings(50);
+});
+
+test("Uncompiled Object 12 Char Replace", function () {
+    for (var i = 0; i < 50; i++)
+        ret = tmp[i].replace(new RegExp("aaaaaaaaaa", "g"), "asdfasdfasdf");
+});
+
+endTest();

+ 194 - 0
Jint.Benchmark/Scripts/dromaeo/dromaeo-object-string.js

@@ -0,0 +1,194 @@
+startTest("dromaeo-object-string", 'ef8605c3');
+
+// Try to force real results
+var ret;
+var num = 80000;
+
+// TESTS: String concatenation
+
+test("Concat String", function () {
+    var str = "";
+    for (var i = 0; i < num; i++)
+        str += "a";
+    ret = str;
+});
+
+test("Concat String Object", function () {
+    var str = new String();
+    for (var i = 0; i < num; i++)
+        str += "a";
+    ret = str;
+});
+
+test("Concat String from charCode", function () {
+    var str = "";
+    for (var i = 0; i < num / 2; i++)
+        str += String.fromCharCode(97);
+    ret = str;
+});
+
+test("Array String Join", function () {
+    var str = [];
+    for (var i = 0; i < num / 2; i++)
+        str.push("a");
+    ret = str.join("");
+});
+
+var ostr = [], tmp, tmp2, num = 5000, tmpstr;
+
+for (var i = 0; i < 16384; i++)
+    ostr.push(String.fromCharCode((25 * Math.random()) + 97));
+
+ostr = ostr.join("");
+ostr += ostr;
+ostr += ostr;
+
+var str;
+var i = 52288;
+
+prep(function () {
+    str = new String(ostr);
+});
+
+// TESTS: split
+test("String Split", function () {
+    ret = str.split("");
+});
+
+prep(function () {
+    tmpstr = str;
+    tmpstr += tmpstr;
+    tmpstr += tmpstr;
+    tmpstr += tmpstr;
+    tmpstr += tmpstr;
+});
+
+test("String Split on Char", function () {
+    ret = tmpstr.split("a");
+});
+
+prep(function () {
+    str += str;
+});
+
+// TESTS: characters
+
+test("charAt", function () {
+    for (var j = 0; j < num; j++) {
+        ret = str.charAt(0);
+        ret = str.charAt(str.length - 1);
+        ret = str.charAt(15000);
+        ret = str.charAt(12000);
+    }
+});
+
+test("[Number]", function () {
+    for (var j = 0; j < num; j++) {
+        ret = str[0];
+        ret = str[str.length - 1];
+        ret = str[15000];
+        ret = str[10000];
+        ret = str[5000];
+    }
+});
+
+test("charCodeAt", function () {
+    for (var j = 0; j < num; j++) {
+        ret = str.charCodeAt(0);
+        ret = str.charCodeAt(str.length - 1);
+        ret = str.charCodeAt(15000);
+        ret = str.charCodeAt(10000);
+        ret = str.charCodeAt(5000);
+    }
+});
+
+// TESTS: indexOf
+
+test("indexOf", function () {
+    for (var j = 0; j < num; j++) {
+        ret = str.indexOf("a");
+        ret = str.indexOf("b");
+        ret = str.indexOf("c");
+        ret = str.indexOf("d");
+    }
+});
+
+test("lastIndexOf", function () {
+    for (var j = 0; j < num; j++) {
+        ret = str.lastIndexOf("a");
+        ret = str.lastIndexOf("b");
+        ret = str.lastIndexOf("c");
+        ret = str.lastIndexOf("d");
+    }
+});
+
+// TESTS: slice
+
+test("slice", function () {
+    for (var j = 0; j < num; j++) {
+        ret = str.slice(0);
+        ret = str.slice(0, 5);
+        ret = str.slice(-1);
+        ret = str.slice(-6, -1);
+        ret = str.slice(15000, 15005);
+        ret = str.slice(12000, -1);
+    }
+});
+
+// TESTS: substr
+
+test("substr", function () {
+    for (var j = 0; j < num; j++) {
+        ret = str.substr(0);
+        ret = str.substr(0, 5);
+        ret = str.substr(-1);
+        ret = str.substr(-6, 1);
+        ret = str.substr(15000, 5);
+        ret = str.substr(12000, 5);
+    }
+});
+
+// TESTS: substring
+
+test("substring", function () {
+    for (var j = 0; j < num; j++) {
+        ret = str.substring(0);
+        ret = str.substring(0, 5);
+        ret = str.substring(-1);
+        ret = str.substring(-6, -1);
+        ret = str.substring(15000, 15005);
+        ret = str.substring(12000, -1);
+    }
+});
+
+// TESTS: toLower/UpperCase
+
+test("toLowerCase", function () {
+    for (var j = 0; j < num / 1000; j++) {
+        ret = str.toLowerCase();
+    }
+});
+
+test("toUpperCase", function () {
+    for (var j = 0; j < num / 1000; j++) {
+        ret = str.toUpperCase();
+    }
+});
+
+// TESTS: comparing
+prep(function () {
+    tmp = str;
+    tmp2 = str;
+});
+
+test("comparing", function () {
+    tmp = "a" + tmp + "a";
+    tmp2 = "a" + tmp2 + "a";
+    for (var j = 0; j < num / 1000; j++) {
+        ret = tmp == tmp2;
+        ret = tmp < tmp2;
+        ret = tmp > tmp2;
+    }
+});
+
+endTest();

+ 147 - 0
Jint.Benchmark/Scripts/dromaeo/dromaeo-string-base64.js

@@ -0,0 +1,147 @@
+/* ***** BEGIN LICENSE BLOCK *****
+ * Version: MPL 1.1/GPL 2.0/LGPL 2.1
+ *
+ * The contents of this file are subject to the Mozilla Public License Version
+ * 1.1 (the "License"); you may not use this file except in compliance with
+ * the License. You may obtain a copy of the License at
+ * http://www.mozilla.org/MPL/
+ *
+ * Software distributed under the License is distributed on an "AS IS" basis,
+ * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
+ * for the specific language governing rights and limitations under the
+ * License.
+ *
+ * The Original Code is Mozilla XML-RPC Client component.
+ *
+ * The Initial Developer of the Original Code is
+ * Digital Creations 2, Inc.
+ * Portions created by the Initial Developer are Copyright (C) 2000
+ * the Initial Developer. All Rights Reserved.
+ *
+ * Contributor(s):
+ *   Martijn Pieters <[email protected]> (original author)
+ *   Samuel Sieb <[email protected]>
+ *
+ * Alternatively, the contents of this file may be used under the terms of
+ * either the GNU General Public License Version 2 or later (the "GPL"), or
+ * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
+ * in which case the provisions of the GPL or the LGPL are applicable instead
+ * of those above. If you wish to allow use of your version of this file only
+ * under the terms of either the GPL or the LGPL, and not to allow others to
+ * use your version of this file under the terms of the MPL, indicate your
+ * decision by deleting the provisions above and replace them with the notice
+ * and other provisions required by the GPL or the LGPL. If you do not delete
+ * the provisions above, a recipient may use your version of this file under
+ * the terms of any one of the MPL, the GPL or the LGPL.
+ *
+ * ***** END LICENSE BLOCK ***** */
+
+// From: http://lxr.mozilla.org/mozilla/source/extensions/xml-rpc/src/nsXmlRpcClient.js#956
+
+/* Convert data (an array of integers) to a Base64 string. */
+var toBase64Table = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/';
+var base64Pad = '=';
+
+function toBase64(data) {
+    var result = '';
+    var length = data.length;
+    var i;
+    // Convert every three bytes to 4 ascii characters.
+    for (i = 0; i < (length - 2) ; i += 3) {
+        result += toBase64Table[data.charCodeAt(i) >> 2];
+        result += toBase64Table[((data.charCodeAt(i) & 0x03) << 4) + (data.charCodeAt(i + 1) >> 4)];
+        result += toBase64Table[((data.charCodeAt(i + 1) & 0x0f) << 2) + (data.charCodeAt(i + 2) >> 6)];
+        result += toBase64Table[data.charCodeAt(i + 2) & 0x3f];
+    }
+
+    // Convert the remaining 1 or 2 bytes, pad out to 4 characters.
+    if (length % 3) {
+        i = length - (length % 3);
+        result += toBase64Table[data.charCodeAt(i) >> 2];
+        if ((length % 3) == 2) {
+            result += toBase64Table[((data.charCodeAt(i) & 0x03) << 4) + (data.chartCodeAt(i + 1) >> 4)];
+            result += toBase64Table[(data.charCodeAt(i + 1) & 0x0f) << 2];
+            result += base64Pad;
+        } else {
+            result += toBase64Table[(data.charCodeAt(i) & 0x03) << 4];
+            result += base64Pad + base64Pad;
+        }
+    }
+
+    return result;
+}
+
+/* Convert Base64 data to a string */
+var toBinaryTable = [
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
+    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, 62, -1, -1, -1, 63,
+    52, 53, 54, 55, 56, 57, 58, 59, 60, 61, -1, -1, -1, 0, -1, -1,
+    -1, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14,
+    15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, -1, -1, -1, -1, -1,
+    -1, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40,
+    41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, -1, -1, -1, -1, -1
+];
+
+function base64ToString(data) {
+    var result = '';
+    var leftbits = 0; // number of bits decoded, but yet to be appended
+    var leftdata = 0; // bits decoded, but yet to be appended
+
+    // Convert one by one.
+    for (var i = 0; i < data.length; i++) {
+        var c = toBinaryTable[data.charCodeAt(i) & 0x7f];
+        var padding = (data.charCodeAt(i) == base64Pad.charCodeAt(0));
+        // Skip illegal characters and whitespace
+        if (c == -1) continue;
+
+        // Collect data into leftdata, update bitcount
+        leftdata = (leftdata << 6) | c;
+        leftbits += 6;
+
+        // If we have 8 or more bits, append 8 bits to the result
+        if (leftbits >= 8) {
+            leftbits -= 8;
+            // Append if not padding.
+            if (!padding)
+                result += String.fromCharCode((leftdata >> leftbits) & 0xff);
+            leftdata &= (1 << leftbits) - 1;
+        }
+    }
+
+    // If there are any bits left, the base64 string was corrupted
+    if (leftbits)
+        throw Components.Exception('Corrupted base64 string');
+
+    return result;
+}
+
+startTest("dromaeo-string-base64", '09340c18');
+
+var str = [];
+
+for (var i = 0; i < 4096; i++)
+    str.push(String.fromCharCode((25 * Math.random()) + 97));
+
+str = str.join("");
+str += str;
+str += str;
+
+var base64;
+
+test("Convert String to Base 64", function () {
+    base64 = toBase64(str);
+});
+
+prep(function () {
+    if (!base64)
+        base64 = toBase64(str);
+});
+
+test("Convert Base 64 to String", function () {
+    if (str !== base64ToString(base64)) {
+        throw "String conversion mis-match.";
+    }
+});
+
+endTest();

+ 3 - 0
Jint/Native/Array/ArrayExecutionContext.cs

@@ -1,4 +1,5 @@
 using System.Collections.Generic;
 using System.Collections.Generic;
+using System.Text;
 using System.Threading;
 using System.Threading;
 
 
 namespace Jint.Native.Array
 namespace Jint.Native.Array
@@ -15,6 +16,7 @@ namespace Jint.Native.Array
         private JsValue[] _callArray1;
         private JsValue[] _callArray1;
         private JsValue[] _callArray3;
         private JsValue[] _callArray3;
         private JsValue[] _callArray4;
         private JsValue[] _callArray4;
+        private StringBuilder _stringBuilder;
 
 
         private ArrayExecutionContext()
         private ArrayExecutionContext()
         {
         {
@@ -25,6 +27,7 @@ namespace Jint.Native.Array
         public JsValue[] CallArray1 => _callArray1 = _callArray1 ?? new JsValue[1];
         public JsValue[] CallArray1 => _callArray1 = _callArray1 ?? new JsValue[1];
         public JsValue[] CallArray3 => _callArray3 = _callArray3 ?? new JsValue[3];
         public JsValue[] CallArray3 => _callArray3 = _callArray3 ?? new JsValue[3];
         public JsValue[] CallArray4 => _callArray4 = _callArray4 ?? new JsValue[4];
         public JsValue[] CallArray4 => _callArray4 = _callArray4 ?? new JsValue[4];
+        public StringBuilder StringBuilder => _stringBuilder = _stringBuilder ?? new StringBuilder();
 
 
         public static ArrayExecutionContext Current => _executionContext.Value;
         public static ArrayExecutionContext Current => _executionContext.Value;
     }
     }

+ 19 - 11
Jint/Native/Array/ArrayPrototype.cs

@@ -704,21 +704,29 @@ namespace Jint.Native.Array
                 return "";
                 return "";
             }
             }
 
 
-            var element0 = o.Get(0);
-            string r = element0 == Undefined.Instance || element0 == Null.Instance
-                ? ""
-                : TypeConverter.ToString(element0);
-            for (uint k = 1; k < len; k++)
+            string StringFromJsValue(JsValue value)
             {
             {
-                var s = r + sep;
-                var element = o.Get(k);
-                string next = element == Undefined.Instance || element == Null.Instance
+                return value == Undefined.Instance || value == Null.Instance
                     ? ""
                     ? ""
-                    : TypeConverter.ToString(element);
-                r = s + next;
+                    : TypeConverter.ToString(value);
             }
             }
 
 
-            return r;
+            var s = StringFromJsValue(o.Get(0));
+            if (len == 1)
+            {
+                return s;
+            }
+
+            var sb = ArrayExecutionContext.Current.StringBuilder;
+            sb.Clear();
+            sb.Append(s);
+            for (uint k = 1; k < len; k++)
+            {
+                sb.Append(sep);
+                sb.Append(StringFromJsValue(o.Get(k)));
+            }
+
+            return sb.ToString();
         }
         }
 
 
         private JsValue ToLocaleString(JsValue thisObj, JsValue[] arguments)
         private JsValue ToLocaleString(JsValue thisObj, JsValue[] arguments)

+ 0 - 1
Jint/Native/String/StringPrototype.cs

@@ -335,7 +335,6 @@ namespace Jint.Native.String
                     {
                     {
                         segments.Capacity = s.Length;
                         segments.Capacity = s.Length;
                     }
                     }
-                    segments = new List<string>(s.Length);
                     foreach (var c in s)
                     foreach (var c in s)
                     {
                     {
                         segments.Add(TypeConverter.ToString(c));
                         segments.Add(TypeConverter.ToString(c));