6

I wanted to extend String object prototype with some utility method. It worked, but the performance was surprisingly low. Passing a string to a function is 10x times faster than overriding the String.prototype method that is doing the same thing. To make sure this really happens I created a very simple count() function and the corresponding methods.

(I was experimenting, and created three different versions of the method.)

function count(str, char) {
    var n = 0;
    for (var i = 0; i < str.length; i++) if (str[i] == char) n++;
    return n;
}

String.prototype.count = function (char) {
    var n = 0;
    for (var i = 0; i < this.length; i++) if (this[i] == char) n++;
    return n;
}

String.prototype.count_reuse = function (char) {
    return count(this, char)
}

String.prototype.count_var = function (char) {
    var str = this;
    var n = 0;
    for (var i = 0; i < str.length; i++) if (str[i] == char) n++;
    return n;
}

// Here is how I measued speed, using Node.js 6.1.0

var STR ='0110101110010110100111010011101010101111110001010110010101011101101010101010111111000';
var REP = 1e3//6;

console.time('func')
for (var i = 0; i < REP; i++) count(STR,'1')
console.timeEnd('func')

console.time('proto')
for (var i = 0; i < REP; i++) STR.count('1')
console.timeEnd('proto')

console.time('proto-reuse')
for (var i = 0; i < REP; i++) STR.count_reuse('1')
console.timeEnd('proto-reuse')

console.time('proto-var')
for (var i = 0; i < REP; i++) STR.count_var('1')
console.timeEnd('proto-var')

Results:

func: 705 ms
proto: 10011 ms
proto-reuse: 10366 ms
proto-var: 9703 ms

As you can see the difference is dramatic.

The below proves that performance of method calls is neglectably slower, and that the function code it self is slower for methods.

function count_dummy(str, char) {
    return 1234;
}

String.prototype.count_dummy = function (char) {
    return 1234; // Just to prove that accessing the method is not the bottle-neck.
}

console.time('func-dummy')
for (var i = 0; i < REP; i++) count_dummy(STR,'1')
console.timeEnd('func-dummy')

console.time('proto-dummy')
for (var i = 0; i < REP; i++) STR.count_dummy('1')
console.timeEnd('proto-dummy')

console.time('func-dummy')
for (var i = 0; i < REP; i++) count_dummy(STR,'1')
console.timeEnd('func-dummy')

Results:

func-dummy: 0.165ms
proto-dummy: 0.247ms

Although on huge repetitions (like 1e8) prototyped methods proves to be 10x times slower than functions, this can be ignored for this case.

All this may be related only to a String object, because simple generic objects perform about the same when you pass them to functions or call their methods:

var A = { count: 1234 };

function getCount(obj) { return obj.count }

A.getCount = function() { return this.count }

console.time('func')
for (var i = 0; i < 1e9; i++) getCount(A)
console.timeEnd('func')

console.time('method')
for (var i = 0; i < 1e9; i++) A.getCount()
console.timeEnd('method')

Results:

func: 1689.942ms
method: 1674.639ms

I've been searching on Stackoverflow and binging, but other that the recommendation "do not extend String or Array because you pollute the name space" (which is not a problem for my particular project), I cannot find anything related to performance of methods compared to functions. So should I simply forget about extending the String object due to performance drop of added methods or there is more about it?

exebook
  • 27,243
  • 27
  • 105
  • 196
  • Thanks for your information but, you're last implementation is far away from `prototype` you can replace object with function and then add prototype method. then get a new instance of it. BTW result is the same. – Morteza Tourani Jul 16 '16 at 05:37

1 Answers1

5

This is most likely because you are not using strict mode, and the this value inside your method needs to be cast to a String instance instead of being a primitive string.

You can confirm this by repeating your measurement on var STR = new String('01101011…').

Then fix your implementation:

String.prototype.count = function (char) {
    "use strict";
    var n = 0;
    for (var i = 0; i < this.length; i++)
        if (this[i] == char)
            n++;
    return n;
};
Bergi
  • 513,640
  • 108
  • 821
  • 1,164
  • 1
    It works, but does not make sense to me! Thus I added a new question: http://stackoverflow.com/questions/38411552/why-use-strict-improves-performance-10x-in-this-example – exebook Jul 16 '16 at 13:10
  • 2
    @exebook: As I said, sloppy mode casts the `this` value to an object, so it needs to create a `String` instance on every call, which is quite some overhead for such a simple method as yours – Bergi Jul 16 '16 at 15:35