Skip to content

Integer/Char pattern matching output is not optimal #7511

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

Open
cometkim opened this issue May 23, 2025 · 1 comment
Open

Integer/Char pattern matching output is not optimal #7511

cometkim opened this issue May 23, 2025 · 1 comment

Comments

@cometkim
Copy link
Member

cometkim commented May 23, 2025

The integer/Char pattern matching optimization output is worse than that of the plain switch table.

For this example

benchmark.mjs
import { bench, run, barplot, summary, do_not_optimize } from "mitata";
import fc from "fast-check";

const samples = fc.sample(
  fc.integer({ min: 0, max: 256 }),
  { numRuns: 10000, seed: 42 }
);

function isFinalIf(ch) {
  return (
    ch === 48
    || ch === 49
    || ch === 50
    || ch === 51
    || ch === 52
    || ch === 53
    || ch === 54
    || ch === 55
    || ch === 56
    || ch === 57
    || ch === 65
    || ch === 66
    || ch === 67
    || ch === 68
    || ch === 69
    || ch === 70
    || ch === 71
    || ch === 72
    || ch === 73
    || ch === 74
    || ch === 75
    || ch === 76
    || ch === 77
    || ch === 78
    || ch === 79
    || ch === 80
    || ch === 82
    || ch === 83
    || ch === 84
    || ch === 85
    || ch === 86
    || ch === 87
    || ch === 88
    || ch === 89
    || ch === 90
    || ch === 97
    || ch === 99
    || ch === 102
    || ch === 110
    || ch === 113
    || ch === 117
    || ch === 121
    || ch === 61
    || ch === 60
    || ch === 62
    || ch === 126
  );
}

function isFinalSwitch(ch) {
  switch (ch) {
    case 48:
    case 49:
    case 50:
    case 51:
    case 52:
    case 53:
    case 54:
    case 55:
    case 56:
    case 57:
    case 65:
    case 66:
    case 67:
    case 68:
    case 69:
    case 70:
    case 71:
    case 72:
    case 73:
    case 74:
    case 75:
    case 76:
    case 77:
    case 78:
    case 79:
    case 80:
    case 82:
    case 83:
    case 84:
    case 85:
    case 86:
    case 87:
    case 88:
    case 89:
    case 90:
    case 97:
    case 99:
    case 102:
    case 110:
    case 113:
    case 117:
    case 121:
    case 61:
    case 60:
    case 62:
    case 126:
      return true;
  }
  return false;
}

function isFinalIncludes(ch) {
  return [
    48,
    49,
    50,
    51,
    52,
    53,
    54,
    55,
    56,
    57,
    65,
    66,
    67,
    68,
    69,
    70,
    71,
    72,
    73,
    74,
    75,
    76,
    77,
    78,
    79,
    80,
    82,
    83,
    84,
    85,
    86,
    87,
    88,
    89,
    90,
    97,
    99,
    102,
    110,
    113,
    117,
    121,
    61,
    60,
    62,
    126,
  ].includes(ch);
}

/** Generated by ReScript v12.0.0-alpha.13
 *
 * [Open in Playground](https://rescript-lang.org/try?version=v12.0.0-alpha.13&module=esmodule&code=DYUwLgBAlgzgYlAdgQ2ASUZAvBAxgCwiwD4IBvAKAghgHcowC9DLqAfCAFgA4qIPOATj4cArAAYREUQEYpogEzyAzPM7zR8gGzyA7FK2b2ELTuNb953ueHHdku3LtK7qu+rtH+EXWe+7Lf2t-W29uBzCXMLcwjzCvDm4-RMDE4MTQjkEIrNSIQUyIGXEojhliqXKYspk8mQUnby1Gji0ck1KihS0iUjAAJwBXECkAfV6IADNUGBHqAF8KRaA)
 */
function isFinalRes(ch) {
  if (ch < 63) {
    if (ch >= 58) {
      return ch >= 60;
    } else {
      return ch >= 48;
    }
  }
  if (ch < 81) {
    return ch >= 65;
  }
  if (ch >= 127) {
    return false;
  }
  switch (ch) {
    case 81:
    case 91:
    case 92:
    case 93:
    case 94:
    case 95:
    case 96:
    case 98:
    case 100:
    case 101:
    case 103:
    case 104:
    case 105:
    case 106:
    case 107:
    case 108:
    case 109:
    case 111:
    case 112:
    case 114:
    case 115:
    case 116:
    case 118:
    case 119:
    case 120:
    case 122:
    case 123:
    case 124:
    case 125:
      return false;
    case 82:
    case 83:
    case 84:
    case 85:
    case 86:
    case 87:
    case 88:
    case 89:
    case 90:
    case 97:
    case 99:
    case 102:
    case 110:
    case 113:
    case 117:
    case 121:
    case 126:
      return true;
  }
  return false;
}

summary(() => {
  barplot(() => {
    bench("if-else chain", () => {
      do_not_optimize(samples.filter(isFinalIf));
    });

    bench("switch statement", () => {
      do_not_optimize(samples.filter(isFinalSwitch));
    });

    bench("array includes", () => {
      do_not_optimize(samples.filter(isFinalIncludes));
    });

    bench("ReScript pattern", () => {
      do_not_optimize(samples.filter(isFinalRes));
    }).baseline();
  });
});

run();

And gives the following results on my machine:

$ node --expose-gc benchmark.mjs
clk: ~4.48 GHz
cpu: Intel(R) Core(TM) Ultra 7 258V
runtime: node 24.0.2 (x64-linux)

...

  ReScript pattern
   1.75x slower than switch statement
   1.24x faster than if-else chain
   2.49x faster than array includes
$ bun --expose-gc benchmark.mjs
clk: ~4.49 GHz
cpu: Intel(R) Core(TM) Ultra 7 258V
runtime: bun 1.2.13 (x64-linux)

...

summary
  ReScript pattern
   2.22x slower than switch statement
   2.03x faster than array includes
   3.33x faster than if-else chain

According to this micro benchmark, the branches generated by ReScript are never faster than the simplest naive switch case.

@cometkim
Copy link
Member Author

cometkim commented May 23, 2025

Minified code (without the function name) size:

  • isFinalIf: 427
  • isFinalSwitch: 402
  • isFinalIncludes: 166
  • isFinalRes (ReScript): 244

Size compression looks good enough.

However, the if-else chain tends to gzip more effectively in the real world. Sequential case pass-through either.

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

1 participant