Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

lib: prefer using Array#includes instead of Array#indexOf #26568

Closed
starkwang opened this issue Mar 10, 2019 · 8 comments
Closed

lib: prefer using Array#includes instead of Array#indexOf #26568

starkwang opened this issue Mar 10, 2019 · 8 comments

Comments

@starkwang
Copy link
Contributor

starkwang commented Mar 10, 2019

In our lib code, we are still using Array#indexOf to determine if an item is existed in array.

For example:

node/lib/url.js

Line 584 in 3414bc7

this.hostname.indexOf(':') === -1 ?

if (str.indexOf("'") !== -1) {

For semantics, I think it's more appropriate to move them into Array#includes and it seems no performance difference between them at present: https://jsperf.com/array-indexof-vs-includes.

@starkwang starkwang added help wanted Issues that need assistance from volunteers or PRs that need help to proceed. good first issue Issues that are suitable for first-time contributors. labels Mar 10, 2019
@mscdex
Copy link
Contributor

mscdex commented Mar 10, 2019

Assuming you tested the equivalent of master, did you also check the versioned branches? It may/may not be as fast in older versions of V8.

@starkwang
Copy link
Contributor Author

I've tested it on LTS version (code). Here is the result:

Node.js 6.17.0 (indexOf is 3x faster):

Array#indexOf x 7,221,953 ops/sec ±2.65% (79 runs sampled)
Array#includes x 1,972,658 ops/sec ±2.02% (84 runs sampled)
Fastest is Array#indexOf

Node.js 8.15.1 (same):

Array#indexOf x 7,340,047 ops/sec ±4.30% (75 runs sampled)
Array#includes x 8,899,792 ops/sec ±0.44% (90 runs sampled)
Fastest is Array#includes

Node.js 10.15.3 (includes is 80x faster):

Array#indexOf x 11,671,636 ops/sec ±4.92% (73 runs sampled)
Array#includes x 861,062,120 ops/sec ±0.51% (91 runs sampled)
Fastest is Array#includes

Node.js 11.11.0 (includes is 80x faster):

Array#indexOf x 10,776,587 ops/sec ±4.10% (78 runs sampled)
Array#includes x 854,403,715 ops/sec ±0.60% (88 runs sampled)
Fastest is Array#includes

So we may not land this change into 6.x.

@BridgeAR
Copy link
Member

@starkwang the actual code is optimized away in your benchmark. Therefore it is significantly faster.

@BridgeAR
Copy link
Member

Array#indexOf() will always be faster as it has less to do by spec and the engine is not able to optimize that part away. The difference is not big but it is definitely there.

I do not think we should change this ever.

@BridgeAR BridgeAR removed good first issue Issues that are suitable for first-time contributors. help wanted Issues that need assistance from volunteers or PRs that need help to proceed. labels Mar 10, 2019
@BridgeAR
Copy link
Member

const items = [
  'jpg', '3fr', 'ari', 'arw',
  'bay', 'crw', 'cr2', 'cap',
  'data', 'dcs', 'dcr', 'dng',
  'drf', 'eip', 'erf', 'fff',
  'iiq', 'k25', 'kdc', 'mdc',
  'mef', 'mos', 'mrw', 'nef',
  'nrw', 'obm', 'orf', 'pef',
  'ptx', 'pxn', 'r3d', 'raf',
  'raw', 'rwl', 'rw2', 'rwz',
  'sr2', 'srf', 'srw', 'tif',
  'x3f'
];

const searchStrings = [
  'raf',
  'nef',
  'cr'
];

function indexOf(arr, str) {
  if (arr.indexOf(str) !== -1)
    return 1;
  return 0;
}

function includes(arr, str) {
  if (arr.includes(str))
    return 1;
  return 0;
}

function bench(fn) {
  console.time(fn.name);
  let count = 0;
  for (var i = 0; i < 1e7; i++) {
    count += fn(items, searchStrings[i % searchStrings.length]);
  }
  console.timeEnd(fn.name);
  console.log(count);
}

for (var i = 0; i < 3; i++) {
  bench(indexOf);
  bench(includes);
}

Yields on master:

indexOf: 756.462ms
includes: 958.480ms
indexOf: 946.475ms
includes: 965.666ms
indexOf: 947.888ms
includes: 1002.555ms

@devsnek
Copy link
Member

devsnek commented Mar 10, 2019

Array#indexOf() will always be faster as it has less to do by spec and the engine is not able to optimize that part away.

just for the record, includes is actually much simpler in the spec. indexOf does a HasProperty before each Get, while includes just goes straight to Get.

on v11.11.0 i get:

the difference is negligible

@BridgeAR
Copy link
Member

BridgeAR commented Mar 10, 2019

@devsnek you are right, I remembered the spec wrong in this case (I remembered it the other way around). However, in most cases the HasProperty check will just be optimized away by the engine.

V8 7.3 seems to be on par performance wise.

@refack
Copy link
Contributor

refack commented Mar 10, 2019

For such a tiny op, depend on the use case, IMHO we should always prefer readability, simplicity, and explicivity. If the usage is to simply test for inclusion, and there is no use of the found index, then .include is optimal.

One main reason for this is that our stdlib is used as a reference implementation.

https://jsperf.com/array-indexof-vs-includes/25 that indicate +/- 10% on my chrome's V8 7.2

starkwang added a commit that referenced this issue Mar 21, 2019
PR-URL: #26732
Refs: #26568
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Richard Lau <riclau@uk.ibm.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
targos pushed a commit to targos/node that referenced this issue Mar 27, 2019
PR-URL: nodejs#26732
Refs: nodejs#26568
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Richard Lau <riclau@uk.ibm.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
targos pushed a commit that referenced this issue Mar 27, 2019
PR-URL: #26732
Refs: #26568
Reviewed-By: Michaël Zasso <targos@protonmail.com>
Reviewed-By: Richard Lau <riclau@uk.ibm.com>
Reviewed-By: Luigi Pinca <luigipinca@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants