0

I want to understand the performance difference for constructing arrays. Running the following program, I am puzzled by the output below:

Time for range0: 521
Time for range1: 149
Time for range2: 1848
Time for range3: 8411
Time for range4: 3487

I don't understand why 3 takes longer than 4, while 1 takes shorter than 2. Also, seems the map function is very inefficient; what is the use of it?


function range0(start, count) {
    var arr = [];
    for (var i = 0; i < count; i++) {
        arr.push(start + i);
    }
    return arr;
}

function range1(start, count) {
    var arr = new Array(count);
    for (var i = 0; i < count; i++) {
        arr[i] = start + i;
    }
    return arr;
}

function range2(start, count) {
    var arr = Array.apply(0, Array(count));
    for (var i = 0; i < count; i++) {
        arr[i] = start + i;
    }
    return arr;
}

function range3(start, count) {
    var arr = new Array(count);
    return arr.map(function(element, index) {
        return index + start;
    });
}

function range4(start, count) {
    var arr = Array.apply(0, Array(count));
    return arr.map(function(element, index) {
        return index + start;
    });
}

function profile(range) {
    var iterations = 100000,
        start = 0, count = 1000,
        startTime, endTime, finalTime;

    startTime = performance.now();

    for (var i = 0; i < iterations; ++i) {
        range(start, count);
    }

    endTime = performance.now();

    finalTime = (endTime - startTime);
    console.log(range.name + ': ' + finalTime + ' ms');
}

[range0, range1, range2, range3, range4].forEach(profile);

5
  • Modern engines have optimizations for pre-allocated, homogeneous arrays, so that likely explains range1. Commented May 31, 2016 at 23:39
  • .map() exists to take an existing array and create a new one of the same length using potentially complex logic to determine the value of each member based on current members. It's a very convenient method. Commented May 31, 2016 at 23:41
  • Because .map() has it's own job because var arr = Array.apply(0, Array(count)) is a very ugly JS instruction.. Commented May 31, 2016 at 23:42
  • 2
    Your range3 doesn't actually work properly. Commented May 31, 2016 at 23:44
  • Using apply with very many arguments is most likely the main culprit here. Also notice that the use of map does create a second array, so it's quite incomparable to range0 or range1 when you want to measure the time to create one array. Commented Jun 1, 2016 at 0:58

1 Answer 1

1

I don't understand why 3 takes longer than 4

Me neither. It is a surprising result, given my superficial analysis and the results I obtained by profiling the code. On my computer running Google Chrome 50, range4 is twice as slow compared to range3.

I'd have to study the Javascript implementation you are using in order to figure out why that happens.

while 1 takes shorter than 2.

range1 executes faster because it uses a loop and optimizes memory allocations, while range2 uses functions and does unnecessary memory allocations.

Also, seems the map function is very inefficient; what is the use of it?

The map function is used to compute a new Array based on the values of an existing one.

[1, 2, 3, 4, 5].map(number => number * number);
// [1, 4, 9, 16, 25]

On my computer

Time for range0: 783
Time for range1: 287
Time for range2: 10541
Time for range3: 14981
Time for range4: 28243

My results reflect my expectations regarding the performance of each function.

A superficial analysis of each function

  • range0

    Creates an Array and populates it via a loop. It is the most simple and straightforward code possible. I suppose it could be understood as the baseline for performance comparison.

  • range1

    Uses the Array constructor with a length parameter. This greatly optimizes the underlying memory allocation required to store the elements. Since the exact number of elements is known beforehand, the memory does not have to be reallocated as the number of elements grows; the exact amount of memory needed to store all elements can be allocated exactly once, when the Array is instantiated.

  • range2

    Applies an empty arguments list to the constructor function, with this set to the number 0. This is semantically equivalent to Array() – the fact the arguments list was created with a count parameter has no bearing on the result of the function application. In fact, it needlessly wastes time allocating memory for an empty arguments list.

    You probably meant to use call:

      Array.call(null, count)
    
  • range3

    Like range1, but uses map with a function instead of a loop. The initial memory allocation is optimized, but the overhead of calling the function count times is likely to be huge.

    In addition, map generates a new Array instance. Since that instance also has count elements, it would make sense to optimize that memory allocation as well, however it is unclear to me whether that actually happens. Nevertheless, two separate memory allocations are taking place, instead of just one as in range1.

  • range4

    Combines all the inefficiencies of range2 and range3.

    Amazingly, it executes faster than range3 on your computer. It's unclear to me why that happened. I suppose one would have to investigate your Javascript particular implementation in order to figure it out.

Sign up to request clarification or add additional context in comments.

1 Comment

I run the code on nodeJS 4.4.5. The nodeJs engine must have done some good thing to optimize range4. I agree Array.apply(null, Array(count)) is not a proper way to create an empty array. I copied the code from some samples. Definitely should use Array.call(null, count) instead.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.