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

feat: Carry over node isIPv4/isIPv6 performance improvements #8271

Merged
merged 2 commits into from
Jan 20, 2024

Conversation

maxmilton
Copy link
Contributor

@maxmilton maxmilton commented Jan 19, 2024

What does this PR do?

Copy over the isIPv4/isIPv6 performance improvements from
nodejs/node#49568.

  • Documentation or TypeScript types (it's okay to leave the rest blank in this case)
  • Code changes

How did you verify your code works?

Run relevant tests:

  • ./build/bun-debug test test/js/bun/dns
  • ./build/bun-debug test test/js/node/net

Copy over the `isIPv4`/`isIPv6` performance improvements from
<nodejs/node#49568>.
Copy link
Collaborator

@Jarred-Sumner Jarred-Sumner left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

JavaScriptCore is a different engine, so the performance may be different. Do you have a benchmark that shows this regex is faster than the previous?

@maxmilton
Copy link
Contributor Author

maxmilton commented Jan 19, 2024

Sure, that makes sense, since the regex engine is likely to be quite different. The new code is also faster in bun except for some specific cases.

Here are mitata based benchmarks:
import { baseline, bench, group, run } from 'mitata';
import assert from 'node:assert';

const v4Seg_OLD = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])';
const v4Str_OLD = `(${v4Seg_OLD}[.]){3}${v4Seg_OLD}`;
const IPv4Reg_OLD = new RegExp(`^${v4Str_OLD}$`);

const v6Seg_OLD = '(?:[0-9a-fA-F]{1,4})';
const IPv6Reg_OLD = new RegExp(
  '^(' +
    `(?:${v6Seg_OLD}:){7}(?:${v6Seg_OLD}|:)|` +
    `(?:${v6Seg_OLD}:){6}(?:${v4Str_OLD}|:${v6Seg_OLD}|:)|` +
    `(?:${v6Seg_OLD}:){5}(?::${v4Str_OLD}|(:${v6Seg_OLD}){1,2}|:)|` +
    `(?:${v6Seg_OLD}:){4}(?:(:${v6Seg_OLD}){0,1}:${v4Str_OLD}|(:${v6Seg_OLD}){1,3}|:)|` +
    `(?:${v6Seg_OLD}:){3}(?:(:${v6Seg_OLD}){0,2}:${v4Str_OLD}|(:${v6Seg_OLD}){1,4}|:)|` +
    `(?:${v6Seg_OLD}:){2}(?:(:${v6Seg_OLD}){0,3}:${v4Str_OLD}|(:${v6Seg_OLD}){1,5}|:)|` +
    `(?:${v6Seg_OLD}:){1}(?:(:${v6Seg_OLD}){0,4}:${v4Str_OLD}|(:${v6Seg_OLD}){1,6}|:)|` +
    `(?::((?::${v6Seg_OLD}){0,5}:${v4Str_OLD}|(?::${v6Seg_OLD}){1,7}|:))` +
    ')(%[0-9a-zA-Z-.:]{1,})?$',
);

function isIPv4_OLD(s) {
  return IPv4Reg_OLD.test(s);
}
function isIPv6_OLD(s) {
  return IPv6Reg_OLD.test(s);
}
function isIP_OLD(s) {
  if (isIPv4_OLD(s)) return 4;
  if (isIPv6_OLD(s)) return 6;
  return 0;
}

const v4Seg_NEW = '(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])';
const v4Str_NEW = `(?:${v4Seg_NEW}\\.){3}${v4Seg_NEW}`;
const IPv4Reg_NEW = new RegExp(`^${v4Str_NEW}$`);

const v6Seg_NEW = '(?:[0-9a-fA-F]{1,4})';
const IPv6Reg2_NEW = new RegExp(
  '^(?:' +
    `(?:${v6Seg_NEW}:){7}(?:${v6Seg_NEW}|:)|` +
    `(?:${v6Seg_NEW}:){6}(?:${v4Str_NEW}|:${v6Seg_NEW}|:)|` +
    `(?:${v6Seg_NEW}:){5}(?::${v4Str_NEW}|(?::${v6Seg_NEW}){1,2}|:)|` +
    `(?:${v6Seg_NEW}:){4}(?:(?::${v6Seg_NEW}){0,1}:${v4Str_NEW}|(?::${v6Seg_NEW}){1,3}|:)|` +
    `(?:${v6Seg_NEW}:){3}(?:(?::${v6Seg_NEW}){0,2}:${v4Str_NEW}|(?::${v6Seg_NEW}){1,4}|:)|` +
    `(?:${v6Seg_NEW}:){2}(?:(?::${v6Seg_NEW}){0,3}:${v4Str_NEW}|(?::${v6Seg_NEW}){1,5}|:)|` +
    `(?:${v6Seg_NEW}:){1}(?:(?::${v6Seg_NEW}){0,4}:${v4Str_NEW}|(?::${v6Seg_NEW}){1,6}|:)|` +
    `(?::(?:(?::${v6Seg_NEW}){0,5}:${v4Str_NEW}|(?::${v6Seg_NEW}){1,7}|:))` +
    ')(?:%[0-9a-zA-Z-.:]{1,})?$',
);

function isIPv4_NEW(s) {
  return IPv4Reg_NEW.test(s);
}
function isIPv6_NEW(s) {
  return IPv6Reg2_NEW.test(s);
}
function isIP_NEW(s) {
  if (isIPv4_NEW(s)) return 4;
  if (isIPv6_NEW(s)) return 6;
  return 0;
}

const v4_ips = [
  '0.0.0.0',
  '127.0.0.1',
  '255.255.255.255',
  '192.168.0.1',
  '192.0.2.33',
];
const v6_ips = [
  '::1',
  'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
  '2001:db8:85a3:0:0:8a2e:370:7334',
  '::ffff:192.0.2.33',
];
const invalid_ips = [
  '',
  '.',
  '0.0.0',
  '10.168.209.250.1',
  'ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffg',
];

baseline('baseline', () => 0); // prevent some first run bias

group('isIPv4 true', () => {
  for (const ip of v4_ips) {
    assert.ok(isIPv4_NEW(ip));
    assert.ok(isIPv4_OLD(ip));
    bench(`NEW ${ip}`, () => isIPv4_NEW(ip));
    bench(`OLD ${ip}`, () => isIPv4_OLD(ip));
  }
});

group('isIPv4 false', () => {
  for (const ip of [...v6_ips, ...invalid_ips]) {
    assert.ok(!isIPv4_NEW(ip));
    assert.ok(!isIPv4_OLD(ip));
    bench(`OLD ${ip}`, () => isIPv4_OLD(ip));
    bench(`NEW ${ip}`, () => isIPv4_NEW(ip));
  }
});

group('isIPv6 true', () => {
  for (const ip of v6_ips) {
    assert.ok(isIPv6_NEW(ip));
    assert.ok(isIPv6_OLD(ip));
    bench(`NEW ${ip}`, () => isIPv6_NEW(ip));
    bench(`OLD ${ip}`, () => isIPv6_OLD(ip));
  }
});

group('isIPv6 false', () => {
  for (const ip of [...v4_ips, ...invalid_ips]) {
    assert.ok(!isIPv6_NEW(ip));
    assert.ok(!isIPv6_OLD(ip));
    bench(`OLD ${ip}`, () => isIPv6_OLD(ip));
    bench(`NEW ${ip}`, () => isIPv6_NEW(ip));
  }
});

group('isIP true', () => {
  for (const ip of v4_ips) {
    assert.strictEqual(isIP_NEW(ip), 4);
    assert.strictEqual(isIP_OLD(ip), 4);
    bench(`NEW ${ip}`, () => isIP_NEW(ip));
    bench(`OLD ${ip}`, () => isIP_OLD(ip));
  }
  for (const ip of v6_ips) {
    assert.strictEqual(isIP_NEW(ip), 6);
    assert.strictEqual(isIP_OLD(ip), 6);
    bench(`NEW ${ip}`, () => isIP_NEW(ip));
    bench(`OLD ${ip}`, () => isIP_OLD(ip));
  }
});

group('isIP false', () => {
  for (const ip of invalid_ips) {
    assert.strictEqual(isIP_NEW(ip), 0);
    assert.strictEqual(isIP_OLD(ip), 0);
    bench(`NEW ${ip}`, () => isIP_NEW(ip));
    bench(`OLD ${ip}`, () => isIP_OLD(ip));
  }
});

await run();
And the mitata results:
❱ bun ./isip-regex.mjs
cpu: AMD Ryzen 7 7735HS with Radeon Graphics
runtime: bun 1.0.23 (x64-linux)

benchmark                                        time (avg)             (min … max)       p75       p99      p995
----------------------------------------------------------------------------------- -----------------------------
baseline                                     423.88 ps/iter   (418.7 ps … 14.47 ns)  419.8 ps  439.8 ps  573.1 ps

summary
  baseline

• isIPv4 true
----------------------------------------------------------------------------------- -----------------------------
NEW 0.0.0.0                                  192.26 ns/iter (191.02 ns … 197.67 ns) 192.32 ns 196.52 ns 196.58 ns
OLD 0.0.0.0                                  129.38 ns/iter (128.13 ns … 133.46 ns)  129.5 ns 132.66 ns 132.93 ns
NEW 127.0.0.1                                193.82 ns/iter (193.01 ns … 198.26 ns) 193.89 ns 197.73 ns 197.99 ns
OLD 127.0.0.1                                187.35 ns/iter (180.63 ns … 218.25 ns) 187.81 ns 204.93 ns 214.89 ns
NEW 255.255.255.255                          132.72 ns/iter (131.59 ns … 183.88 ns) 132.49 ns 138.58 ns 138.77 ns
OLD 255.255.255.255                          427.16 ns/iter (420.43 ns … 435.97 ns)    428 ns 435.26 ns 435.97 ns
NEW 192.168.0.1                              177.64 ns/iter (175.41 ns … 246.14 ns) 177.36 ns 185.63 ns 219.55 ns
OLD 192.168.0.1                              250.84 ns/iter (241.19 ns … 269.38 ns) 252.78 ns 267.34 ns  268.1 ns
NEW 192.0.2.33                               208.01 ns/iter (205.78 ns … 254.43 ns) 207.96 ns 214.57 ns 215.11 ns
OLD 192.0.2.33                               214.24 ns/iter (209.95 ns … 222.26 ns)  214.9 ns 219.57 ns 220.61 ns

summary for isIPv4 true
  OLD 0.0.0.0
   1.03x faster than NEW 255.255.255.255
   1.37x faster than NEW 192.168.0.1
   1.45x faster than OLD 127.0.0.1
   1.49x faster than NEW 0.0.0.0
   1.5x faster than NEW 127.0.0.1
   1.61x faster than NEW 192.0.2.33
   1.66x faster than OLD 192.0.2.33
   1.94x faster than OLD 192.168.0.1
   3.3x faster than OLD 255.255.255.255

• isIPv4 false
----------------------------------------------------------------------------------- -----------------------------
OLD ::1                                       84.53 ns/iter   (82.96 ns … 97.57 ns)  85.02 ns  88.94 ns  89.62 ns
NEW ::1                                       88.92 ns/iter      (88 ns … 91.75 ns)  88.91 ns  90.71 ns  90.96 ns
OLD ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff  341.36 ns/iter  (339.2 ns … 347.96 ns) 342.65 ns 346.95 ns 347.96 ns
NEW ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff  348.46 ns/iter (347.25 ns … 356.42 ns) 348.34 ns 355.89 ns 356.42 ns
OLD 2001:db8:85a3:0:0:8a2e:370:7334          339.25 ns/iter (335.65 ns … 346.99 ns) 339.95 ns 346.75 ns 346.99 ns
NEW 2001:db8:85a3:0:0:8a2e:370:7334          344.55 ns/iter (343.12 ns … 358.76 ns) 344.39 ns 351.23 ns 358.76 ns
OLD ::ffff:192.0.2.33                        192.41 ns/iter  (189.3 ns … 196.81 ns) 193.01 ns 196.33 ns 196.61 ns
NEW ::ffff:192.0.2.33                        207.94 ns/iter (206.34 ns … 212.66 ns) 208.09 ns 211.69 ns 211.98 ns
OLD                                           23.18 ns/iter   (22.46 ns … 31.46 ns)   23.2 ns  23.75 ns  24.14 ns
NEW                                            28.9 ns/iter   (28.56 ns … 32.33 ns)  28.96 ns   29.5 ns  29.62 ns
OLD .                                         44.58 ns/iter    (44.01 ns … 50.8 ns)  44.63 ns  45.51 ns  45.57 ns
NEW .                                         53.39 ns/iter   (52.96 ns … 54.92 ns)  53.43 ns   54.5 ns   54.6 ns
OLD 0.0.0                                    200.64 ns/iter (197.73 ns … 250.12 ns) 200.91 ns 204.76 ns 205.33 ns
NEW 0.0.0                                    197.54 ns/iter  (195.98 ns … 202.1 ns) 198.07 ns 201.27 ns 201.51 ns
OLD 10.168.209.250.1                         566.95 ns/iter (562.56 ns … 576.07 ns) 568.13 ns 576.07 ns 576.07 ns
NEW 10.168.209.250.1                         545.95 ns/iter (543.22 ns … 553.84 ns) 546.75 ns 551.63 ns 553.84 ns
OLD ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffg  340.82 ns/iter (320.32 ns … 356.01 ns) 348.78 ns 354.03 ns 356.01 ns
NEW ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffg  349.02 ns/iter (347.34 ns … 355.78 ns) 348.97 ns 355.54 ns 355.78 ns

summary for isIPv4 false
  OLD 
   1.25x faster than NEW 
   1.92x faster than OLD .
   2.3x faster than NEW .
   3.65x faster than OLD ::1
   3.84x faster than NEW ::1
   8.3x faster than OLD ::ffff:192.0.2.33
   8.52x faster than NEW 0.0.0
   8.66x faster than OLD 0.0.0
   8.97x faster than NEW ::ffff:192.0.2.33
   14.64x faster than OLD 2001:db8:85a3:0:0:8a2e:370:7334
   14.7x faster than OLD ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffg
   14.73x faster than OLD ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
   14.86x faster than NEW 2001:db8:85a3:0:0:8a2e:370:7334
   15.03x faster than NEW ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
   15.06x faster than NEW ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffg
   23.55x faster than NEW 10.168.209.250.1
   24.46x faster than OLD 10.168.209.250.1

• isIPv6 true
----------------------------------------------------------------------------------- -----------------------------
NEW ::1                                      366.93 ns/iter (364.38 ns … 376.92 ns) 367.05 ns 373.88 ns 376.92 ns
OLD ::1                                      392.65 ns/iter (390.16 ns … 398.58 ns) 393.35 ns 397.89 ns 398.58 ns
NEW ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff  311.77 ns/iter (310.11 ns … 317.39 ns) 311.99 ns 316.93 ns 317.39 ns
OLD ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff  332.77 ns/iter (331.04 ns … 338.61 ns) 332.81 ns 338.39 ns 338.61 ns
NEW 2001:db8:85a3:0:0:8a2e:370:7334          296.47 ns/iter (293.48 ns … 304.52 ns) 297.68 ns 303.79 ns 304.52 ns
OLD 2001:db8:85a3:0:0:8a2e:370:7334          324.48 ns/iter (322.13 ns … 333.82 ns) 324.88 ns 333.17 ns 333.82 ns
NEW ::ffff:192.0.2.33                        651.91 ns/iter (646.05 ns … 656.82 ns) 652.56 ns 656.82 ns 656.82 ns
OLD ::ffff:192.0.2.33                        701.33 ns/iter (697.62 ns … 712.62 ns) 702.29 ns 712.62 ns 712.62 ns

summary for isIPv6 true
  NEW 2001:db8:85a3:0:0:8a2e:370:7334
   1.05x faster than NEW ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
   1.09x faster than OLD 2001:db8:85a3:0:0:8a2e:370:7334
   1.12x faster than OLD ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
   1.24x faster than NEW ::1
   1.32x faster than OLD ::1
   2.2x faster than NEW ::ffff:192.0.2.33
   2.37x faster than OLD ::ffff:192.0.2.33

• isIPv6 false
----------------------------------------------------------------------------------- -----------------------------
OLD 0.0.0.0                                  460.07 ns/iter (457.32 ns … 466.39 ns) 460.86 ns    465 ns 466.39 ns
NEW 0.0.0.0                                  432.86 ns/iter (430.15 ns … 441.07 ns) 433.77 ns  439.5 ns 441.07 ns
OLD 127.0.0.1                                632.84 ns/iter (628.93 ns … 643.37 ns) 634.01 ns 643.37 ns 643.37 ns
NEW 127.0.0.1                                615.81 ns/iter (611.14 ns … 624.04 ns)  616.6 ns 624.04 ns 624.04 ns
OLD 255.255.255.255                          687.09 ns/iter (684.03 ns … 701.46 ns)  688.4 ns 701.46 ns 701.46 ns
NEW 255.255.255.255                          667.17 ns/iter (664.74 ns … 679.73 ns) 667.83 ns 679.73 ns 679.73 ns
OLD 192.168.0.1                              671.53 ns/iter (648.33 ns … 681.98 ns) 676.26 ns 681.98 ns 681.98 ns
NEW 192.168.0.1                              654.03 ns/iter (648.39 ns … 658.38 ns) 656.79 ns 658.38 ns 658.38 ns
OLD 192.0.2.33                               665.46 ns/iter (661.05 ns … 676.53 ns) 665.11 ns 676.53 ns 676.53 ns
NEW 192.0.2.33                               638.65 ns/iter (636.02 ns … 650.87 ns) 639.27 ns 650.87 ns 650.87 ns
OLD                                          114.35 ns/iter  (112.28 ns … 125.2 ns)    114 ns 122.19 ns 122.31 ns
NEW                                          103.13 ns/iter (101.97 ns … 122.88 ns) 103.22 ns 105.44 ns 105.66 ns
OLD .                                        174.06 ns/iter (172.47 ns … 177.36 ns) 174.27 ns 177.14 ns 177.18 ns
NEW .                                        155.64 ns/iter (154.18 ns … 183.36 ns) 155.43 ns  158.9 ns 171.22 ns
OLD 0.0.0                                    434.34 ns/iter (430.78 ns … 443.73 ns) 435.51 ns 441.96 ns 443.73 ns
NEW 0.0.0                                    414.87 ns/iter    (412 ns … 423.88 ns) 415.18 ns 423.27 ns 423.88 ns
OLD 10.168.209.250.1                         602.29 ns/iter (590.25 ns … 613.83 ns) 603.65 ns 613.83 ns 613.83 ns
NEW 10.168.209.250.1                         584.42 ns/iter (580.91 ns … 593.64 ns) 585.48 ns 593.64 ns 593.64 ns
OLD ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffg     3.1 µs/iter     (3.09 µs … 3.12 µs)    3.1 µs   3.12 µs   3.12 µs
NEW ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffg    3.06 µs/iter     (3.06 µs … 3.09 µs)   3.06 µs   3.09 µs   3.09 µs

summary for isIPv6 false
  NEW 
   1.11x faster than OLD 
   1.51x faster than NEW .
   1.69x faster than OLD .
   4.02x faster than NEW 0.0.0
   4.2x faster than NEW 0.0.0.0
   4.21x faster than OLD 0.0.0
   4.46x faster than OLD 0.0.0.0
   5.67x faster than NEW 10.168.209.250.1
   5.84x faster than OLD 10.168.209.250.1
   5.97x faster than NEW 127.0.0.1
   6.14x faster than OLD 127.0.0.1
   6.19x faster than NEW 192.0.2.33
   6.34x faster than NEW 192.168.0.1
   6.45x faster than OLD 192.0.2.33
   6.47x faster than NEW 255.255.255.255
   6.51x faster than OLD 192.168.0.1
   6.66x faster than OLD 255.255.255.255
   29.69x faster than NEW ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffg
   30.03x faster than OLD ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffg

• isIP true
----------------------------------------------------------------------------------- -----------------------------
NEW 0.0.0.0                                  198.09 ns/iter (195.32 ns … 240.93 ns) 197.94 ns 215.26 ns 215.71 ns
OLD 0.0.0.0                                  124.71 ns/iter (123.27 ns … 164.09 ns)  125.4 ns 128.06 ns 128.45 ns
NEW 127.0.0.1                                188.56 ns/iter (186.75 ns … 193.11 ns) 188.82 ns 192.66 ns 192.73 ns
OLD 127.0.0.1                                186.76 ns/iter (181.61 ns … 192.17 ns) 187.51 ns 191.32 ns 191.92 ns
NEW 255.255.255.255                          137.96 ns/iter (137.01 ns … 146.28 ns) 137.93 ns 141.45 ns 142.43 ns
OLD 255.255.255.255                          419.94 ns/iter (412.12 ns … 427.75 ns) 421.22 ns 427.55 ns 427.75 ns
NEW 192.168.0.1                              182.24 ns/iter (180.34 ns … 197.66 ns) 182.43 ns 185.73 ns 190.54 ns
OLD 192.168.0.1                               249.3 ns/iter (239.23 ns … 267.57 ns) 249.99 ns 264.94 ns  265.6 ns
NEW 192.0.2.33                               211.71 ns/iter  (209.5 ns … 232.65 ns) 212.06 ns 220.42 ns 227.55 ns
OLD 192.0.2.33                               207.28 ns/iter (202.66 ns … 213.19 ns) 207.96 ns 211.26 ns 211.98 ns
NEW ::1                                      434.34 ns/iter (430.34 ns … 441.62 ns) 436.25 ns 441.14 ns 441.62 ns
OLD ::1                                      463.61 ns/iter (457.37 ns … 513.57 ns)  466.1 ns  510.3 ns 513.57 ns
NEW ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff  644.42 ns/iter (635.07 ns … 732.94 ns) 645.04 ns 732.94 ns 732.94 ns
OLD ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff  663.42 ns/iter (654.28 ns … 693.19 ns) 665.85 ns 693.19 ns 693.19 ns
NEW 2001:db8:85a3:0:0:8a2e:370:7334          630.92 ns/iter (624.16 ns … 713.28 ns) 632.03 ns 713.28 ns 713.28 ns
OLD 2001:db8:85a3:0:0:8a2e:370:7334           684.1 ns/iter    (669.12 ns … 734 ns) 688.02 ns    734 ns    734 ns
NEW ::ffff:192.0.2.33                        832.32 ns/iter (817.07 ns … 917.69 ns) 835.07 ns 917.69 ns 917.69 ns
OLD ::ffff:192.0.2.33                        890.67 ns/iter (870.68 ns … 948.59 ns) 897.18 ns 948.59 ns 948.59 ns

summary for isIP true
  OLD 0.0.0.0
   1.11x faster than NEW 255.255.255.255
   1.46x faster than NEW 192.168.0.1
   1.5x faster than OLD 127.0.0.1
   1.51x faster than NEW 127.0.0.1
   1.59x faster than NEW 0.0.0.0
   1.66x faster than OLD 192.0.2.33
   1.7x faster than NEW 192.0.2.33
   2x faster than OLD 192.168.0.1
   3.37x faster than OLD 255.255.255.255
   3.48x faster than NEW ::1
   3.72x faster than OLD ::1
   5.06x faster than NEW 2001:db8:85a3:0:0:8a2e:370:7334
   5.17x faster than NEW ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
   5.32x faster than OLD ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
   5.49x faster than OLD 2001:db8:85a3:0:0:8a2e:370:7334
   6.67x faster than NEW ::ffff:192.0.2.33
   7.14x faster than OLD ::ffff:192.0.2.33

• isIP false
----------------------------------------------------------------------------------- -----------------------------
NEW                                          120.54 ns/iter  (116.11 ns … 233.2 ns)  118.9 ns 218.28 ns 229.41 ns
OLD                                          135.01 ns/iter (133.09 ns … 139.73 ns) 136.04 ns  138.2 ns  138.5 ns
NEW .                                         197.2 ns/iter (194.49 ns … 206.13 ns) 198.56 ns 203.63 ns 204.53 ns
OLD .                                        214.12 ns/iter (211.15 ns … 217.94 ns) 215.77 ns 217.68 ns 217.92 ns
NEW 0.0.0                                    607.88 ns/iter     (602 ns … 615.3 ns)  609.6 ns  615.3 ns  615.3 ns
OLD 0.0.0                                    641.02 ns/iter (634.54 ns … 650.19 ns) 644.35 ns 650.19 ns 650.19 ns
NEW 10.168.209.250.1                           1.18 µs/iter     (1.16 µs … 1.21 µs)   1.19 µs   1.21 µs   1.21 µs
OLD 10.168.209.250.1                           1.21 µs/iter     (1.21 µs … 1.25 µs)   1.22 µs   1.25 µs   1.25 µs
NEW ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffg    3.42 µs/iter     (3.39 µs … 3.48 µs)   3.42 µs   3.48 µs   3.48 µs
OLD ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffg    3.47 µs/iter      (3.46 µs … 3.5 µs)   3.47 µs    3.5 µs    3.5 µs

summary for isIP false
  NEW 
   1.12x faster than OLD 
   1.64x faster than NEW .
   1.78x faster than OLD .
   5.04x faster than NEW 0.0.0
   5.32x faster than OLD 0.0.0
   9.82x faster than NEW 10.168.209.250.1
   10.08x faster than OLD 10.168.209.250.1
   28.38x faster than NEW ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffg
   28.78x faster than OLD ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffg

I sometimes get unreliable results when using mitata and bun so I also created benchmarks using the benchmark package. These tend to be more consistent for me, especially when running them multiple times.

Here are benchmark based benchmarks:
import Benchmark from 'benchmark';
import assert from 'node:assert';

// Original regexes
const v4Seg_OLD = '(?:[0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])';
const v4Str_OLD = `(${v4Seg_OLD}[.]){3}${v4Seg_OLD}`;
const IPv4Reg_OLD = new RegExp(`^${v4Str_OLD}$`);

const v6Seg_OLD = '(?:[0-9a-fA-F]{1,4})';
const IPv6Reg_OLD = new RegExp(
  '^(' +
    `(?:${v6Seg_OLD}:){7}(?:${v6Seg_OLD}|:)|` +
    `(?:${v6Seg_OLD}:){6}(?:${v4Str_OLD}|:${v6Seg_OLD}|:)|` +
    `(?:${v6Seg_OLD}:){5}(?::${v4Str_OLD}|(:${v6Seg_OLD}){1,2}|:)|` +
    `(?:${v6Seg_OLD}:){4}(?:(:${v6Seg_OLD}){0,1}:${v4Str_OLD}|(:${v6Seg_OLD}){1,3}|:)|` +
    `(?:${v6Seg_OLD}:){3}(?:(:${v6Seg_OLD}){0,2}:${v4Str_OLD}|(:${v6Seg_OLD}){1,4}|:)|` +
    `(?:${v6Seg_OLD}:){2}(?:(:${v6Seg_OLD}){0,3}:${v4Str_OLD}|(:${v6Seg_OLD}){1,5}|:)|` +
    `(?:${v6Seg_OLD}:){1}(?:(:${v6Seg_OLD}){0,4}:${v4Str_OLD}|(:${v6Seg_OLD}){1,6}|:)|` +
    `(?::((?::${v6Seg_OLD}){0,5}:${v4Str_OLD}|(?::${v6Seg_OLD}){1,7}|:))` +
    ')(%[0-9a-zA-Z-.:]{1,})?$',
);

function isIPv4_OLD(s) {
  return IPv4Reg_OLD.test(s);
}
function isIPv6_OLD(s) {
  return IPv6Reg_OLD.test(s);
}
function isIP_OLD(s) {
  if (isIPv4_OLD(s)) return 4;
  if (isIPv6_OLD(s)) return 6;
  return 0;
}

// With performance optimizations from node.js project PR
const v4Seg_NEW = '(?:25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9][0-9]|[0-9])';
const v4Str_NEW = `(?:${v4Seg_NEW}\\.){3}${v4Seg_NEW}`;
const IPv4Reg_NEW = new RegExp(`^${v4Str_NEW}$`);

const v6Seg_NEW = '(?:[0-9a-fA-F]{1,4})';
const IPv6Reg2_NEW = new RegExp(
  '^(?:' +
    `(?:${v6Seg_NEW}:){7}(?:${v6Seg_NEW}|:)|` +
    `(?:${v6Seg_NEW}:){6}(?:${v4Str_NEW}|:${v6Seg_NEW}|:)|` +
    `(?:${v6Seg_NEW}:){5}(?::${v4Str_NEW}|(?::${v6Seg_NEW}){1,2}|:)|` +
    `(?:${v6Seg_NEW}:){4}(?:(?::${v6Seg_NEW}){0,1}:${v4Str_NEW}|(?::${v6Seg_NEW}){1,3}|:)|` +
    `(?:${v6Seg_NEW}:){3}(?:(?::${v6Seg_NEW}){0,2}:${v4Str_NEW}|(?::${v6Seg_NEW}){1,4}|:)|` +
    `(?:${v6Seg_NEW}:){2}(?:(?::${v6Seg_NEW}){0,3}:${v4Str_NEW}|(?::${v6Seg_NEW}){1,5}|:)|` +
    `(?:${v6Seg_NEW}:){1}(?:(?::${v6Seg_NEW}){0,4}:${v4Str_NEW}|(?::${v6Seg_NEW}){1,6}|:)|` +
    `(?::(?:(?::${v6Seg_NEW}){0,5}:${v4Str_NEW}|(?::${v6Seg_NEW}){1,7}|:))` +
    ')(?:%[0-9a-zA-Z-.:]{1,})?$',
);

function isIPv4_NEW(s) {
  return IPv4Reg_NEW.test(s);
}
function isIPv6_NEW(s) {
  return IPv6Reg2_NEW.test(s);
}
function isIP_NEW(s) {
  if (isIPv4_NEW(s)) return 4;
  if (isIPv6_NEW(s)) return 6;
  return 0;
}

function run(group) {
  for (const cases of group) {
    const suite = new Benchmark.Suite();

    for (const [name, fn] of cases) {
      suite.add(name, fn);
    }

    suite
      .on('cycle', function (event) {
        console.log(String(event.target));
      })
      .on('complete', function () {
        console.log('Fastest is ' + this.filter('fastest').map('name') + '\n');
      })
      .run();
  }
}

const v4_ips = [
  '0.0.0.0',
  '127.0.0.1',
  '255.255.255.255',
  '192.168.0.1',
  '192.0.2.33',
];
const v6_ips = [
  '::1',
  'ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff',
  '2001:db8:85a3:0:0:8a2e:370:7334',
  '::ffff:192.0.2.33',
];
const invalid_ips = [
  '',
  '.',
  '0.0.0',
  '10.168.209.250.1',
  'ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffg',
];
/** @type {[string, ()=>void][][]} */
const cases = [[], [], [], [], []];

v4_ips.forEach((ip) => {
  assert.ok(isIPv4_OLD(ip));
  assert.ok(isIPv4_NEW(ip));
  assert.strictEqual(isIP_OLD(ip), 4);
  assert.strictEqual(isIP_NEW(ip), 4);

  cases[0].push([`isIPv4 ${ip} OLD`, () => isIPv4_OLD(ip)]);
  cases[0].push([`isIPv4 ${ip} NEW`, () => isIPv4_NEW(ip)]);

  cases[3].push([`isIP ${ip} OLD`, () => isIP_OLD(ip)]);
  cases[3].push([`isIP ${ip} NEW`, () => isIP_NEW(ip)]);
});

v6_ips.forEach((ip) => {
  assert.ok(isIPv6_OLD(ip));
  assert.ok(isIPv6_NEW(ip));
  assert.strictEqual(isIP_OLD(ip), 6);
  assert.strictEqual(isIP_NEW(ip), 6);

  cases[1].push([`isIPv6 ${ip} OLD`, () => isIPv6_OLD(ip)]);
  cases[1].push([`isIPv6 ${ip} NEW`, () => isIPv6_NEW(ip)]);

  cases[3].push([`isIP ${ip} OLD`, () => isIP_OLD(ip)]);
  cases[3].push([`isIP ${ip} NEW`, () => isIP_NEW(ip)]);
});

invalid_ips.forEach((ip) => {
  assert.ok(!isIPv4_OLD(ip));
  assert.ok(!isIPv4_NEW(ip));
  assert.ok(!isIPv6_OLD(ip));
  assert.ok(!isIPv6_NEW(ip));
  assert.strictEqual(isIP_OLD(ip), 0);
  assert.strictEqual(isIP_NEW(ip), 0);

  cases[2].push([`isIPv4 ${ip} OLD`, () => isIPv4_OLD(ip)]);
  cases[2].push([`isIPv4 ${ip} NEW`, () => isIPv4_NEW(ip)]);
  cases[2].push([`isIPv6 ${ip} OLD`, () => isIPv6_OLD(ip)]);
  cases[2].push([`isIPv6 ${ip} NEW`, () => isIPv6_NEW(ip)]);

  cases[4].push([`isIP ${ip} OLD`, () => isIP_OLD(ip)]);
  cases[4].push([`isIP ${ip} NEW`, () => isIP_NEW(ip)]);
});

run(cases);
And the benchmark results:
❱ bun ./isip-regex-benchmark.mjs
isIPv4 0.0.0.0 OLD x 7,430,361 ops/sec ±0.77% (95 runs sampled)
isIPv4 0.0.0.0 NEW x 5,001,232 ops/sec ±0.65% (96 runs sampled)
isIPv4 127.0.0.1 OLD x 5,066,198 ops/sec ±0.55% (94 runs sampled)
isIPv4 127.0.0.1 NEW x 5,163,993 ops/sec ±0.73% (93 runs sampled)
isIPv4 255.255.255.255 OLD x 2,378,886 ops/sec ±0.30% (96 runs sampled)
isIPv4 255.255.255.255 NEW x 6,878,658 ops/sec ±1.01% (91 runs sampled)
isIPv4 192.168.0.1 OLD x 3,899,376 ops/sec ±0.45% (94 runs sampled)
isIPv4 192.168.0.1 NEW x 5,429,998 ops/sec ±0.71% (93 runs sampled)
isIPv4 192.0.2.33 OLD x 4,554,487 ops/sec ±0.58% (95 runs sampled)
isIPv4 192.0.2.33 NEW x 4,758,270 ops/sec ±0.67% (93 runs sampled)
Fastest is isIPv4 0.0.0.0 OLD

isIPv6 ::1 OLD x 2,549,095 ops/sec ±0.39% (96 runs sampled)
isIPv6 ::1 NEW x 2,747,282 ops/sec ±0.32% (98 runs sampled)
isIPv6 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff OLD x 2,931,636 ops/sec ±0.43% (96 runs sampled)
isIPv6 ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff NEW x 3,141,376 ops/sec ±0.39% (96 runs sampled)
isIPv6 2001:db8:85a3:0:0:8a2e:370:7334 OLD x 3,050,789 ops/sec ±0.50% (96 runs sampled)
isIPv6 2001:db8:85a3:0:0:8a2e:370:7334 NEW x 3,341,314 ops/sec ±0.38% (95 runs sampled)
isIPv6 ::ffff:192.0.2.33 OLD x 1,426,003 ops/sec ±0.24% (98 runs sampled)
isIPv6 ::ffff:192.0.2.33 NEW x 1,547,941 ops/sec ±0.21% (97 runs sampled)
Fastest is isIPv6 2001:db8:85a3:0:0:8a2e:370:7334 NEW

isIPv4  OLD x 32,833,578 ops/sec ±3.34% (75 runs sampled)
isIPv4  NEW x 33,117,700 ops/sec ±3.60% (74 runs sampled)
isIPv6  OLD x 7,835,056 ops/sec ±0.94% (93 runs sampled)
isIPv6  NEW x 8,812,189 ops/sec ±1.29% (88 runs sampled)
isIPv4 . OLD x 18,363,757 ops/sec ±2.45% (81 runs sampled)
isIPv4 . NEW x 18,424,040 ops/sec ±2.87% (79 runs sampled)
isIPv6 . OLD x 5,159,319 ops/sec ±0.54% (95 runs sampled)
isIPv6 . NEW x 5,875,177 ops/sec ±0.84% (94 runs sampled)
isIPv4 0.0.0 OLD x 4,461,265 ops/sec ±0.84% (92 runs sampled)
isIPv4 0.0.0 NEW x 4,759,381 ops/sec ±0.73% (93 runs sampled)
isIPv6 0.0.0 OLD x 2,305,245 ops/sec ±0.42% (95 runs sampled)
isIPv6 0.0.0 NEW x 2,449,716 ops/sec ±0.49% (96 runs sampled)
isIPv4 10.168.209.250.1 OLD x 1,642,448 ops/sec ±0.42% (93 runs sampled)
isIPv4 10.168.209.250.1 NEW x 1,712,224 ops/sec ±0.54% (95 runs sampled)
isIPv6 10.168.209.250.1 OLD x 1,657,156 ops/sec ±0.43% (94 runs sampled)
isIPv6 10.168.209.250.1 NEW x 1,748,159 ops/sec ±0.26% (97 runs sampled)
isIPv4 ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffg OLD x 2,895,545 ops/sec ±0.60% (96 runs sampled)
isIPv4 ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffg NEW x 2,785,398 ops/sec ±0.35% (98 runs sampled)
isIPv6 ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffg OLD x 318,115 ops/sec ±0.21% (98 runs sampled)
isIPv6 ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffg NEW x 320,937 ops/sec ±0.36% (100 runs sampled)
Fastest is isIPv4  NEW,isIPv4  OLD

isIP 0.0.0.0 OLD x 7,049,636 ops/sec ±0.86% (88 runs sampled)
isIP 0.0.0.0 NEW x 4,860,774 ops/sec ±0.59% (92 runs sampled)
isIP 127.0.0.1 OLD x 4,943,284 ops/sec ±0.61% (94 runs sampled)
isIP 127.0.0.1 NEW x 5,016,337 ops/sec ±0.72% (93 runs sampled)
isIP 255.255.255.255 OLD x 2,332,741 ops/sec ±0.33% (94 runs sampled)
isIP 255.255.255.255 NEW x 6,749,868 ops/sec ±0.90% (91 runs sampled)
isIP 192.168.0.1 OLD x 3,826,877 ops/sec ±0.50% (94 runs sampled)
isIP 192.168.0.1 NEW x 5,298,357 ops/sec ±0.76% (93 runs sampled)
isIP 192.0.2.33 OLD x 4,450,107 ops/sec ±0.51% (93 runs sampled)
isIP 192.0.2.33 NEW x 4,647,593 ops/sec ±0.66% (95 runs sampled)
isIP ::1 OLD x 2,072,369 ops/sec ±0.32% (98 runs sampled)
isIP ::1 NEW x 2,204,738 ops/sec ±0.20% (95 runs sampled)
isIP ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff OLD x 1,446,882 ops/sec ±0.44% (92 runs sampled)
isIP ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff NEW x 1,471,333 ops/sec ±0.31% (98 runs sampled)
isIP 2001:db8:85a3:0:0:8a2e:370:7334 OLD x 1,432,696 ops/sec ±0.21% (95 runs sampled)
isIP 2001:db8:85a3:0:0:8a2e:370:7334 NEW x 1,524,187 ops/sec ±0.22% (98 runs sampled)
isIP ::ffff:192.0.2.33 OLD x 1,102,668 ops/sec ±0.27% (95 runs sampled)
isIP ::ffff:192.0.2.33 NEW x 1,186,355 ops/sec ±0.20% (99 runs sampled)
Fastest is isIP 0.0.0.0 OLD

isIP  OLD x 6,640,286 ops/sec ±0.82% (93 runs sampled)
isIP  NEW x 7,849,702 ops/sec ±0.94% (91 runs sampled)
isIP . OLD x 4,180,209 ops/sec ±0.57% (95 runs sampled)
isIP . NEW x 4,680,992 ops/sec ±0.73% (92 runs sampled)
isIP 0.0.0 OLD x 1,541,390 ops/sec ±0.29% (96 runs sampled)
isIP 0.0.0 NEW x 1,662,312 ops/sec ±0.37% (94 runs sampled)
isIP 10.168.209.250.1 OLD x 818,861 ops/sec ±0.14% (98 runs sampled)
isIP 10.168.209.250.1 NEW x 846,496 ops/sec ±0.27% (97 runs sampled)
isIP ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffg OLD x 286,284 ops/sec ±0.09% (100 runs sampled)
isIP ffff:ffff:ffff:ffff:ffff:ffff:ffff:fffg NEW x 286,676 ops/sec ±0.13% (100 runs sampled)
Fastest is isIP  NEW

The performance depends on the specific string that's passed to the functions. For the IP strings I benchmarked, only 0.0.0.0 was notably faster in isIPv4/isIP with the old code. On average there was a 16.63% speed up (in the benchmark powered results).

@dsonet
Copy link

dsonet commented Jan 19, 2024

Since the changes were making the capturing group to be non-capturing, I suppose it would also use less memory?

@Jarred-Sumner
Copy link
Collaborator

Awesome. Thank you .

@Jarred-Sumner Jarred-Sumner merged commit 1c5b318 into oven-sh:main Jan 20, 2024
26 of 31 checks passed
@Uzlopak
Copy link

Uzlopak commented Jan 22, 2024

@Jarred-Sumner

Well... #4691

This will not motivate people to participate...

@maxmilton
Copy link
Contributor Author

@Uzlopak sorry, that's my bad. I did look for an existing issue but it seems I didn't search through the existing PRs!

ryoppippi pushed a commit to ryoppippi/bun that referenced this pull request Feb 1, 2024
…#8271)

Copy over the `isIPv4`/`isIPv6` performance improvements from
<nodejs/node#49568>.
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

Successfully merging this pull request may close these issues.

4 participants