diff --git a/CHANGELOG.md b/CHANGELOG.md index 1cb29b39..0a8b0e94 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - Generate empty constructor with semicolon instead of {}. - Use ArgumentError.notNull for null errors. - Reject dynamic fields. +- Add simple benchmark for hashing. Improve hashing performance. ## 0.1.5 diff --git a/built_value/lib/built_value.dart b/built_value/lib/built_value.dart index c79c7cf1..7c5ec177 100644 --- a/built_value/lib/built_value.dart +++ b/built_value/lib/built_value.dart @@ -4,8 +4,6 @@ library built_value; -export 'package:quiver/core.dart' show hashObjects; - /// Implement this for a Built Value. /// /// Then use built_value_generator.dart code generation functionality to @@ -59,3 +57,19 @@ abstract class Builder, B extends Builder> { // // Fields marked with this annotation are allowed to be null. const String nullable = 'nullable'; + +/// For use by generated code in calculating hash codes. Do not use directly. +int $jc(int hash, int value) { + // Jenkins hash "combine". + hash = 0x1fffffff & (hash + value); + hash = 0x1fffffff & (hash + ((0x0007ffff & hash) << 10)); + return hash ^ (hash >> 6); +} + +/// For use by generated code in calculating hash codes. Do not use directly. +int $jf(int hash) { + // Jenkins hash "finish". + hash = 0x1fffffff & (hash + ((0x03ffffff & hash) << 3)); + hash = hash ^ (hash >> 11); + return 0x1fffffff & (hash + ((0x00003fff & hash) << 15)); +} diff --git a/built_value/pubspec.yaml b/built_value/pubspec.yaml index 6f4dce28..9c7bc7db 100644 --- a/built_value/pubspec.yaml +++ b/built_value/pubspec.yaml @@ -8,6 +8,3 @@ homepage: https://github.com/google/built_value.dart environment: sdk: '>=1.8.0 <2.0.0' - -dependencies: - quiver: '>=0.21.0 <0.24.0' diff --git a/built_value_generator/lib/src/source_class.dart b/built_value_generator/lib/src/source_class.dart index 29518a7c..9af8cbfb 100644 --- a/built_value_generator/lib/src/source_class.dart +++ b/built_value_generator/lib/src/source_class.dart @@ -259,9 +259,11 @@ abstract class SourceClass implements Built { if (fields.length == 0) { result.writeln('return ${name.hashCode};'); } else { - result.writeln('return hashObjects(['); - result.write(fields.map((field) => field.name).join(', ')); - result.writeln(']);'); + result.writeln(r'return $jf('); + result.writeln(r'$jc(' * fields.length); + result.writeln('0, '); + result.write(fields.map((field) => '${field.name}.hashCode').join('), ')); + result.writeln('));'); } result.writeln('}'); result.writeln(); diff --git a/built_value_generator/lib/src/source_class.g.dart b/built_value_generator/lib/src/source_class.g.dart index 6a9d9db6..bf17ffa3 100644 --- a/built_value_generator/lib/src/source_class.g.dart +++ b/built_value_generator/lib/src/source_class.g.dart @@ -87,21 +87,30 @@ class _$SourceClass extends SourceClass { } int get hashCode { - return hashObjects([ - name, - builtParameters, - hasBuilder, - builderParameters, - fields, - partStatement, - hasPartStatement, - valueClassIsAbstract, - valueClassConstructors, - valueClassFactories, - builderClassIsAbstract, - builderClassConstructors, - builderClassFactories - ]); + return $jf($jc( + $jc( + $jc( + $jc( + $jc( + $jc( + $jc( + $jc( + $jc( + $jc( + $jc( + $jc($jc(0, name.hashCode), + builtParameters.hashCode), + hasBuilder.hashCode), + builderParameters.hashCode), + fields.hashCode), + partStatement.hashCode), + hasPartStatement.hashCode), + valueClassIsAbstract.hashCode), + valueClassConstructors.hashCode), + valueClassFactories.hashCode), + builderClassIsAbstract.hashCode), + builderClassConstructors.hashCode), + builderClassFactories.hashCode)); } String toString() { diff --git a/built_value_generator/lib/src/source_field.g.dart b/built_value_generator/lib/src/source_field.g.dart index afd9a1dd..fc1d563f 100644 --- a/built_value_generator/lib/src/source_field.g.dart +++ b/built_value_generator/lib/src/source_field.g.dart @@ -61,16 +61,18 @@ class _$SourceField extends SourceField { } int get hashCode { - return hashObjects([ - name, - type, - isGetter, - isNullable, - builderFieldExists, - builderFieldIsNormalField, - typeInBuilder, - isNestedBuilder - ]); + return $jf($jc( + $jc( + $jc( + $jc( + $jc( + $jc($jc($jc(0, name.hashCode), type.hashCode), + isGetter.hashCode), + isNullable.hashCode), + builderFieldExists.hashCode), + builderFieldIsNormalField.hashCode), + typeInBuilder.hashCode), + isNestedBuilder.hashCode)); } String toString() { diff --git a/example/lib/collections.g.dart b/example/lib/collections.g.dart index 13fc12e6..36b45a07 100644 --- a/example/lib/collections.g.dart +++ b/example/lib/collections.g.dart @@ -31,11 +31,11 @@ class _$Collections extends Collections { this.nullableListMultimap, this.nullableSetMultimap}) : super._() { - if (list == null) throw new ArgumentError('null list'); - if (set == null) throw new ArgumentError('null set'); - if (map == null) throw new ArgumentError('null map'); - if (listMultimap == null) throw new ArgumentError('null listMultimap'); - if (setMultimap == null) throw new ArgumentError('null setMultimap'); + if (list == null) throw new ArgumentError.notNull('list'); + if (set == null) throw new ArgumentError.notNull('set'); + if (map == null) throw new ArgumentError.notNull('map'); + if (listMultimap == null) throw new ArgumentError.notNull('listMultimap'); + if (setMultimap == null) throw new ArgumentError.notNull('setMultimap'); } factory _$Collections([updates(CollectionsBuilder b)]) => @@ -61,18 +61,22 @@ class _$Collections extends Collections { } int get hashCode { - return hashObjects([ - list, - set, - map, - listMultimap, - setMultimap, - nullableList, - nullableSet, - nullableMap, - nullableListMultimap, - nullableSetMultimap - ]); + return $jf($jc( + $jc( + $jc( + $jc( + $jc( + $jc( + $jc( + $jc($jc($jc(0, list.hashCode), set.hashCode), + map.hashCode), + listMultimap.hashCode), + setMultimap.hashCode), + nullableList.hashCode), + nullableSet.hashCode), + nullableMap.hashCode), + nullableListMultimap.hashCode), + nullableSetMultimap.hashCode)); } String toString() { diff --git a/example/lib/compound_value.g.dart b/example/lib/compound_value.g.dart index 499bee48..bfcc80cb 100644 --- a/example/lib/compound_value.g.dart +++ b/example/lib/compound_value.g.dart @@ -12,7 +12,7 @@ class _$CompoundValue extends CompoundValue { final ValidatedValue validatedValue; _$CompoundValue._({this.simpleValue, this.validatedValue}) : super._() { - if (simpleValue == null) throw new ArgumentError('null simpleValue'); + if (simpleValue == null) throw new ArgumentError.notNull('simpleValue'); } factory _$CompoundValue([updates(CompoundValueBuilder b)]) => @@ -30,7 +30,7 @@ class _$CompoundValue extends CompoundValue { } int get hashCode { - return hashObjects([simpleValue, validatedValue]); + return $jf($jc($jc(0, simpleValue.hashCode), validatedValue.hashCode)); } String toString() { diff --git a/example/lib/simple_value.g.dart b/example/lib/simple_value.g.dart index 9b2506ae..c8694895 100644 --- a/example/lib/simple_value.g.dart +++ b/example/lib/simple_value.g.dart @@ -12,7 +12,7 @@ class _$SimpleValue extends SimpleValue { final String aString; _$SimpleValue._({this.anInt, this.aString}) : super._() { - if (anInt == null) throw new ArgumentError('null anInt'); + if (anInt == null) throw new ArgumentError.notNull('anInt'); } factory _$SimpleValue([updates(SimpleValueBuilder b)]) => @@ -29,7 +29,7 @@ class _$SimpleValue extends SimpleValue { } int get hashCode { - return hashObjects([anInt, aString]); + return $jf($jc($jc(0, anInt.hashCode), aString.hashCode)); } String toString() { diff --git a/example/lib/validated_value.g.dart b/example/lib/validated_value.g.dart index 370ca5f1..cfa8f2f3 100644 --- a/example/lib/validated_value.g.dart +++ b/example/lib/validated_value.g.dart @@ -12,7 +12,7 @@ class _$ValidatedValue extends ValidatedValue { final String aString; _$ValidatedValue._({this.anInt, this.aString}) : super._() { - if (anInt == null) throw new ArgumentError('null anInt'); + if (anInt == null) throw new ArgumentError.notNull('anInt'); } factory _$ValidatedValue([updates(ValidatedValueBuilder b)]) => @@ -30,7 +30,7 @@ class _$ValidatedValue extends ValidatedValue { } int get hashCode { - return hashObjects([anInt, aString]); + return $jf($jc($jc(0, anInt.hashCode), aString.hashCode)); } String toString() { diff --git a/example/lib/value_with_code.g.dart b/example/lib/value_with_code.g.dart index 7faaa66e..cd53c466 100644 --- a/example/lib/value_with_code.g.dart +++ b/example/lib/value_with_code.g.dart @@ -12,7 +12,7 @@ class _$ValueWithCode extends ValueWithCode { final String aString; _$ValueWithCode._({this.anInt, this.aString}) : super._() { - if (anInt == null) throw new ArgumentError('null anInt'); + if (anInt == null) throw new ArgumentError.notNull('anInt'); } factory _$ValueWithCode([updates(ValueWithCodeBuilder b)]) => @@ -29,7 +29,7 @@ class _$ValueWithCode extends ValueWithCode { } int get hashCode { - return hashObjects([anInt, aString]); + return $jf($jc($jc(0, anInt.hashCode), aString.hashCode)); } String toString() { diff --git a/example/lib/value_with_defaults.g.dart b/example/lib/value_with_defaults.g.dart index 7072858e..15b8b962 100644 --- a/example/lib/value_with_defaults.g.dart +++ b/example/lib/value_with_defaults.g.dart @@ -12,7 +12,7 @@ class _$ValueWithDefaults extends ValueWithDefaults { final String aString; _$ValueWithDefaults._({this.anInt, this.aString}) : super._() { - if (anInt == null) throw new ArgumentError('null anInt'); + if (anInt == null) throw new ArgumentError.notNull('anInt'); } factory _$ValueWithDefaults([updates(ValueWithDefaultsBuilder b)]) => @@ -30,7 +30,7 @@ class _$ValueWithDefaults extends ValueWithDefaults { } int get hashCode { - return hashObjects([anInt, aString]); + return $jf($jc($jc(0, anInt.hashCode), aString.hashCode)); } String toString() { diff --git a/example/pubspec.yaml b/example/pubspec.yaml index de0240f4..48706891 100644 --- a/example/pubspec.yaml +++ b/example/pubspec.yaml @@ -19,4 +19,5 @@ dev_dependencies: # built_value_generator: '^0.1.6' built_value_generator: path: ../built_value_generator + quiver: '>=0.21.0 <0.24.0' test: any diff --git a/example/test/benchmark_test.dart b/example/test/benchmark_test.dart new file mode 100644 index 00000000..0cef6a92 --- /dev/null +++ b/example/test/benchmark_test.dart @@ -0,0 +1,36 @@ +// Copyright (c) 2016, Google Inc. Please see the AUTHORS file for details. +// All rights reserved. Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +import 'package:example/simple_value.dart'; +import 'package:test/test.dart'; + +void main() { + group('benchmark SimpleValue', () { + test('hashCode', () { + final value = new SimpleValue((b) => b + ..anInt = 0 + ..aString = 'zero'); + benchmark('hashCode', () => value.hashCode); + }); + }); +} + +void benchmark(String name, function()) { + // Warm up. + for (var i = 0; i != 1000; ++i) { + function(); + } + + // Time. + for (int i = 0; i != 3; ++i) { + final stopwatch = new Stopwatch()..start(); + final reps = 10000000; + for (var i = 0; i != reps; ++i) { + function(); + } + final perSecond = + (reps / (stopwatch.elapsedMicroseconds / 1000000.0)).round(); + print('$name: $perSecond/s'); + } +} diff --git a/example/test/collections_test.dart b/example/test/collections_test.dart index c4de0266..36aa2b82 100644 --- a/example/test/collections_test.dart +++ b/example/test/collections_test.dart @@ -4,6 +4,7 @@ import 'package:built_collection/built_collection.dart'; import 'package:example/collections.dart'; +import 'package:quiver/core.dart'; import 'package:test/test.dart'; void main() { @@ -70,5 +71,29 @@ void main() { 'six': [false] }); }); + + test('hash matches quiver hash', () { + final collections = new Collections((b) => b + ..list.add(1) + ..set.add('two') + ..map['three'] = 4 + ..listMultimap.add(5, true) + ..setMultimap.add('six', false)); + + expect( + collections.hashCode, + hashObjects([ + collections.list, + collections.set, + collections.map, + collections.listMultimap, + collections.setMultimap, + collections.nullableList, + collections.nullableSet, + collections.nullableMap, + collections.nullableListMultimap, + collections.nullableSetMultimap, + ])); + }); }); } diff --git a/example/test/compound_value_test.dart b/example/test/compound_value_test.dart index 3ec63204..21c5f9c5 100644 --- a/example/test/compound_value_test.dart +++ b/example/test/compound_value_test.dart @@ -4,6 +4,7 @@ import 'package:example/compound_value.dart'; import 'package:example/validated_value.dart'; +import 'package:quiver/core.dart'; import 'package:test/test.dart'; void main() { @@ -28,5 +29,14 @@ void main() { ..validatedValue.anInt = 2).validatedValue.anInt, 2); }); + + test('hash matches quiver hash', () { + final value = new CompoundValue((b) => b + ..simpleValue.anInt = 1 + ..simpleValue.aString = 'two'); + + expect(value.hashCode, + hashObjects([value.simpleValue, value.validatedValue])); + }); }); } diff --git a/example/test/simple_value_test.dart b/example/test/simple_value_test.dart index e196456c..29a03b03 100644 --- a/example/test/simple_value_test.dart +++ b/example/test/simple_value_test.dart @@ -3,6 +3,7 @@ // license that can be found in the LICENSE file. import 'package:example/simple_value.dart'; +import 'package:quiver/core.dart'; import 'package:test/test.dart'; void main() { @@ -61,6 +62,13 @@ void main() { expect(value1, isNot(equals(value2))); }); + test('hash matches quiver hash', () { + final value = new SimpleValue((b) => b + ..anInt = 73 + ..aString = 'seventythree'); + expect(value.hashCode, hashObjects([value.anInt, value.aString])); + }); + test('hashes equal when equal', () { final value1 = new SimpleValue((b) => b ..anInt = 0