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
signaturebadge-question-mark 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)
5656pub fn hashArray(hasher: anytype, key: anytype, comptime strat: HashStrategy) void {
5757 switch (strat) {
5858 .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.
6259 for (key) |element| {
6360 hash(hasher, element, .Shallow);
6461 }
......@@ -75,30 +72,34 @@ pub fn hashArray(hasher: anytype, key: anytype, comptime strat: HashStrategy) vo
7572/// Strategy is provided to determine if pointers should be followed or not.
7673pub fn hash(hasher: anytype, key: anytype, comptime strat: HashStrategy) void {
7774 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
7881 switch (@typeInfo(Key)) {
7982 .NoReturn,
8083 .Opaque,
8184 .Undefined,
8285 .Void,
8386 .Null,
84 .BoundFn,
8587 .ComptimeFloat,
8688 .ComptimeInt,
8789 .Type,
8890 .EnumLiteral,
8991 .Frame,
92 .Float,
9093 => @compileError("cannot hash this type"),
9194
9295 // Help the optimizer see that hashing an int is easy by inlining!
9396 // TODO Check if the situation is better after #561 is resolved.
9497 .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
9899 .Bool => hash(hasher, @boolToInt(key), strat),
99100 .Enum => hash(hasher, @enumToInt(key), strat),
100101 .ErrorSet => hash(hasher, @errorToInt(key), strat),
101 .AnyFrame, .Fn => hash(hasher, @ptrToInt(key), strat),
102 .AnyFrame, .BoundFn, .Fn => hash(hasher, @ptrToInt(key), strat),
102103
103104 .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 {
121122 },
122123
123124 .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.
127125 inline for (info.fields) |field| {
128126 // We reuse the hash of the previous field as the seed for the
129127 // next one so that they're dependant.
......@@ -266,12 +264,12 @@ test "hash slice deep" {
266264test "hash struct deep" {
267265 const Foo = struct {
268266 a: u32,
269 b: f64,
267 b: u16,
270268 c: *bool,
271269
272270 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 {
275273 const ptr = try allocator.create(bool);
276274 ptr.* = c_;
277275 return Self{ .a = a_, .b = b_, .c = ptr };
......@@ -279,9 +277,9 @@ test "hash struct deep" {
279277 };
280278
281279 const allocator = std.testing.allocator;
282 const foo = try Foo.init(allocator, 123, 1.0, true);
283 const bar = try Foo.init(allocator, 123, 1.0, true);
284 const baz = try Foo.init(allocator, 123, 1.0, false);
280 const foo = try Foo.init(allocator, 123, 10, true);
281 const bar = try Foo.init(allocator, 123, 10, true);
282 const baz = try Foo.init(allocator, 123, 10, false);
285283 defer allocator.destroy(foo.c);
286284 defer allocator.destroy(bar.c);
287285 defer allocator.destroy(baz.c);
......@@ -338,12 +336,12 @@ test "testHash struct" {
338336test "testHash union" {
339337 const Foo = union(enum) {
340338 A: u32,
341 B: f32,
339 B: bool,
342340 C: u32,
343341 };
344342
345343 const a = Foo{ .A = 18 };
346 var b = Foo{ .B = 12.34 };
344 var b = Foo{ .B = true };
347345 const c = Foo{ .C = 18 };
348346 testing.expect(testHash(a) == testHash(a));
349347 testing.expect(testHash(a) != testHash(b));
lib/std/hash_map.zig+8-3
......@@ -5,6 +5,7 @@ const testing = std.testing;
55const math = std.math;
66const mem = std.mem;
77const meta = std.meta;
8const trait = meta.trait;
89const autoHash = std.hash.autoHash;
910const Wyhash = std.hash.Wyhash;
1011const Allocator = mem.Allocator;
......@@ -1023,9 +1024,13 @@ pub fn getTrivialEqlFn(comptime K: type) (fn (K, K) bool) {
10231024pub fn getAutoHashFn(comptime K: type) (fn (K) u32) {
10241025 return struct {
10251026 fn hash(key: K) u32 {
1026 var hasher = Wyhash.init(0);
1027 autoHash(&hasher, key);
1028 return @truncate(u32, hasher.final());
1027 if (comptime trait.hasUniqueRepresentation(K)) {
1028 return @truncate(u32, Wyhash.hash(0, std.mem.asBytes(&key)));
1029 } else {
1030 var hasher = Wyhash.init(0);
1031 autoHash(&hasher, key);
1032 return @truncate(u32, hasher.final());
1033 }
10291034 }
10301035 }.hash;
10311036}
lib/std/meta/trait.zig+68
......@@ -429,3 +429,71 @@ test "std.meta.trait.hasFunctions" {
429429 testing.expect(!hasFunctions(TestStruct2, .{ "a", "b", "c" }));
430430 testing.expect(!hasFunctions(TestStruct2, tuple));
431431}
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}