authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-07-27 07:12:54+00:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-07-27 07:12:54+00:00
log5139aa7ba4ccbe1c8bc435dc3c8c38cb9887de6f
tree91e9bb6000396886aa3ddf9c51919596e7519752
parent6cc72af03df86796251f2bca49fa45a006e67be5
parentf67ce1e35fe3ecf19b50f64b9fe2d85747f7934d
signature Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #5932 from Sahnvour/hash

new trait `hasUniqueRepresentation` and hashmap speedup

3 files changed, 91 insertions(+), 20 deletions(-)

lib/std/hash/auto_hash.zig+15-17
...@@ -56,9 +56,6 @@ pub fn hashPointer(hasher: anytype, key: anytype, comptime strat: HashStrategy)...@@ -56,9 +56,6 @@ pub fn hashPointer(hasher: anytype, key: anytype, comptime strat: HashStrategy)
56pub fn hashArray(hasher: anytype, key: anytype, comptime strat: HashStrategy) void {56pub fn hashArray(hasher: anytype, key: anytype, comptime strat: HashStrategy) void {
57 switch (strat) {57 switch (strat) {
58 .Shallow => {58 .Shallow => {
59 // TODO detect via a trait when Key has no padding bits to
60 // hash it as an array of bytes.
61 // Otherwise, hash every element.
62 for (key) |element| {59 for (key) |element| {
63 hash(hasher, element, .Shallow);60 hash(hasher, element, .Shallow);
64 }61 }
...@@ -75,30 +72,34 @@ pub fn hashArray(hasher: anytype, key: anytype, comptime strat: HashStrategy) vo...@@ -75,30 +72,34 @@ pub fn hashArray(hasher: anytype, key: anytype, comptime strat: HashStrategy) vo
75/// Strategy is provided to determine if pointers should be followed or not.72/// Strategy is provided to determine if pointers should be followed or not.
76pub fn hash(hasher: anytype, key: anytype, comptime strat: HashStrategy) void {73pub fn hash(hasher: anytype, key: anytype, comptime strat: HashStrategy) void {
77 const Key = @TypeOf(key);74 const Key = @TypeOf(key);
75
76 if (strat == .Shallow and comptime meta.trait.hasUniqueRepresentation(Key)) {
77 @call(.{ .modifier = .always_inline }, hasher.update, .{mem.asBytes(&key)});
78 return;
79 }
80
78 switch (@typeInfo(Key)) {81 switch (@typeInfo(Key)) {
79 .NoReturn,82 .NoReturn,
80 .Opaque,83 .Opaque,
81 .Undefined,84 .Undefined,
82 .Void,85 .Void,
83 .Null,86 .Null,
84 .BoundFn,
85 .ComptimeFloat,87 .ComptimeFloat,
86 .ComptimeInt,88 .ComptimeInt,
87 .Type,89 .Type,
88 .EnumLiteral,90 .EnumLiteral,
89 .Frame,91 .Frame,
92 .Float,
90 => @compileError("cannot hash this type"),93 => @compileError("cannot hash this type"),
9194
92 // Help the optimizer see that hashing an int is easy by inlining!95 // Help the optimizer see that hashing an int is easy by inlining!
93 // TODO Check if the situation is better after #561 is resolved.96 // TODO Check if the situation is better after #561 is resolved.
94 .Int => @call(.{ .modifier = .always_inline }, hasher.update, .{std.mem.asBytes(&key)}),97 .Int => @call(.{ .modifier = .always_inline }, hasher.update, .{std.mem.asBytes(&key)}),
9598
96 .Float => |info| hash(hasher, @bitCast(std.meta.Int(false, info.bits), key), strat),
97
98 .Bool => hash(hasher, @boolToInt(key), strat),99 .Bool => hash(hasher, @boolToInt(key), strat),
99 .Enum => hash(hasher, @enumToInt(key), strat),100 .Enum => hash(hasher, @enumToInt(key), strat),
100 .ErrorSet => hash(hasher, @errorToInt(key), strat),101 .ErrorSet => hash(hasher, @errorToInt(key), strat),
101 .AnyFrame, .Fn => hash(hasher, @ptrToInt(key), strat),102 .AnyFrame, .BoundFn, .Fn => hash(hasher, @ptrToInt(key), strat),
102103
103 .Pointer => @call(.{ .modifier = .always_inline }, hashPointer, .{ hasher, key, strat }),104 .Pointer => @call(.{ .modifier = .always_inline }, hashPointer, .{ hasher, key, strat }),
104105
...@@ -121,9 +122,6 @@ pub fn hash(hasher: anytype, key: anytype, comptime strat: HashStrategy) void {...@@ -121,9 +122,6 @@ pub fn hash(hasher: anytype, key: anytype, comptime strat: HashStrategy) void {
121 },122 },
122123
123 .Struct => |info| {124 .Struct => |info| {
124 // TODO detect via a trait when Key has no padding bits to
125 // hash it as an array of bytes.
126 // Otherwise, hash every field.
127 inline for (info.fields) |field| {125 inline for (info.fields) |field| {
128 // We reuse the hash of the previous field as the seed for the126 // We reuse the hash of the previous field as the seed for the
129 // next one so that they're dependant.127 // next one so that they're dependant.
...@@ -266,12 +264,12 @@ test "hash slice deep" {...@@ -266,12 +264,12 @@ test "hash slice deep" {
266test "hash struct deep" {264test "hash struct deep" {
267 const Foo = struct {265 const Foo = struct {
268 a: u32,266 a: u32,
269 b: f64,267 b: u16,
270 c: *bool,268 c: *bool,
271269
272 const Self = @This();270 const Self = @This();
273271
274 pub fn init(allocator: *mem.Allocator, a_: u32, b_: f64, c_: bool) !Self {272 pub fn init(allocator: *mem.Allocator, a_: u32, b_: u16, c_: bool) !Self {
275 const ptr = try allocator.create(bool);273 const ptr = try allocator.create(bool);
276 ptr.* = c_;274 ptr.* = c_;
277 return Self{ .a = a_, .b = b_, .c = ptr };275 return Self{ .a = a_, .b = b_, .c = ptr };
...@@ -279,9 +277,9 @@ test "hash struct deep" {...@@ -279,9 +277,9 @@ test "hash struct deep" {
279 };277 };
280278
281 const allocator = std.testing.allocator;279 const allocator = std.testing.allocator;
282 const foo = try Foo.init(allocator, 123, 1.0, true);280 const foo = try Foo.init(allocator, 123, 10, true);
283 const bar = try Foo.init(allocator, 123, 1.0, true);281 const bar = try Foo.init(allocator, 123, 10, true);
284 const baz = try Foo.init(allocator, 123, 1.0, false);282 const baz = try Foo.init(allocator, 123, 10, false);
285 defer allocator.destroy(foo.c);283 defer allocator.destroy(foo.c);
286 defer allocator.destroy(bar.c);284 defer allocator.destroy(bar.c);
287 defer allocator.destroy(baz.c);285 defer allocator.destroy(baz.c);
...@@ -338,12 +336,12 @@ test "testHash struct" {...@@ -338,12 +336,12 @@ test "testHash struct" {
338test "testHash union" {336test "testHash union" {
339 const Foo = union(enum) {337 const Foo = union(enum) {
340 A: u32,338 A: u32,
341 B: f32,339 B: bool,
342 C: u32,340 C: u32,
343 };341 };
344342
345 const a = Foo{ .A = 18 };343 const a = Foo{ .A = 18 };
346 var b = Foo{ .B = 12.34 };344 var b = Foo{ .B = true };
347 const c = Foo{ .C = 18 };345 const c = Foo{ .C = 18 };
348 testing.expect(testHash(a) == testHash(a));346 testing.expect(testHash(a) == testHash(a));
349 testing.expect(testHash(a) != testHash(b));347 testing.expect(testHash(a) != testHash(b));
lib/std/hash_map.zig+8-3
...@@ -5,6 +5,7 @@ const testing = std.testing;...@@ -5,6 +5,7 @@ const testing = std.testing;
5const math = std.math;5const math = std.math;
6const mem = std.mem;6const mem = std.mem;
7const meta = std.meta;7const meta = std.meta;
8const trait = meta.trait;
8const autoHash = std.hash.autoHash;9const autoHash = std.hash.autoHash;
9const Wyhash = std.hash.Wyhash;10const Wyhash = std.hash.Wyhash;
10const Allocator = mem.Allocator;11const Allocator = mem.Allocator;
...@@ -1023,9 +1024,13 @@ pub fn getTrivialEqlFn(comptime K: type) (fn (K, K) bool) {...@@ -1023,9 +1024,13 @@ pub fn getTrivialEqlFn(comptime K: type) (fn (K, K) bool) {
1023pub fn getAutoHashFn(comptime K: type) (fn (K) u32) {1024pub fn getAutoHashFn(comptime K: type) (fn (K) u32) {
1024 return struct {1025 return struct {
1025 fn hash(key: K) u32 {1026 fn hash(key: K) u32 {
1026 var hasher = Wyhash.init(0);1027 if (comptime trait.hasUniqueRepresentation(K)) {
1027 autoHash(&hasher, key);1028 return @truncate(u32, Wyhash.hash(0, std.mem.asBytes(&key)));
1028 return @truncate(u32, hasher.final());1029 } else {
1030 var hasher = Wyhash.init(0);
1031 autoHash(&hasher, key);
1032 return @truncate(u32, hasher.final());
1033 }
1029 }1034 }
1030 }.hash;1035 }.hash;
1031}1036}
lib/std/meta/trait.zig+68
...@@ -429,3 +429,71 @@ test "std.meta.trait.hasFunctions" {...@@ -429,3 +429,71 @@ test "std.meta.trait.hasFunctions" {
429 testing.expect(!hasFunctions(TestStruct2, .{ "a", "b", "c" }));429 testing.expect(!hasFunctions(TestStruct2, .{ "a", "b", "c" }));
430 testing.expect(!hasFunctions(TestStruct2, tuple));430 testing.expect(!hasFunctions(TestStruct2, tuple));
431}431}
432
433/// True if every value of the type `T` has a unique bit pattern representing it.
434/// In other words, `T` has no unused bits and no padding.
435pub fn hasUniqueRepresentation(comptime T: type) bool {
436 switch (@typeInfo(T)) {
437 else => return false, // TODO can we know if it's true for some of these types ?
438
439 .AnyFrame,
440 .Bool,
441 .BoundFn,
442 .Enum,
443 .ErrorSet,
444 .Fn,
445 .Int, // TODO check that it is still true
446 .Pointer,
447 => return true,
448
449 .Array => |info| return comptime hasUniqueRepresentation(info.child),
450
451 .Struct => |info| {
452 var sum_size = @as(usize, 0);
453
454 inline for (info.fields) |field| {
455 const FieldType = field.field_type;
456 if (comptime !hasUniqueRepresentation(FieldType)) return false;
457 sum_size += @sizeOf(FieldType);
458 }
459
460 return @sizeOf(T) == sum_size;
461 },
462
463 .Vector => |info| return comptime hasUniqueRepresentation(info.child),
464 }
465}
466
467test "std.meta.trait.hasUniqueRepresentation" {
468 const TestStruct1 = struct {
469 a: u32,
470 b: u32,
471 };
472
473 testing.expect(hasUniqueRepresentation(TestStruct1));
474
475 const TestStruct2 = struct {
476 a: u32,
477 b: u16,
478 };
479
480 testing.expect(!hasUniqueRepresentation(TestStruct2));
481
482 const TestStruct3 = struct {
483 a: u32,
484 b: u32,
485 };
486
487 testing.expect(hasUniqueRepresentation(TestStruct3));
488
489 testing.expect(hasUniqueRepresentation(i1));
490 testing.expect(hasUniqueRepresentation(u2));
491 testing.expect(hasUniqueRepresentation(i3));
492 testing.expect(hasUniqueRepresentation(u4));
493 testing.expect(hasUniqueRepresentation(i5));
494 testing.expect(hasUniqueRepresentation(u6));
495 testing.expect(hasUniqueRepresentation(i7));
496 testing.expect(hasUniqueRepresentation(u8));
497 testing.expect(hasUniqueRepresentation(i9));
498 testing.expect(hasUniqueRepresentation(u10));
499}