| ... | ... | @@ -694,6 +694,21 @@ pub fn HashMap( |
| 694 | 694 | self.unmanaged = .{}; |
| 695 | 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 | 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 | 1659 | fn grow(self: *Self, allocator: Allocator, new_capacity: Size, ctx: Context) Allocator.Error!void { |
| 1556 | 1660 | @setCold(true); |
| 1557 | 1661 | const new_cap = @max(new_capacity, minimal_capacity); |
| ... | ... | @@ -2272,3 +2376,32 @@ test "getOrPut allocation failure" { |
| 2272 | 2376 | var map: std.StringHashMapUnmanaged(void) = .{}; |
| 2273 | 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 | } |