// ignore_for_file: unnecessary_lambdas import 'dart:io'; import 'package:xml/xml.dart'; import 'package:xml/xml_events.dart'; import '../test/utils/examples.dart'; /// Measures the time it takes to run [function] in microseconds. /// /// It does so in two steps: /// /// - the code is warmed up for the duration of [warmup]; and /// - the code is benchmarked for the duration of [measure]. /// /// The resulting duration is the average time measured to run [function] once. double benchmark( void Function() function, { Duration warmup = const Duration(milliseconds: 200), Duration measure = const Duration(seconds: 2), }) { _benchmark(function, warmup); return _benchmark(function, measure); } double _benchmark(void Function() function, Duration duration) { final watch = Stopwatch(); final micros = duration.inMicroseconds; var count = 0; var elapsed = 0; watch.start(); while (elapsed < micros) { function(); elapsed = watch.elapsedMicroseconds; count++; } return elapsed / count; } /// Compare the speedup between [reference] and [comparison] in percentage. /// /// A result of 0 means that both reference and comparison run at the same /// speed. A positive number signifies a speedup, a negative one a slowdown. double percentChange(double reference, double comparison) => 100 * (reference - comparison) / reference; String characterData() { const string = '''a&bcnopqr'''; final builder = XmlBuilder(); builder.processing('xml', 'version="1.0"'); builder.element( 'character', nest: () { for (var i = 0; i < 20; i++) { builder.text('$string$string$string$string$string$string'); builder.element( 'foo', nest: () { builder.attribute('key', '$string$string$string$string'); }, ); } }, ); return builder.buildDocument().toString(); } final Map benchmarks = { 'atom': atomXml, 'books': booksXml, 'bookstore': bookstoreXml, 'complicated': complicatedXml, 'shiporder': shiporderXsd, 'decoding': characterData(), }; void main(List args) { final builder = XmlBuilder(); addBenchmarks(builder); final document = builder.buildDocument(); if (args.contains('xml')) { stdout.writeln(document.toXmlString(pretty: true)); } else { stdout.writeln( [ '', ...document .findAllElements('benchmark') .first .findAllElements('measure') .map((measure) => measure.getAttribute('name')), ].join(';'), ); stdout.write( document .findAllElements('benchmark') .map( (benchmark) => [ benchmark.getAttribute('name'), ...benchmark .findAllElements('time') .map((time) => time.innerText), ].join(';'), ) .join('\n'), ); } } void addBenchmarks(XmlBuilder builder) { builder.processing('xml', 'version="1.0"'); builder.element( 'benchmarks', nest: () { for (final entry in benchmarks.entries) { addBenchmark(builder, entry); } }, ); } void addBenchmark(XmlBuilder builder, MapEntry entry) { builder.element( 'benchmark', attributes: {'name': entry.key}, nest: () { final source = entry.value; final document = XmlDocument.parse(source); final parser = benchmark(() => XmlDocument.parse(source)); final streamEvents = benchmark(() => XmlEventDecoder().convert(source)); final streamNodes = benchmark( () => const XmlNodeDecoder().convert(XmlEventDecoder().convert(source)), ); final iterator = benchmark(() => parseEvents(source).toList()); final serialize = benchmark(() => document.toXmlString()); final serializePretty = benchmark( () => document.toXmlString(pretty: true), ); addMeasure(builder, 'parser', parser); addMeasure(builder, 'streamEvents', streamEvents, parser); addMeasure(builder, 'streamNodes', streamNodes, parser); addMeasure(builder, 'iterator', iterator, parser); addMeasure(builder, 'serialize', serialize); addMeasure(builder, 'serializePretty', serializePretty, serialize); }, ); } void addMeasure( XmlBuilder builder, String name, double measure, [ double? reference, ]) { builder.element( 'measure', attributes: {'name': name}, nest: () { builder.element('time', nest: measure.toStringAsFixed(6)); if (reference != null) { final speedup = percentChange(reference, measure); builder.element('speedup', nest: speedup.toStringAsFixed(2)); } }, ); }