From bfda03d2d0afbd31987aa4b6a92dee91586d92b3 Mon Sep 17 00:00:00 2001 From: Rapougnac Date: Tue, 28 May 2024 14:43:51 +0200 Subject: [PATCH] Bumped sdk version, updated deps, improved code. `Eterl` nows implements `Codec` --- README.md | 31 + benchmark/eterl_benchmark.dart | 1104 +++++++++++++++++++++++++++++++- lib/src/decoder.dart | 2 +- lib/src/encoder.dart | 12 +- lib/src/eterl.dart | 64 +- lib/src/max_int_value_vm.dart | 3 +- pubspec.yaml | 4 +- 7 files changed, 1184 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 570dd4a..7b1a689 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,7 @@ Eterl is a fast packer and unpacker for the External Term Erlang Format (version 131). ## Example + ```dart import 'package:eterl/eterl.dart'; @@ -25,3 +26,33 @@ void main() { - `Map`s - `List`s - `Tuple`s (only while decoding, tuples are converted into a `List`) + +## Benchmarks + +`dart run benchmark/eterl_benchmark.dart` + +```shell +[210ms:142us] Encoder: hello world + mean: 0.15 ± 0.11 us, median: 0.13 ± 0.017 us + ▉▂▄▂__________ 110 _____ sample size: 100 (averaged over 225 runs) + +[243ms:186us] Encoder: data + mean: 2.59 ± 0.76 us, median: 2.28 ± 0.24 us + ▆▉▄▂________________▁_______ sample size: 79 (averaged over 199 runs) + +[274ms:047us] Encoder: Complex data + mean: 49.75 ± 12.45 us, median: 42.58 ± 14.93 us + ▉▁▁▁_ sample size: 28 (averaged over 46 runs) + +[206ms:551us] Decoder: hello world + mean: 0.23 ± 0.12 us, median: 0.17 ± 0.10 us + ▉_▄▁_____________ sample size: 100 (averaged over 188 runs) + +[243ms:790us] Decoder: data + mean: 3.60 ± 1.17 us, median: 2.91 ± 1.46 us + ▉▆▁▁▃_ sample size: 75 (averaged over 165 runs) + +[266ms:378us] Decoder: Complex data + mean: 158.66 ± 312.078 us, median: 63.87 ± 12.28 us + ▉▅▁_▁____ 149 _____ sample size: 27 (averaged over 28 runs) +``` diff --git a/benchmark/eterl_benchmark.dart b/benchmark/eterl_benchmark.dart index 3067e4d..e432675 100644 --- a/benchmark/eterl_benchmark.dart +++ b/benchmark/eterl_benchmark.dart @@ -1,4 +1,4 @@ -import 'package:benchmark_harness/benchmark_harness.dart'; +import 'package:benchmark_runner/benchmark_runner.dart'; import 'package:eterl/eterl.dart'; const data = { @@ -21,34 +21,1094 @@ const data = { ], 'a__bb': '124', '124': 4, - '9': [] + '9': ['\u0000'] }, '6': null, }; -class EterlBenchmark extends BenchmarkBase { - const EterlBenchmark() : super('Eterl'); +void main(List args) { + group('Encoder', () { + benchmark('hello world', () { + eterl.pack('hello world'); + }); - @override - void run() { - final encoded = eterl.pack(data); - final decoded = eterl.unpack(encoded); - if (decoded.toString() != data.toString()) { - throw Exception('The decoded data is not equal to the original data'); - } - } + benchmark('data', () { + eterl.pack(data); + }); - @override - void setup() {} + benchmark('Complex data', () { + final mockData = { + ...data, + 'seven': BigInt.parse("1234567890123456789"), + 'eight': BigInt.parse('1234567890123456789012345678901234567890'), + '9': BigInt.parse('-${'1' * 1000}'), + }; - @override - void teardown() {} + eterl.pack(mockData); + }); + }); - static void main() { - const EterlBenchmark().report(); - } -} + group('Decoder', () { + benchmark('hello world', () { + eterl.unpack([ + 131, + 109, + 0, + 0, + 0, + 11, + 104, + 101, + 108, + 108, + 111, + 32, + 119, + 111, + 114, + 108, + 100, + ]); + }); + + benchmark('data', () { + eterl.unpack([ + 131, + 116, + 0, + 0, + 0, + 5, + 109, + 0, + 0, + 0, + 1, + 97, + 97, + 1, + 109, + 0, + 0, + 0, + 1, + 98, + 116, + 0, + 0, + 0, + 2, + 109, + 0, + 0, + 0, + 1, + 99, + 108, + 0, + 0, + 0, + 3, + 97, + 3, + 97, + 4, + 97, + 5, + 106, + 109, + 0, + 0, + 0, + 1, + 100, + 116, + 0, + 0, + 0, + 1, + 109, + 0, + 0, + 0, + 1, + 99, + 108, + 0, + 0, + 0, + 3, + 116, + 0, + 0, + 0, + 1, + 109, + 0, + 0, + 0, + 1, + 49, + 97, + 2, + 97, + 3, + 109, + 0, + 0, + 0, + 57, + 195, + 169, + 9, + 62, + 60, + 44, + 112, + 195, + 169, + 195, + 169, + 126, + 126, + 240, + 159, + 152, + 128, + 240, + 159, + 152, + 137, + 240, + 159, + 152, + 144, + 240, + 159, + 152, + 145, + 240, + 159, + 152, + 141, + 240, + 159, + 164, + 169, + 240, + 159, + 152, + 145, + 240, + 159, + 152, + 170, + 240, + 159, + 152, + 180, + 240, + 159, + 152, + 147, + 240, + 159, + 152, + 178, + 106, + 109, + 0, + 0, + 0, + 1, + 100, + 119, + 3, + 110, + 105, + 108, + 109, + 0, + 0, + 0, + 1, + 101, + 116, + 0, + 0, + 0, + 4, + 109, + 0, + 0, + 0, + 1, + 103, + 108, + 0, + 0, + 0, + 2, + 116, + 0, + 0, + 0, + 2, + 109, + 0, + 0, + 0, + 1, + 104, + 119, + 3, + 110, + 105, + 108, + 109, + 0, + 0, + 0, + 1, + 105, + 109, + 0, + 0, + 0, + 1, + 106, + 109, + 0, + 0, + 0, + 9, + 49, + 52, + 55, + 56, + 55, + 56, + 49, + 57, + 52, + 106, + 109, + 0, + 0, + 0, + 5, + 97, + 95, + 95, + 98, + 98, + 109, + 0, + 0, + 0, + 3, + 49, + 50, + 52, + 109, + 0, + 0, + 0, + 3, + 49, + 50, + 52, + 97, + 4, + 109, + 0, + 0, + 0, + 1, + 57, + 108, + 0, + 0, + 0, + 1, + 109, + 0, + 0, + 0, + 1, + 0, + 106, + 109, + 0, + 0, + 0, + 1, + 54, + 119, + 3, + 110, + 105, + 108, + ]); + }); -void main() { - EterlBenchmark.main(); + benchmark('Complex data', () { + eterl.unpack([ + 131, + 116, + 0, + 0, + 0, + 8, + 109, + 0, + 0, + 0, + 1, + 97, + 97, + 1, + 109, + 0, + 0, + 0, + 1, + 98, + 116, + 0, + 0, + 0, + 2, + 109, + 0, + 0, + 0, + 1, + 99, + 108, + 0, + 0, + 0, + 3, + 97, + 3, + 97, + 4, + 97, + 5, + 106, + 109, + 0, + 0, + 0, + 1, + 100, + 116, + 0, + 0, + 0, + 1, + 109, + 0, + 0, + 0, + 1, + 99, + 108, + 0, + 0, + 0, + 3, + 116, + 0, + 0, + 0, + 1, + 109, + 0, + 0, + 0, + 1, + 49, + 97, + 2, + 97, + 3, + 109, + 0, + 0, + 0, + 57, + 195, + 169, + 9, + 62, + 60, + 44, + 112, + 195, + 169, + 195, + 169, + 126, + 126, + 240, + 159, + 152, + 128, + 240, + 159, + 152, + 137, + 240, + 159, + 152, + 144, + 240, + 159, + 152, + 145, + 240, + 159, + 152, + 141, + 240, + 159, + 164, + 169, + 240, + 159, + 152, + 145, + 240, + 159, + 152, + 170, + 240, + 159, + 152, + 180, + 240, + 159, + 152, + 147, + 240, + 159, + 152, + 178, + 106, + 109, + 0, + 0, + 0, + 1, + 100, + 119, + 3, + 110, + 105, + 108, + 109, + 0, + 0, + 0, + 1, + 101, + 116, + 0, + 0, + 0, + 4, + 109, + 0, + 0, + 0, + 1, + 103, + 108, + 0, + 0, + 0, + 2, + 116, + 0, + 0, + 0, + 2, + 109, + 0, + 0, + 0, + 1, + 104, + 119, + 3, + 110, + 105, + 108, + 109, + 0, + 0, + 0, + 1, + 105, + 109, + 0, + 0, + 0, + 1, + 106, + 109, + 0, + 0, + 0, + 9, + 49, + 52, + 55, + 56, + 55, + 56, + 49, + 57, + 52, + 106, + 109, + 0, + 0, + 0, + 5, + 97, + 95, + 95, + 98, + 98, + 109, + 0, + 0, + 0, + 3, + 49, + 50, + 52, + 109, + 0, + 0, + 0, + 3, + 49, + 50, + 52, + 97, + 4, + 109, + 0, + 0, + 0, + 1, + 57, + 108, + 0, + 0, + 0, + 1, + 109, + 0, + 0, + 0, + 1, + 0, + 106, + 109, + 0, + 0, + 0, + 1, + 54, + 119, + 3, + 110, + 105, + 108, + 109, + 0, + 0, + 0, + 5, + 115, + 101, + 118, + 101, + 110, + 110, + 8, + 0, + 21, + 129, + 233, + 125, + 244, + 16, + 34, + 17, + 109, + 0, + 0, + 0, + 5, + 101, + 105, + 103, + 104, + 116, + 110, + 17, + 0, + 210, + 10, + 63, + 206, + 150, + 95, + 188, + 172, + 184, + 243, + 219, + 192, + 117, + 32, + 201, + 160, + 3, + 109, + 0, + 0, + 0, + 1, + 57, + 111, + 0, + 0, + 1, + 159, + 1, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 28, + 199, + 113, + 181, + 32, + 69, + 253, + 148, + 121, + 67, + 189, + 73, + 192, + 244, + 15, + 109, + 82, + 4, + 20, + 237, + 140, + 117, + 156, + 129, + 151, + 150, + 235, + 201, + 164, + 102, + 80, + 158, + 146, + 192, + 204, + 13, + 238, + 184, + 11, + 195, + 175, + 67, + 70, + 98, + 100, + 217, + 213, + 142, + 108, + 127, + 207, + 18, + 161, + 191, + 131, + 98, + 193, + 222, + 87, + 74, + 221, + 148, + 230, + 9, + 15, + 107, + 231, + 178, + 128, + 50, + 37, + 70, + 163, + 200, + 96, + 152, + 52, + 177, + 75, + 2, + 77, + 29, + 28, + 56, + 65, + 67, + 230, + 75, + 71, + 31, + 8, + 35, + 208, + 239, + 127, + 163, + 187, + 156, + 173, + 198, + 0, + 53, + 51, + 216, + 66, + 32, + 131, + 155, + 200, + 113, + 70, + 100, + 193, + 88, + 174, + 192, + 71, + 129, + 11, + 251, + 35, + 105, + 193, + 175, + 105, + 204, + 157, + 75, + 19, + 124, + 141, + 130, + 247, + 42, + 131, + 23, + 92, + 226, + 63, + 214, + 164, + 158, + 243, + 108, + 84, + 133, + 105, + 35, + 163, + 131, + 241, + 240, + 82, + 232, + 122, + 9, + 181, + 144, + 131, + 124, + 25, + 148, + 6, + 253, + 42, + 217, + 241, + 10, + 185, + 106, + 197, + 130, + 178, + 184, + 159, + 69, + 13, + 142, + 252, + 86, + 126, + 17, + 149, + 65, + 26, + 216, + 214, + 3, + 64, + 187, + 247, + 163, + 149, + 224, + 17, + 239, + 187, + 138, + 63, + 9, + 194, + 217, + 38, + 159, + 74, + 130, + 242, + 102, + 88, + 153, + 11, + 29, + 97, + 59, + 104, + 40, + 37, + 191, + 81, + 200, + 122, + 9, + 33, + 230, + 254, + 203, + 167, + 249, + 15, + 249, + 224, + 91, + 121, + 255, + 242, + 156, + 61, + 55, + 89, + 132, + 237, + 15, + 228, + 177, + 85, + 39, + 190, + 72, + 66, + 13, + 251, + 180, + 83, + 109, + 140, + 137, + 117, + 229, + 175, + 125, + 144, + 245, + 130, + 105, + 208, + 181, + 197, + 54, + 111, + 160, + 50, + 233, + 246, + 218, + 107, + 100, + 163, + 93, + 24, + 226, + 167, + 151, + 237, + 178, + 53, + 116, + 76, + 84, + 98, + 196, + 249, + 62, + 108, + ]); + }); + }); } diff --git a/lib/src/decoder.dart b/lib/src/decoder.dart index e495bce..ab648aa 100644 --- a/lib/src/decoder.dart +++ b/lib/src/decoder.dart @@ -15,7 +15,7 @@ class Decoder { Decoder(Uint8List buffer) : _buffer = buffer, _bytes = buffer.buffer.asByteData() { - final bufferVersion = _buffer.buffer.asByteData().getUint8(0); + final bufferVersion = _bytes.getUint8(0); if (bufferVersion != version) { throw Exception('The version is unsupported' diff --git a/lib/src/encoder.dart b/lib/src/encoder.dart index 025ea6e..2eecffd 100644 --- a/lib/src/encoder.dart +++ b/lib/src/encoder.dart @@ -77,9 +77,9 @@ class Encoder { final keys = value.keys; _append32(keys.length); - for (var i = 0; i < keys.length; i++) { - _encodeString(keys.toList()[i].toString()); - _encodeValue(value[keys.toList()[i]]); + for (final key in keys) { + _encodeString(key.toString()); + _encodeValue(value[key]); } return; @@ -133,6 +133,7 @@ class Encoder { if (value is bool) { // 'true' or 'false' _encodeAtom(value.toString()); + return; } if (value is BigInt) { @@ -179,8 +180,9 @@ class Encoder { _append8(atom.length); // atom is always ASCII ('true', 'false', or 'nil'). - for (int i = 0; i < atom.length; i++) { - _buffer[_offset++] = atom.codeUnitAt(i); + final atomBytes = ascii.encode(atom); + for (int i = 0; i < atomBytes.length; i++) { + _buffer[_offset++] = atomBytes[i]; } } diff --git a/lib/src/eterl.dart b/lib/src/eterl.dart index 78e9053..6241e3a 100644 --- a/lib/src/eterl.dart +++ b/lib/src/eterl.dart @@ -5,7 +5,7 @@ import 'package:eterl/src/decoder.dart'; import 'package:eterl/src/encoder.dart'; /// Eterl encoder and decoder. -class Eterl { +final class Eterl implements Codec> { const Eterl._(); /// Unpack encoded data from the Erlang External Term Format into a Dart object. @@ -27,10 +27,37 @@ class Eterl { return encoder.encode(toEncode); } - /// Returns an [EterlDecoder] that can be used to decode data from the Erlang. + /// Returns an [EterlDecoder] that can be used to decode data from Erlang. EterlDecoder unpacker() => _EterlDecoderImpl(); + + /// Returns an [EterlEncoder] that can be used to encode data to Erlang. + EterlEncoder packer() => _EterlEncoderImpl(); + + @override + Converter> get encoder => _EterlEncoderImpl(); + + @override + Converter, Object?> get decoder => _EterlDecoderImpl(); + + @override + Object? decode(List toDecode) => unpack(toDecode); + + @override + List encode(Object? toEncode) => pack(toEncode); + + @override + Codec, R> fuse(Codec other) { + throw UnimplementedError(); + } + + @override + Codec, Object?> get inverted { + throw UnimplementedError(); + } } +abstract class EterlEncoder extends Converter> {} + abstract class EterlDecoder extends Converter, T> {} class _EterlDecoderImpl extends Converter, T> @@ -40,7 +67,18 @@ class _EterlDecoderImpl extends Converter, T> @override ByteConversionSink startChunkedConversion(Sink sink) { - return _EterlConversionSink(sink); + return _EterlDecoderConversionSink(sink); + } +} + +class _EterlEncoderImpl extends Converter> + implements EterlEncoder { + @override + List convert(dynamic input) => eterl.pack(input); + + @override + Sink startChunkedConversion(Sink> sink) { + return _EterlEncoderConversionSink(sink); } } @@ -59,9 +97,24 @@ Uint8List eterlPack(T toEncode, [int defaultBufferSize = Encoder.defaultBufferSize]) => eterl.pack(toEncode, defaultBufferSize); -class _EterlConversionSink extends ByteConversionSink { +class _EterlEncoderConversionSink implements ChunkedConversionSink> { + final Sink> _sink; + _EterlEncoderConversionSink(this._sink); + + @override + void add(List chunk) { + _sink.add(eterl.pack(chunk)); + } + + @override + void close() { + _sink.close(); + } +} + +class _EterlDecoderConversionSink extends ByteConversionSink { final Sink _sink; - _EterlConversionSink(this._sink); + _EterlDecoderConversionSink(this._sink); @override void add(List chunk) { @@ -76,5 +129,6 @@ class _EterlConversionSink extends ByteConversionSink { @override void addSlice(List chunk, int startIndex, int endIndex, bool isLast) { if (isLast) close(); + add(chunk.sublist(startIndex, endIndex)); } } diff --git a/lib/src/max_int_value_vm.dart b/lib/src/max_int_value_vm.dart index a22b915..4dbe2ce 100644 --- a/lib/src/max_int_value_vm.dart +++ b/lib/src/max_int_value_vm.dart @@ -1 +1,2 @@ -const int maxIntValue = 9223372036854775807; +// Could be a constant, but magic number is not a good practice. +final int maxIntValue = double.maxFinite.toInt(); diff --git a/pubspec.yaml b/pubspec.yaml index 3ec9e00..d5f34c0 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -5,9 +5,9 @@ repository: https://github.com/Rapougnac/eterl issue_tracker: https://github.com/Rapougnac/eterl/issues environment: - sdk: '>=2.18.6 <3.0.0' + sdk: '>=3.2.4 <4.0.0' dev_dependencies: - benchmark_harness: ^2.2.2 + benchmark_runner: ^0.1.7 lints: ^2.0.0 test: ^1.16.0