| ... | @@ -694,6 +694,21 @@ pub fn HashMap( | ... | @@ -694,6 +694,21 @@ pub fn HashMap( |
| 694 | self.unmanaged = .{}; | 694 | self.unmanaged = .{}; |
| 695 | return result; | 695 | return result; |
| 696 | } | 696 | } |
| | 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 | } |
| 697 | }; | 712 | }; |
| 698 | } | 713 | } |
| 699 | | 714 | |
| ... | @@ -1552,6 +1567,95 @@ pub fn HashMapUnmanaged( | ... | @@ -1552,6 +1567,95 @@ pub fn HashMapUnmanaged( |
| 1552 | return result; | 1567 | return result; |
| 1553 | } | 1568 | } |
| 1554 | | 1569 | |
| | 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 | |
| 1555 | fn grow(self: *Self, allocator: Allocator, new_capacity: Size, ctx: Context) Allocator.Error!void { | 1659 | fn grow(self: *Self, allocator: Allocator, new_capacity: Size, ctx: Context) Allocator.Error!void { |
| 1556 | @setCold(true); | 1660 | @setCold(true); |
| 1557 | const new_cap = @max(new_capacity, minimal_capacity); | 1661 | const new_cap = @max(new_capacity, minimal_capacity); |
| ... | @@ -2272,3 +2376,32 @@ test "getOrPut allocation failure" { | ... | @@ -2272,3 +2376,32 @@ test "getOrPut allocation failure" { |
| 2272 | var map: std.StringHashMapUnmanaged(void) = .{}; | 2376 | var map: std.StringHashMapUnmanaged(void) = .{}; |
| 2273 | try testing.expectError(error.OutOfMemory, map.getOrPut(std.testing.failing_allocator, "hello")); | 2377 | try testing.expectError(error.OutOfMemory, map.getOrPut(std.testing.failing_allocator, "hello")); |
| 2274 | } | 2378 | } |
| | 2379 | |
| | 2380 | test "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 | } |