6
\$\begingroup\$

Given two arrays and two indices, I need to concatenate the prefix of the first array and the reversed prefix of the second array.

For example:

// Input
arr1 = [1,2,3,4,5,6,7,8,9];
arr2 = [10,11,12,13,14,15];
ind1 = 6;
ind2 = 3;

// Output
arr = [1,2,3,4,5,6,12,11,10];

I have implemented two different functions for this purpose:

function func1(arr1,arr2,ind1,ind2)
{
    let arr = [];
    for (let n = 0; n <= ind1; n++)
        arr.push(arr1[n]);
    for (let n = ind2; n >= 0; n--)
        arr.push(arr2[n]);
    return arr;
}

function func2(arr1,arr2,ind1,ind2)
{
    return arr1.slice(0, ind1 + 1).concat(arr2.slice(0, ind2 + 1).reverse());
}

And now I would like determine which one of them is better, using this performance analysis:

function measure(func,arr1,arr2,ind1,ind2)
{
    let t0 = performance.now();
    arr = func(arr1,arr2,ind1,ind2);
    let t1 = performance.now();
    return t1 - t0;
}

results = '';

for (let len = 10; len <= 120; len += 10)
{
    let func1Count = 0;
    let func2Count = 0;
    for (let i=0; i<1000000; i++)
    {
        let arr1 = new Array(len);
        let arr2 = new Array(len);
        let ind1 = Math.floor(Math.random()*len);
        let ind2 = Math.floor(Math.random()*len);
        func1Count += measure(func1,arr1,arr2,ind1,ind2);
        func2Count += measure(func2,arr1,arr2,ind1,ind2);
    }
    if (func1Count < func2Count)
        results += 'len='+len+': func1 is better ('+func1Count+' vs '+func2Count+')\n';
    else
        results += 'len='+len+': func2 is better ('+func1Count+' vs '+func2Count+')\n';
}

alert(results);

The test above indicates that the first function is better if the array length is (approximately) 80 or less, and that the second function is better otherwise.

I would like to ensure the correctness of my performance analysis.

In addition, I should note that this code will eventually run on server side (i.e., NodeJS), but I am testing it on client side (i.e., a browser). So if this method is fundamentally wrong (i.e., testing a server-side code over a client-side platform), then I would be happy to be notified.

\$\endgroup\$

migrated from stackoverflow.com Dec 18 '17 at 23:10

This question came from our site for professional and enthusiast programmers.

  • \$\begingroup\$ Before benchmarking your code, you should test whether it works. I think the example input doesn't produce the example output. \$\endgroup\$ – Roland Illig Dec 20 '17 at 2:57
  • \$\begingroup\$ @RolandIllig: What makes you think that? Have YOU tested it? I know it works, because I HAVE tested it. Only thing I can think of, is that I pasted it incorrectly here, which is why I'm asking (again) - have you tested it? \$\endgroup\$ – goodvibration Dec 20 '17 at 7:10
  • \$\begingroup\$ You need to fix the global in your code. arr is undeclared. \$\endgroup\$ – Blindman67 Dec 20 '17 at 17:53
  • 3
    \$\begingroup\$ The two functions cannot be compared. There is a hidden cost of memory management that is much higher for the second function than the first. There is no reliable method from within the Javascript context to measure memory deallocation (GC). Getting faster time for the second function ignores the overhead of GC. Thus you can never reliably say func2 is faster than func1. And because func1 does not use as much memory you can say that func1 is faster than func2 but not by how much. There are large unknown involved in the way you test. You are best off using devTools or something like lighthouse. \$\endgroup\$ – Blindman67 Dec 20 '17 at 18:19
  • \$\begingroup\$ @Blindman67 so, the comparison criteria is not given, yet folks are trying to anwer the question as in "abstract cow in the vacuum". This saddens me a bit. \$\endgroup\$ – Igor Soloydenko Dec 26 '17 at 14:32
2
\$\begingroup\$

The only note I can make is that in func1 and func2 you have an "API bug": the indices are "included" in the array. Consider this example:

[1, 2, 3, 4].slice(1, 3)

will produce [2, 3], so the point is that 3 in slice(1, 3) is not included. This does not happen only in Javascript, but also in other languages as well (I know Java does it that way). So the only advice would be to write your functions as

function func1(arr1,arr2,ind1,ind2)
{
    let arr = [];
    for (let n = 0; n < ind1; n++)
        arr.push(arr1[n]);
    for (let n = ind2 - 1; n >= 0; n--)
        arr.push(arr2[n]);
    return arr;
}

function func2(arr1,arr2,ind1,ind2)
{
    return arr1.slice(0, ind1).concat(arr2.slice(0, ind2).reverse());
}

Also, it is good to have a single space after each comma , in the function parameter list.

\$\endgroup\$
2
+50
\$\begingroup\$

TL,DR

The biggest problem I see with your question is your use of the word "better," as if that's an objective term that means the same thing in every context. Faster !== better. Assuming you're the one maintaining the code, the best one is always whichever one works and whichever one you like the most.

Synopsis

First, let me mention jsperf - a site that was built for javascript performance testing. If you insist on testing server-side code on the client side, you should really use this instead of building your own, for many reasons, the main one being: because it's easier. But to get the most accurate results, you really should test in the environment it will be run in (on the server side).

It is good to know how to write your own benchmarks, but if you do, you should make sure your benchmark code has the smallest footprint possible so as not to affect the results. Among other things, every time you concat a string with += the computer has to load the entire string into memory. It is better to put your string parts in an array and then join the array at the end.

As Blindman67 noted in the comments, benchmarking Javascript is tricky and generally unreliable. There are simply too many variables (different JS engines, other things running on the computer, etc).

The two functions cannot be compared. There is a hidden cost of memory management that is much higher for the second function than the first. There is no reliable method from within the Javascript context to measure memory deallocation (GC). Getting faster time for the second function ignores the overhead of GC. Thus you can never reliably say func2 is faster than func1. And because func1 does not use as much memory you can say that func1 is faster than func2 but not by how much. There are large unknown involved in the way you test. You are best off using devTools or something like lighthouse. - Blindman67

In this case, you're wasting more of your own time than you are of the user's time as any difference will be minuscule. Therefore you should choose the approach that is more readable and easier to maintain. IMO, that would be the first approach.

Micro-enhancements & Considerations

  • var is faster than let. Other than some superficial desire to be cutting edge, there's really no reason to use let over var in this case. If you value speed, var is better. If you're applying for a job and want to prove that you're not stuck in the year 2000, let is better.
  • A single declaration is faster than multiple declarations. However multiple declarations eliminate the risk of accidentally creating a global variable because you used a semicolon instead of a comma. From a "best practices"/maintainability standpoint your way is better. To some crazy person who values speed over all else, it's probably better to declare your variables at the top.
  • The less stuff the computer has to do, the faster it will go. The first statement in the for loop is only evaluated once but the other two statements are evaluated every time, so if faster means better, then it would be "better" to do your second loop like this: for (n = 1+ind2; n--;) which eliminates one whole statement that needs to be evaluated on ever iteration. (Note: if you want your output array to match what your current function produces then use the +1, but if you want it to match what you gave as the output array jut do for (n = ind2; n--;). See the other answer as well as the first comment in this thread for more info.
  • There was this thing a little while back called the Apple goto fail bug which was a pretty big deal and basically broke the internet. The problem had to do with not using the curly braces - someone later added a line to a for loop (or a conditional or something, idr) and since there were no curly braces it, well, broke. In my opinion, you should only omit the curlies if the statement can fit on the same line, otherwise you run the risk of creating bugs later on, also, it's more readable and concise when it's on one line if it all fits comfortably.

How I would do it

(Assuming I wanted it to be as fast as possible, and that you want it to output what you said it should output rather than what it actually outputs)

function func1(arr1,arr2,ind1,ind2){
    var arr = [], n=0;
    for (;n < ind1; n++) arr.push(arr1[n]);
    for (n = ind2; n--;) arr.push(arr2[n]);
    return arr;
}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.