authorgravatar for mrjbq7@gmail.comJohn Benediktsson <mrjbq7@gmail.com> 2024-08-08 11:59:22-07:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2024-08-08 11:59:22-07:00
loga854ce3021773a3f5cd1dce8ac6bc9eb5f4f937c
treee795414cd349b68d07dc7b9b40b542b376c3405c
parent8031251c33bc66c5648122c0dcd89a8b00c42229
signaturebadge-check Signed by PGP key B5690EEEBB952194

std.hash_map: adding a rehash() method (#19923)

see #17851

1 files changed, 133 insertions(+), 0 deletions(-)

lib/std/hash_map.zig+133
......@@ -694,6 +694,21 @@ pub fn HashMap(
694694 self.unmanaged = .{};
695695 return result;
696696 }
697
698 /// Rehash the map, in-place.
699 ///
700 /// Over time, due to the current tombstone-based implementation, a
701 /// HashMap could become fragmented due to the buildup of tombstone
702 /// entries that causes a performance degradation due to excessive
703 /// probing. The kind of pattern that might cause this is a long-lived
704 /// HashMap with repeated inserts and deletes.
705 ///
706 /// After this function is called, there will be no tombstones in
707 /// the HashMap, each of the entries is rehashed and any existing
708 /// key/value pointers into the HashMap are invalidated.
709 pub fn rehash(self: *Self) void {
710 self.unmanaged.rehash(self.ctx);
711 }
697712 };
698713}
699714
......@@ -1552,6 +1567,95 @@ pub fn HashMapUnmanaged(
15521567 return result;
15531568 }
15541569
1570 /// Rehash the map, in-place.
1571 ///
1572 /// Over time, due to the current tombstone-based implementation, a
1573 /// HashMap could become fragmented due to the buildup of tombstone
1574 /// entries that causes a performance degradation due to excessive
1575 /// probing. The kind of pattern that might cause this is a long-lived
1576 /// HashMap with repeated inserts and deletes.
1577 ///
1578 /// After this function is called, there will be no tombstones in
1579 /// the HashMap, each of the entries is rehashed and any existing
1580 /// key/value pointers into the HashMap are invalidated.
1581 pub fn rehash(self: *Self, ctx: anytype) void {
1582 const mask = self.capacity() - 1;
1583
1584 var metadata = self.metadata.?;
1585 var keys_ptr = self.keys();
1586 var values_ptr = self.values();
1587 var curr: Size = 0;
1588
1589 // While we are re-hashing every slot, we will use the
1590 // fingerprint to mark used buckets as being used and either free
1591 // (needing to be rehashed) or tombstone (already rehashed).
1592
1593 while (curr < self.capacity()) : (curr += 1) {
1594 metadata[curr].fingerprint = Metadata.free;
1595 }
1596
1597 // Now iterate over all the buckets, rehashing them
1598
1599 curr = 0;
1600 while (curr < self.capacity()) {
1601 if (!metadata[curr].isUsed()) {
1602 assert(metadata[curr].isFree());
1603 curr += 1;
1604 continue;
1605 }
1606
1607 const hash = ctx.hash(keys_ptr[curr]);
1608 const fingerprint = Metadata.takeFingerprint(hash);
1609 var idx = @as(usize, @truncate(hash & mask));
1610
1611 // For each bucket, rehash to an index:
1612 // 1) before the cursor, probed into a free slot, or
1613 // 2) equal to the cursor, no need to move, or
1614 // 3) ahead of the cursor, probing over already rehashed
1615
1616 while ((idx < curr and metadata[idx].isUsed()) or
1617 (idx > curr and metadata[idx].fingerprint == Metadata.tombstone))
1618 {
1619 idx = (idx + 1) & mask;
1620 }
1621
1622 if (idx < curr) {
1623 assert(metadata[idx].isFree());
1624 metadata[idx].fill(fingerprint);
1625 keys_ptr[idx] = keys_ptr[curr];
1626 values_ptr[idx] = values_ptr[curr];
1627
1628 metadata[curr].used = 0;
1629 assert(metadata[curr].isFree());
1630 keys_ptr[curr] = undefined;
1631 values_ptr[curr] = undefined;
1632
1633 curr += 1;
1634 } else if (idx == curr) {
1635 metadata[idx].fingerprint = fingerprint;
1636 curr += 1;
1637 } else {
1638 assert(metadata[idx].fingerprint != Metadata.tombstone);
1639 metadata[idx].fingerprint = Metadata.tombstone;
1640 if (metadata[idx].isUsed()) {
1641 std.mem.swap(K, &keys_ptr[curr], &keys_ptr[idx]);
1642 std.mem.swap(V, &values_ptr[curr], &values_ptr[idx]);
1643 } else {
1644 metadata[idx].used = 1;
1645 keys_ptr[idx] = keys_ptr[curr];
1646 values_ptr[idx] = values_ptr[curr];
1647
1648 metadata[curr].fingerprint = Metadata.free;
1649 metadata[curr].used = 0;
1650 keys_ptr[curr] = undefined;
1651 values_ptr[curr] = undefined;
1652
1653 curr += 1;
1654 }
1655 }
1656 }
1657 }
1658
15551659 fn grow(self: *Self, allocator: Allocator, new_capacity: Size, ctx: Context) Allocator.Error!void {
15561660 @setCold(true);
15571661 const new_cap = @max(new_capacity, minimal_capacity);
......@@ -2272,3 +2376,32 @@ test "getOrPut allocation failure" {
22722376 var map: std.StringHashMapUnmanaged(void) = .{};
22732377 try testing.expectError(error.OutOfMemory, map.getOrPut(std.testing.failing_allocator, "hello"));
22742378}
2379
2380test "std.hash_map rehash" {
2381 var map = AutoHashMap(usize, usize).init(std.testing.allocator);
2382 defer map.deinit();
2383
2384 var prng = std.Random.DefaultPrng.init(0);
2385 const random = prng.random();
2386
2387 const count = 6 * random.intRangeLessThan(u32, 100_000, 500_000);
2388
2389 for (0..count) |i| {
2390 try map.put(i, i);
2391 if (i % 3 == 0) {
2392 try expectEqual(map.remove(i), true);
2393 }
2394 }
2395
2396 map.rehash();
2397
2398 try expectEqual(map.count(), count * 2 / 3);
2399
2400 for (0..count) |i| {
2401 if (i % 3 == 0) {
2402 try expectEqual(map.get(i), null);
2403 } else {
2404 try expectEqual(map.get(i).?, i);
2405 }
2406 }
2407}