| ... | @@ -473,14 +473,18 @@ const Object = struct { | ... | @@ -473,14 +473,18 @@ const Object = struct { |
| 473 | /// objects remaining in the cache will be freed when the cache itself is freed. | 473 | /// objects remaining in the cache will be freed when the cache itself is freed. |
| 474 | const ObjectCache = struct { | 474 | const ObjectCache = struct { |
| 475 | objects: std.AutoHashMapUnmanaged(u64, CacheEntry) = .empty, | 475 | objects: std.AutoHashMapUnmanaged(u64, CacheEntry) = .empty, |
| 476 | lru_nodes: LruList = .{}, | 476 | lru_nodes: std.DoublyLinkedList = .{}, |
| | 477 | lru_nodes_len: usize = 0, |
| 477 | byte_size: usize = 0, | 478 | byte_size: usize = 0, |
| 478 | | 479 | |
| 479 | const max_byte_size = 128 * 1024 * 1024; // 128MiB | 480 | const max_byte_size = 128 * 1024 * 1024; // 128MiB |
| 480 | /// A list of offsets stored in the cache, with the most recently used | 481 | /// A list of offsets stored in the cache, with the most recently used |
| 481 | /// entries at the end. | 482 | /// entries at the end. |
| 482 | const LruList = std.DoublyLinkedList(u64); | 483 | const LruListNode = struct { |
| 483 | const CacheEntry = struct { object: Object, lru_node: *LruList.Node }; | 484 | data: u64, |
| | 485 | node: std.DoublyLinkedList.Node, |
| | 486 | }; |
| | 487 | const CacheEntry = struct { object: Object, lru_node: *LruListNode }; |
| 484 | | 488 | |
| 485 | fn deinit(cache: *ObjectCache, allocator: Allocator) void { | 489 | fn deinit(cache: *ObjectCache, allocator: Allocator) void { |
| 486 | var object_iterator = cache.objects.iterator(); | 490 | var object_iterator = cache.objects.iterator(); |
| ... | @@ -496,8 +500,8 @@ const ObjectCache = struct { | ... | @@ -496,8 +500,8 @@ const ObjectCache = struct { |
| 496 | /// position if it is present. | 500 | /// position if it is present. |
| 497 | fn get(cache: *ObjectCache, offset: u64) ?Object { | 501 | fn get(cache: *ObjectCache, offset: u64) ?Object { |
| 498 | if (cache.objects.get(offset)) |entry| { | 502 | if (cache.objects.get(offset)) |entry| { |
| 499 | cache.lru_nodes.remove(entry.lru_node); | 503 | cache.lru_nodes.remove(&entry.lru_node.node); |
| 500 | cache.lru_nodes.append(entry.lru_node); | 504 | cache.lru_nodes.append(&entry.lru_node.node); |
| 501 | return entry.object; | 505 | return entry.object; |
| 502 | } else { | 506 | } else { |
| 503 | return null; | 507 | return null; |
| ... | @@ -510,26 +514,29 @@ const ObjectCache = struct { | ... | @@ -510,26 +514,29 @@ const ObjectCache = struct { |
| 510 | /// will not be evicted before the next call to `put` or `deinit` even if | 514 | /// will not be evicted before the next call to `put` or `deinit` even if |
| 511 | /// it exceeds the maximum cache size. | 515 | /// it exceeds the maximum cache size. |
| 512 | fn put(cache: *ObjectCache, allocator: Allocator, offset: u64, object: Object) !void { | 516 | fn put(cache: *ObjectCache, allocator: Allocator, offset: u64, object: Object) !void { |
| 513 | const lru_node = try allocator.create(LruList.Node); | 517 | const lru_node = try allocator.create(LruListNode); |
| 514 | errdefer allocator.destroy(lru_node); | 518 | errdefer allocator.destroy(lru_node); |
| 515 | lru_node.data = offset; | 519 | lru_node.data = offset; |
| 516 | | 520 | |
| 517 | const gop = try cache.objects.getOrPut(allocator, offset); | 521 | const gop = try cache.objects.getOrPut(allocator, offset); |
| 518 | if (gop.found_existing) { | 522 | if (gop.found_existing) { |
| 519 | cache.byte_size -= gop.value_ptr.object.data.len; | 523 | cache.byte_size -= gop.value_ptr.object.data.len; |
| 520 | cache.lru_nodes.remove(gop.value_ptr.lru_node); | 524 | cache.lru_nodes.remove(&gop.value_ptr.lru_node.node); |
| | 525 | cache.lru_nodes_len -= 1; |
| 521 | allocator.destroy(gop.value_ptr.lru_node); | 526 | allocator.destroy(gop.value_ptr.lru_node); |
| 522 | allocator.free(gop.value_ptr.object.data); | 527 | allocator.free(gop.value_ptr.object.data); |
| 523 | } | 528 | } |
| 524 | gop.value_ptr.* = .{ .object = object, .lru_node = lru_node }; | 529 | gop.value_ptr.* = .{ .object = object, .lru_node = lru_node }; |
| 525 | cache.byte_size += object.data.len; | 530 | cache.byte_size += object.data.len; |
| 526 | cache.lru_nodes.append(lru_node); | 531 | cache.lru_nodes.append(&lru_node.node); |
| | 532 | cache.lru_nodes_len += 1; |
| 527 | | 533 | |
| 528 | while (cache.byte_size > max_byte_size and cache.lru_nodes.len > 1) { | 534 | while (cache.byte_size > max_byte_size and cache.lru_nodes_len > 1) { |
| 529 | // The > 1 check is to make sure that we don't evict the most | 535 | // The > 1 check is to make sure that we don't evict the most |
| 530 | // recently added node, even if it by itself happens to exceed the | 536 | // recently added node, even if it by itself happens to exceed the |
| 531 | // maximum size of the cache. | 537 | // maximum size of the cache. |
| 532 | const evict_node = cache.lru_nodes.popFirst().?; | 538 | const evict_node: *LruListNode = @alignCast(@fieldParentPtr("node", cache.lru_nodes.popFirst().?)); |
| | 539 | cache.lru_nodes_len -= 1; |
| 533 | const evict_offset = evict_node.data; | 540 | const evict_offset = evict_node.data; |
| 534 | allocator.destroy(evict_node); | 541 | allocator.destroy(evict_node); |
| 535 | const evict_object = cache.objects.get(evict_offset).?.object; | 542 | const evict_object = cache.objects.get(evict_offset).?.object; |