dnfield / dart_path_parsing

MIT License
27 stars 8 forks source link

Inlined the reading of code units #10

Closed gaaclarke closed 2 years ago

gaaclarke commented 2 years ago

I saw this function show up hot for customer: money. In local testing this is 12% faster in x86_64 AOT.

benchmark: ```dart // @dart=2.12 import 'dart:math' as math; void _skipOptionalSvgSpaces() {} final int _max = double.maxFinite.round(); bool _isValidRangeInt(int x) => -_max <= x && x <= _max; bool _isValidRange(double x) => -double.maxFinite <= x && x <= double.maxFinite; bool _isValidExponent(double x) => -37 <= x && x <= 38; class AsciiConstants { const AsciiConstants._(); /// `\t` (horizontal tab). static const int slashT = 9; /// `\n` (newline). static const int slashN = 10; /// `\f` (form feed). static const int slashF = 12; /// `\r` (carriage return). static const int slashR = 13; /// ` ` (space). static const int space = 32; /// `+` (plus). static const int plus = 43; /// `,` (comma). static const int comma = 44; /// `-` (minus). static const int minus = 45; /// `.` (period). static const int period = 46; /// 0 (the number zero). static const int number0 = 48; /// 1 (the number one). static const int number1 = 49; /// 2 (the number two). static const int number2 = 50; /// 3 (the number three). static const int number3 = 51; /// 4 (the number four). static const int number4 = 52; /// 5 (the number five). static const int number5 = 53; /// 6 (the number six). static const int number6 = 54; /// 7 (the number seven). static const int number7 = 55; /// 8 (the number eight). static const int number8 = 56; /// 9 (the number nine). static const int number9 = 57; /// A static const int upperA = 65; /// C static const int upperC = 67; /// E static const int upperE = 69; /// H static const int upperH = 72; /// L static const int upperL = 76; /// M static const int upperM = 77; /// Q static const int upperQ = 81; /// S static const int upperS = 83; /// T static const int upperT = 84; /// V static const int upperV = 86; /// Z static const int upperZ = 90; /// a static const int lowerA = 97; /// c static const int lowerC = 99; /// e static const int lowerE = 101; /// h static const int lowerH = 104; /// l static const int lowerL = 108; /// m static const int lowerM = 109; /// q static const int lowerQ = 113; /// s static const int lowerS = 115; /// t static const int lowerT = 116; /// v static const int lowerV = 118; /// x static const int lowerX = 120; /// z static const int lowerZ = 122; /// `~` (tilde) static const int tilde = 126; } class Foo { Foo(this._string) : _length = _string.length; int _idx = 0; int _length = 0; String _string; @pragma('vm:prefer-inline') int _readCodeUnit() { if (_idx >= _length) { return -1; } return _string.codeUnitAt(_idx++); } double _parseNumber() { _skipOptionalSvgSpaces(); // Read the sign. int sign = 1; int c = _readCodeUnit(); if (c == AsciiConstants.plus) { c = _readCodeUnit(); } else if (c == AsciiConstants.minus) { sign = -1; c = _readCodeUnit(); } if ((c < AsciiConstants.number0 || c > AsciiConstants.number9) && c != AsciiConstants.period) { throw StateError('First character of a number must be one of [0-9+-.].'); } // Read the integer part, build left-to-right. double integer = 0.0; while (AsciiConstants.number0 <= c && c <= AsciiConstants.number9) { integer = integer * 10 + (c - AsciiConstants.number0); c = _readCodeUnit(); } // Bail out early if this overflows. if (!_isValidRange(integer)) { throw StateError('Numeric overflow'); } double decimal = 0.0; if (c == AsciiConstants.period) { // read the decimals c = _readCodeUnit(); // There must be a least one digit following the . if (c < AsciiConstants.number0 || c > AsciiConstants.number9) throw StateError('There must be at least one digit following the .'); double frac = 1.0; while (AsciiConstants.number0 <= c && c <= AsciiConstants.number9) { frac *= 0.1; decimal += (c - AsciiConstants.number0) * frac; c = _readCodeUnit(); } } double number = integer + decimal; number *= sign; // read the exponent part if (_idx < _length && (c == AsciiConstants.lowerE || c == AsciiConstants.upperE) && (_string.codeUnitAt(_idx) != AsciiConstants.lowerX && _string.codeUnitAt(_idx) != AsciiConstants.lowerM)) { c = _readCodeUnit(); // read the sign of the exponent bool exponentIsNegative = false; if (c == AsciiConstants.plus) { c = _readCodeUnit(); } else if (c == AsciiConstants.minus) { c = _readCodeUnit(); exponentIsNegative = true; } // There must be an exponent if (c < AsciiConstants.number0 || c > AsciiConstants.number9) throw StateError('Missing exponent'); double exponent = 0.0; while (c >= AsciiConstants.number0 && c <= AsciiConstants.number9) { exponent *= 10.0; exponent += c - AsciiConstants.number0; c = _readCodeUnit(); } if (exponentIsNegative) { exponent = -exponent; } // Make sure exponent is valid. if (!_isValidExponent(exponent)) { throw StateError('Invalid exponent $exponent'); } if (exponent != 0) { number *= math.pow(10.0, exponent); } } // Don't return Infinity() or NaN(). if (!_isValidRange(number)) { throw StateError('Numeric overflow'); } // At this stage, c contains an unprocessed character, and _idx has // already been incremented. // If c == -1, the input was already at the end of the string, so no // further processing needs to occur. if (c != -1) { --_idx; // Put the unprocessed character back. // if (mode & kAllowTrailingWhitespace) _skipOptionalSvgSpacesOrDelimiter(); } return number; } void _skipOptionalSvgSpacesOrDelimiter() {} } void main() { int numIterations = 100000000; { Stopwatch stopWatch = Stopwatch(); stopWatch.start(); for (int i = 0; i < numIterations; ++i) { Foo('1234.356')._parseNumber(); } stopWatch.stop(); print('function ${stopWatch.elapsedMicroseconds / numIterations.toDouble()} µs'); } } ```
dnfield commented 2 years ago

CI failure is coveralls weirdness. Thanks!