petitparser / dart-petitparser

Dynamic parser combinators in Dart.
https://pub.dartlang.org/packages/petitparser
MIT License
457 stars 48 forks source link

Possible alternative use of switch that might be fast enough #150

Open RandalSchwartz opened 1 year ago

RandalSchwartz commented 1 year ago

You rewrote this to be faster:

  int fastParseOn(String buffer, int position) {
    final result = parseOn(Context(buffer, position));
    return switch (result) {
      Success(position: final position) => position,
      Failure() => -1
    };
  }

I wonder if this might have been fast enough to still use the switch:

  int fastParseOn(String buffer, int position) {
    final result = parseOn(Context(buffer, position));
    return switch (result) {
      final Success s => s.position,
      Failure _ => -1,
    };
  }

I understand that might work faster for sorting out types and mapping them to branches. I think Type() goes through a lot of extra work compared to Type _.

renggli commented 1 year ago

True, your code example (switch2) is even a tiny bit faster than the original if-then-else (control):

control             10,180.044μs [10,144.016μs; 10,216.072μs]
switch1             13,883.637μs [13,871.288μs; 13,895.986μs]
                    -36.391% [-36.846%; -35.935%]
switch2             10,051.340μs [10,038.367μs; 10,064.313μs]
                    1.257% [0.896%; 1.617%]
switch3             13,957.153μs [13,942.120μs; 13,972.186μs]
                    -37.114% [-37.648%; -36.581%]

However, switch2 also yields the warnings The generic type 'Success<dynamic>' / 'Failure<dynamic>' should have explicit type arguments but doesn't. Fixing that (switch3) makes everything slow like the original switch1.

import 'dart:math';

import 'package:benchmark/benchmark.dart';
import 'package:more/collection.dart';
import 'package:petitparser/petitparser.dart';

final random = Random(42);
const context = Context('', 0);
final input = IntegerRange(1024 * 1024)
    .map<Result<String>>((_) => random.nextBool()
        ? context.success<String>('success', random.nextInt(0xffff))
        : context.failure<String>('failure', random.nextInt(0xffff)))
    .toList(growable: false);

Benchmark exercise(int Function(Result<String>) underTest) => () {
      for (var i = 0; i < input.length; i++) {
        underTest(input[i]);
      }
    };

void main() {
  experiments(
    control: exercise((result) => result.isSuccess ? result.position : -1),
    experiments: {
      'switch1': exercise((result) => switch (result) {
            Success(position: final position) => position,
            Failure() => -1,
          }),
      'switch2': exercise((result) => switch (result) {
            final Success s => s.position,
            Failure _ => -1,
          }),
      'switch3': exercise((result) => switch (result) {
            final Success<String> s => s.position,
            Failure<String> _ => -1,
          }),
    },
  );
}
RandalSchwartz commented 1 year ago

The second case could be _ => -1,, which might speed up the match.

renggli commented 1 year ago

That makes it a bit faster, but not by much:

control             10,138.543μs [10,128.630μs; 10,148.455μs]
switch1             12,682.747μs [12,664.574μs; 12,700.920μs]
                    -25.095% [-25.285%; -24.905%]
switch2             10,060.908μs [10,058.125μs; 10,063.691μs]
                    0.765% [0.665%; 0.866%]
switch3             12,713.996μs [12,705.227μs; 12,722.765μs]
                    -25.403% [-25.565%; -25.242%]