tigerbeetle 0.0.34 → 0.0.37
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +10 -0
- data/ext/tb_client/extconf.rb +13 -13
- data/ext/tb_client/tigerbeetle/LICENSE +177 -0
- data/ext/tb_client/tigerbeetle/build.zig +2327 -0
- data/ext/tb_client/tigerbeetle/src/aof.zig +1000 -0
- data/ext/tb_client/tigerbeetle/src/build_multiversion.zig +808 -0
- data/ext/tb_client/tigerbeetle/src/cdc/amqp/protocol.zig +1283 -0
- data/ext/tb_client/tigerbeetle/src/cdc/amqp/spec.zig +1704 -0
- data/ext/tb_client/tigerbeetle/src/cdc/amqp/types.zig +341 -0
- data/ext/tb_client/tigerbeetle/src/cdc/amqp.zig +1450 -0
- data/ext/tb_client/tigerbeetle/src/cdc/runner.zig +1659 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/samples/main.c +406 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/context.zig +1084 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/echo_client.zig +286 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/packet.zig +158 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/signal.zig +229 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client/signal_fuzz.zig +110 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client.h +386 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client.zig +34 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_exports.zig +281 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_header.zig +312 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/tb_client_header_test.zig +138 -0
- data/ext/tb_client/tigerbeetle/src/clients/c/test.zig +466 -0
- data/ext/tb_client/tigerbeetle/src/clients/docs_samples.zig +157 -0
- data/ext/tb_client/tigerbeetle/src/clients/docs_types.zig +90 -0
- data/ext/tb_client/tigerbeetle/src/clients/dotnet/ci.zig +203 -0
- data/ext/tb_client/tigerbeetle/src/clients/dotnet/docs.zig +79 -0
- data/ext/tb_client/tigerbeetle/src/clients/dotnet/dotnet_bindings.zig +542 -0
- data/ext/tb_client/tigerbeetle/src/clients/go/ci.zig +109 -0
- data/ext/tb_client/tigerbeetle/src/clients/go/docs.zig +86 -0
- data/ext/tb_client/tigerbeetle/src/clients/go/go_bindings.zig +370 -0
- data/ext/tb_client/tigerbeetle/src/clients/go/pkg/native/tb_client.h +386 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/ci.zig +167 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/docs.zig +126 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/java_bindings.zig +996 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/src/client.zig +748 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/src/jni.zig +3238 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/src/jni_tests.zig +1718 -0
- data/ext/tb_client/tigerbeetle/src/clients/java/src/jni_thread_cleaner.zig +190 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/ci.zig +104 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/docs.zig +75 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/node.zig +522 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/node_bindings.zig +267 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/src/c.zig +3 -0
- data/ext/tb_client/tigerbeetle/src/clients/node/src/translate.zig +379 -0
- data/ext/tb_client/tigerbeetle/src/clients/python/ci.zig +131 -0
- data/ext/tb_client/tigerbeetle/src/clients/python/docs.zig +63 -0
- data/ext/tb_client/tigerbeetle/src/clients/python/python_bindings.zig +588 -0
- data/ext/tb_client/tigerbeetle/src/clients/rust/assets/tb_client.h +386 -0
- data/ext/tb_client/tigerbeetle/src/clients/rust/ci.zig +73 -0
- data/ext/tb_client/tigerbeetle/src/clients/rust/docs.zig +106 -0
- data/ext/tb_client/tigerbeetle/src/clients/rust/rust_bindings.zig +305 -0
- data/ext/tb_client/tigerbeetle/src/config.zig +296 -0
- data/ext/tb_client/tigerbeetle/src/constants.zig +790 -0
- data/ext/tb_client/tigerbeetle/src/copyhound.zig +202 -0
- data/ext/tb_client/tigerbeetle/src/counting_allocator.zig +72 -0
- data/ext/tb_client/tigerbeetle/src/direction.zig +11 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/build.zig +158 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/content.zig +156 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/docs.zig +252 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/file_checker.zig +313 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/html.zig +87 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/page_writer.zig +63 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/redirects.zig +47 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/search_index_writer.zig +28 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/service_worker_writer.zig +61 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/single_page_writer.zig +169 -0
- data/ext/tb_client/tigerbeetle/src/docs_website/src/website.zig +46 -0
- data/ext/tb_client/tigerbeetle/src/ewah.zig +445 -0
- data/ext/tb_client/tigerbeetle/src/ewah_benchmark.zig +128 -0
- data/ext/tb_client/tigerbeetle/src/ewah_fuzz.zig +171 -0
- data/ext/tb_client/tigerbeetle/src/fuzz_tests.zig +179 -0
- data/ext/tb_client/tigerbeetle/src/integration_tests.zig +662 -0
- data/ext/tb_client/tigerbeetle/src/io/common.zig +155 -0
- data/ext/tb_client/tigerbeetle/src/io/darwin.zig +1093 -0
- data/ext/tb_client/tigerbeetle/src/io/linux.zig +1880 -0
- data/ext/tb_client/tigerbeetle/src/io/test.zig +1005 -0
- data/ext/tb_client/tigerbeetle/src/io/windows.zig +1598 -0
- data/ext/tb_client/tigerbeetle/src/io.zig +34 -0
- data/ext/tb_client/tigerbeetle/src/iops.zig +134 -0
- data/ext/tb_client/tigerbeetle/src/list.zig +236 -0
- data/ext/tb_client/tigerbeetle/src/lsm/binary_search.zig +848 -0
- data/ext/tb_client/tigerbeetle/src/lsm/binary_search_benchmark.zig +179 -0
- data/ext/tb_client/tigerbeetle/src/lsm/cache_map.zig +424 -0
- data/ext/tb_client/tigerbeetle/src/lsm/cache_map_fuzz.zig +420 -0
- data/ext/tb_client/tigerbeetle/src/lsm/compaction.zig +2117 -0
- data/ext/tb_client/tigerbeetle/src/lsm/composite_key.zig +182 -0
- data/ext/tb_client/tigerbeetle/src/lsm/forest.zig +1119 -0
- data/ext/tb_client/tigerbeetle/src/lsm/forest_fuzz.zig +1102 -0
- data/ext/tb_client/tigerbeetle/src/lsm/forest_table_iterator.zig +200 -0
- data/ext/tb_client/tigerbeetle/src/lsm/groove.zig +1495 -0
- data/ext/tb_client/tigerbeetle/src/lsm/k_way_merge.zig +739 -0
- data/ext/tb_client/tigerbeetle/src/lsm/k_way_merge_benchmark.zig +166 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest.zig +754 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest_level.zig +1294 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest_level_fuzz.zig +510 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest_log.zig +1263 -0
- data/ext/tb_client/tigerbeetle/src/lsm/manifest_log_fuzz.zig +628 -0
- data/ext/tb_client/tigerbeetle/src/lsm/node_pool.zig +247 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_buffer.zig +116 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_builder.zig +543 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_fuzz.zig +938 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_lookup.zig +293 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_merge.zig +362 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_range.zig +99 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_state.zig +17 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scan_tree.zig +1036 -0
- data/ext/tb_client/tigerbeetle/src/lsm/schema.zig +617 -0
- data/ext/tb_client/tigerbeetle/src/lsm/scratch_memory.zig +84 -0
- data/ext/tb_client/tigerbeetle/src/lsm/segmented_array.zig +1500 -0
- data/ext/tb_client/tigerbeetle/src/lsm/segmented_array_benchmark.zig +149 -0
- data/ext/tb_client/tigerbeetle/src/lsm/segmented_array_fuzz.zig +7 -0
- data/ext/tb_client/tigerbeetle/src/lsm/set_associative_cache.zig +865 -0
- data/ext/tb_client/tigerbeetle/src/lsm/table.zig +607 -0
- data/ext/tb_client/tigerbeetle/src/lsm/table_memory.zig +843 -0
- data/ext/tb_client/tigerbeetle/src/lsm/table_value_iterator.zig +105 -0
- data/ext/tb_client/tigerbeetle/src/lsm/timestamp_range.zig +40 -0
- data/ext/tb_client/tigerbeetle/src/lsm/tree.zig +630 -0
- data/ext/tb_client/tigerbeetle/src/lsm/tree_fuzz.zig +933 -0
- data/ext/tb_client/tigerbeetle/src/lsm/zig_zag_merge.zig +557 -0
- data/ext/tb_client/tigerbeetle/src/message_buffer.zig +469 -0
- data/ext/tb_client/tigerbeetle/src/message_bus.zig +1214 -0
- data/ext/tb_client/tigerbeetle/src/message_bus_fuzz.zig +936 -0
- data/ext/tb_client/tigerbeetle/src/message_pool.zig +343 -0
- data/ext/tb_client/tigerbeetle/src/multiversion.zig +2195 -0
- data/ext/tb_client/tigerbeetle/src/queue.zig +390 -0
- data/ext/tb_client/tigerbeetle/src/repl/completion.zig +201 -0
- data/ext/tb_client/tigerbeetle/src/repl/parser.zig +1356 -0
- data/ext/tb_client/tigerbeetle/src/repl/terminal.zig +496 -0
- data/ext/tb_client/tigerbeetle/src/repl.zig +1034 -0
- data/ext/tb_client/tigerbeetle/src/scripts/amqp.zig +973 -0
- data/ext/tb_client/tigerbeetle/src/scripts/cfo.zig +1866 -0
- data/ext/tb_client/tigerbeetle/src/scripts/changelog.zig +304 -0
- data/ext/tb_client/tigerbeetle/src/scripts/ci.zig +227 -0
- data/ext/tb_client/tigerbeetle/src/scripts/client_readmes.zig +658 -0
- data/ext/tb_client/tigerbeetle/src/scripts/devhub.zig +466 -0
- data/ext/tb_client/tigerbeetle/src/scripts/release.zig +1058 -0
- data/ext/tb_client/tigerbeetle/src/scripts.zig +105 -0
- data/ext/tb_client/tigerbeetle/src/shell.zig +1195 -0
- data/ext/tb_client/tigerbeetle/src/stack.zig +260 -0
- data/ext/tb_client/tigerbeetle/src/state_machine/auditor.zig +911 -0
- data/ext/tb_client/tigerbeetle/src/state_machine/workload.zig +2079 -0
- data/ext/tb_client/tigerbeetle/src/state_machine.zig +4872 -0
- data/ext/tb_client/tigerbeetle/src/state_machine_fuzz.zig +288 -0
- data/ext/tb_client/tigerbeetle/src/state_machine_tests.zig +3128 -0
- data/ext/tb_client/tigerbeetle/src/static_allocator.zig +82 -0
- data/ext/tb_client/tigerbeetle/src/stdx/bit_set.zig +157 -0
- data/ext/tb_client/tigerbeetle/src/stdx/bounded_array.zig +292 -0
- data/ext/tb_client/tigerbeetle/src/stdx/debug.zig +65 -0
- data/ext/tb_client/tigerbeetle/src/stdx/flags.zig +1414 -0
- data/ext/tb_client/tigerbeetle/src/stdx/mlock.zig +92 -0
- data/ext/tb_client/tigerbeetle/src/stdx/prng.zig +677 -0
- data/ext/tb_client/tigerbeetle/src/stdx/radix.zig +336 -0
- data/ext/tb_client/tigerbeetle/src/stdx/ring_buffer.zig +511 -0
- data/ext/tb_client/tigerbeetle/src/stdx/sort_test.zig +112 -0
- data/ext/tb_client/tigerbeetle/src/stdx/stdx.zig +1160 -0
- data/ext/tb_client/tigerbeetle/src/stdx/testing/low_level_hash_vectors.zig +142 -0
- data/ext/tb_client/tigerbeetle/src/stdx/testing/snaptest.zig +361 -0
- data/ext/tb_client/tigerbeetle/src/stdx/time_units.zig +275 -0
- data/ext/tb_client/tigerbeetle/src/stdx/unshare.zig +295 -0
- data/ext/tb_client/tigerbeetle/src/stdx/vendored/aegis.zig +436 -0
- data/ext/tb_client/tigerbeetle/src/stdx/windows.zig +48 -0
- data/ext/tb_client/tigerbeetle/src/stdx/zipfian.zig +402 -0
- data/ext/tb_client/tigerbeetle/src/storage.zig +489 -0
- data/ext/tb_client/tigerbeetle/src/storage_fuzz.zig +180 -0
- data/ext/tb_client/tigerbeetle/src/testing/bench.zig +146 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/grid_checker.zig +53 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/journal_checker.zig +61 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/manifest_checker.zig +76 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/message_bus.zig +110 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/network.zig +412 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/state_checker.zig +331 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster/storage_checker.zig +458 -0
- data/ext/tb_client/tigerbeetle/src/testing/cluster.zig +1198 -0
- data/ext/tb_client/tigerbeetle/src/testing/exhaustigen.zig +128 -0
- data/ext/tb_client/tigerbeetle/src/testing/fixtures.zig +181 -0
- data/ext/tb_client/tigerbeetle/src/testing/fuzz.zig +144 -0
- data/ext/tb_client/tigerbeetle/src/testing/id.zig +97 -0
- data/ext/tb_client/tigerbeetle/src/testing/io.zig +317 -0
- data/ext/tb_client/tigerbeetle/src/testing/marks.zig +126 -0
- data/ext/tb_client/tigerbeetle/src/testing/packet_simulator.zig +533 -0
- data/ext/tb_client/tigerbeetle/src/testing/reply_sequence.zig +154 -0
- data/ext/tb_client/tigerbeetle/src/testing/state_machine.zig +389 -0
- data/ext/tb_client/tigerbeetle/src/testing/storage.zig +1247 -0
- data/ext/tb_client/tigerbeetle/src/testing/table.zig +249 -0
- data/ext/tb_client/tigerbeetle/src/testing/time.zig +98 -0
- data/ext/tb_client/tigerbeetle/src/testing/tmp_tigerbeetle.zig +212 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/constants.zig +26 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/faulty_network.zig +580 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/java_driver/ci.zig +39 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/logged_process.zig +214 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/rust_driver/ci.zig +34 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/supervisor.zig +766 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/workload.zig +543 -0
- data/ext/tb_client/tigerbeetle/src/testing/vortex/zig_driver.zig +181 -0
- data/ext/tb_client/tigerbeetle/src/tidy.zig +1448 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/benchmark_driver.zig +227 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/benchmark_load.zig +1069 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/cli.zig +1422 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/inspect.zig +1658 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/inspect_integrity.zig +518 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/libtb_client.zig +36 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle/main.zig +646 -0
- data/ext/tb_client/tigerbeetle/src/tigerbeetle.zig +958 -0
- data/ext/tb_client/tigerbeetle/src/time.zig +236 -0
- data/ext/tb_client/tigerbeetle/src/trace/event.zig +745 -0
- data/ext/tb_client/tigerbeetle/src/trace/statsd.zig +462 -0
- data/ext/tb_client/tigerbeetle/src/trace.zig +556 -0
- data/ext/tb_client/tigerbeetle/src/unit_tests.zig +321 -0
- data/ext/tb_client/tigerbeetle/src/vopr.zig +1785 -0
- data/ext/tb_client/tigerbeetle/src/vortex.zig +101 -0
- data/ext/tb_client/tigerbeetle/src/vsr/checkpoint_trailer.zig +473 -0
- data/ext/tb_client/tigerbeetle/src/vsr/checksum.zig +208 -0
- data/ext/tb_client/tigerbeetle/src/vsr/checksum_benchmark.zig +43 -0
- data/ext/tb_client/tigerbeetle/src/vsr/client.zig +768 -0
- data/ext/tb_client/tigerbeetle/src/vsr/client_replies.zig +532 -0
- data/ext/tb_client/tigerbeetle/src/vsr/client_sessions.zig +338 -0
- data/ext/tb_client/tigerbeetle/src/vsr/clock.zig +1019 -0
- data/ext/tb_client/tigerbeetle/src/vsr/fault_detector.zig +279 -0
- data/ext/tb_client/tigerbeetle/src/vsr/free_set.zig +1381 -0
- data/ext/tb_client/tigerbeetle/src/vsr/free_set_fuzz.zig +315 -0
- data/ext/tb_client/tigerbeetle/src/vsr/grid.zig +1460 -0
- data/ext/tb_client/tigerbeetle/src/vsr/grid_blocks_missing.zig +757 -0
- data/ext/tb_client/tigerbeetle/src/vsr/grid_scrubber.zig +797 -0
- data/ext/tb_client/tigerbeetle/src/vsr/journal.zig +2586 -0
- data/ext/tb_client/tigerbeetle/src/vsr/marzullo.zig +308 -0
- data/ext/tb_client/tigerbeetle/src/vsr/message_header.zig +1777 -0
- data/ext/tb_client/tigerbeetle/src/vsr/multi_batch.zig +715 -0
- data/ext/tb_client/tigerbeetle/src/vsr/multi_batch_fuzz.zig +185 -0
- data/ext/tb_client/tigerbeetle/src/vsr/repair_budget.zig +333 -0
- data/ext/tb_client/tigerbeetle/src/vsr/replica.zig +12355 -0
- data/ext/tb_client/tigerbeetle/src/vsr/replica_format.zig +416 -0
- data/ext/tb_client/tigerbeetle/src/vsr/replica_reformat.zig +165 -0
- data/ext/tb_client/tigerbeetle/src/vsr/replica_test.zig +2910 -0
- data/ext/tb_client/tigerbeetle/src/vsr/routing.zig +1075 -0
- data/ext/tb_client/tigerbeetle/src/vsr/superblock.zig +1603 -0
- data/ext/tb_client/tigerbeetle/src/vsr/superblock_fuzz.zig +484 -0
- data/ext/tb_client/tigerbeetle/src/vsr/superblock_quorums.zig +405 -0
- data/ext/tb_client/tigerbeetle/src/vsr/superblock_quorums_fuzz.zig +355 -0
- data/ext/tb_client/tigerbeetle/src/vsr/sync.zig +29 -0
- data/ext/tb_client/tigerbeetle/src/vsr.zig +1727 -0
- data/lib/tb_client/shared_lib.rb +12 -5
- data/lib/tigerbeetle/client.rb +1 -1
- data/lib/tigerbeetle/platforms.rb +9 -0
- data/lib/tigerbeetle/version.rb +2 -2
- data/tigerbeetle.gemspec +22 -5
- metadata +242 -3
- data/ext/tb_client/pkg.tar.gz +0 -0
|
@@ -0,0 +1,865 @@
|
|
|
1
|
+
const std = @import("std");
|
|
2
|
+
const builtin = @import("builtin");
|
|
3
|
+
|
|
4
|
+
const assert = std.debug.assert;
|
|
5
|
+
const math = std.math;
|
|
6
|
+
const mem = std.mem;
|
|
7
|
+
const meta = std.meta;
|
|
8
|
+
|
|
9
|
+
const stdx = @import("stdx");
|
|
10
|
+
|
|
11
|
+
const div_ceil = stdx.div_ceil;
|
|
12
|
+
const maybe = stdx.maybe;
|
|
13
|
+
const fastrange = stdx.fastrange;
|
|
14
|
+
|
|
15
|
+
pub const Layout = struct {
|
|
16
|
+
ways: u64 = 16,
|
|
17
|
+
tag_bits: u64 = 8,
|
|
18
|
+
clock_bits: u64 = 2,
|
|
19
|
+
cache_line_size: u64 = 64,
|
|
20
|
+
/// Set this to a non-null value to override the alignment of the stored values.
|
|
21
|
+
value_alignment: ?u29 = null,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const Metrics = struct {
|
|
25
|
+
hits: u64 = 0,
|
|
26
|
+
misses: u64 = 0,
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
/// Each Key is associated with a set of n consecutive ways (or slots) that may contain the Value.
|
|
30
|
+
pub fn SetAssociativeCacheType(
|
|
31
|
+
comptime Key: type,
|
|
32
|
+
comptime Value: type,
|
|
33
|
+
comptime key_from_value: fn (*const Value) callconv(.@"inline") Key,
|
|
34
|
+
comptime hash: fn (Key) callconv(.@"inline") u64,
|
|
35
|
+
comptime layout: Layout,
|
|
36
|
+
) type {
|
|
37
|
+
assert(math.isPowerOfTwo(@sizeOf(Key)));
|
|
38
|
+
assert(math.isPowerOfTwo(@sizeOf(Value)));
|
|
39
|
+
|
|
40
|
+
switch (layout.ways) {
|
|
41
|
+
// An 8-way set-associative cache has the clock hand as a u3, which would introduce padding.
|
|
42
|
+
2, 4, 16 => {},
|
|
43
|
+
else => @compileError("ways must be 2, 4 or 16 for optimal CLOCK hand size."),
|
|
44
|
+
}
|
|
45
|
+
switch (layout.tag_bits) {
|
|
46
|
+
8, 16 => {},
|
|
47
|
+
else => @compileError("tag_bits must be 8 or 16."),
|
|
48
|
+
}
|
|
49
|
+
switch (layout.clock_bits) {
|
|
50
|
+
1, 2, 4 => {},
|
|
51
|
+
else => @compileError("clock_bits must be 1, 2 or 4."),
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (layout.value_alignment) |alignment| {
|
|
55
|
+
assert(alignment >= @alignOf(Value));
|
|
56
|
+
assert(@sizeOf(Value) % alignment == 0);
|
|
57
|
+
}
|
|
58
|
+
const value_alignment = layout.value_alignment orelse @alignOf(Value);
|
|
59
|
+
|
|
60
|
+
assert(math.isPowerOfTwo(layout.ways));
|
|
61
|
+
assert(math.isPowerOfTwo(layout.tag_bits));
|
|
62
|
+
assert(math.isPowerOfTwo(layout.clock_bits));
|
|
63
|
+
assert(math.isPowerOfTwo(layout.cache_line_size));
|
|
64
|
+
|
|
65
|
+
assert(@sizeOf(Key) <= @sizeOf(Value));
|
|
66
|
+
assert(@sizeOf(Key) < layout.cache_line_size);
|
|
67
|
+
assert(layout.cache_line_size % @sizeOf(Key) == 0);
|
|
68
|
+
|
|
69
|
+
if (layout.cache_line_size > @sizeOf(Value)) {
|
|
70
|
+
assert(layout.cache_line_size % @sizeOf(Value) == 0);
|
|
71
|
+
} else {
|
|
72
|
+
assert(@sizeOf(Value) % layout.cache_line_size == 0);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
const clock_hand_bits = math.log2_int(u64, layout.ways);
|
|
76
|
+
assert(math.isPowerOfTwo(clock_hand_bits));
|
|
77
|
+
assert((1 << clock_hand_bits) == layout.ways);
|
|
78
|
+
|
|
79
|
+
const tags_per_line = @divExact(layout.cache_line_size * 8, layout.ways * layout.tag_bits);
|
|
80
|
+
assert(tags_per_line > 0);
|
|
81
|
+
|
|
82
|
+
const clocks_per_line = @divExact(layout.cache_line_size * 8, layout.ways * layout.clock_bits);
|
|
83
|
+
assert(clocks_per_line > 0);
|
|
84
|
+
|
|
85
|
+
const clock_hands_per_line = @divExact(layout.cache_line_size * 8, clock_hand_bits);
|
|
86
|
+
assert(clock_hands_per_line > 0);
|
|
87
|
+
|
|
88
|
+
return struct {
|
|
89
|
+
const SetAssociativeCache = @This();
|
|
90
|
+
|
|
91
|
+
const Tag = meta.Int(.unsigned, layout.tag_bits);
|
|
92
|
+
const Count = meta.Int(.unsigned, layout.clock_bits);
|
|
93
|
+
const Clock = meta.Int(.unsigned, clock_hand_bits);
|
|
94
|
+
|
|
95
|
+
/// We don't require `value_count_max` in `init` to be a power of 2, but we do require
|
|
96
|
+
/// it to be a multiple of `value_count_max_multiple`. The calculation below
|
|
97
|
+
/// follows from a multiple which will satisfy all asserts.
|
|
98
|
+
pub const value_count_max_multiple: u64 = @max(
|
|
99
|
+
// `values`:
|
|
100
|
+
@divExact(
|
|
101
|
+
@max(@sizeOf(Value), layout.cache_line_size),
|
|
102
|
+
@min(@sizeOf(Value), layout.cache_line_size),
|
|
103
|
+
) * layout.ways,
|
|
104
|
+
@divExact(layout.cache_line_size * 8, layout.clock_bits), // `counts`
|
|
105
|
+
);
|
|
106
|
+
|
|
107
|
+
name: []const u8,
|
|
108
|
+
sets: u64,
|
|
109
|
+
|
|
110
|
+
metrics: *Metrics,
|
|
111
|
+
|
|
112
|
+
/// A short, partial hash of a Key, corresponding to a Value.
|
|
113
|
+
/// Because the tag is small, collisions are possible:
|
|
114
|
+
/// `tag(v₁) = tag(v₂)` does not imply `v₁ = v₂`.
|
|
115
|
+
/// However, most of the time, where the tag differs, a full key comparison can be avoided.
|
|
116
|
+
/// Since tags are 16-32x smaller than keys, they can also be kept hot in cache.
|
|
117
|
+
tags: []Tag,
|
|
118
|
+
|
|
119
|
+
/// When the corresponding Count is zero, the Value is absent.
|
|
120
|
+
values: []align(value_alignment) Value,
|
|
121
|
+
|
|
122
|
+
/// Each value has a Count, which tracks the number of recent reads.
|
|
123
|
+
///
|
|
124
|
+
/// * A Count is incremented when the value is accessed by `get`.
|
|
125
|
+
/// * A Count is decremented when a cache write to the value's Set misses.
|
|
126
|
+
/// * The value is evicted when its Count reaches zero.
|
|
127
|
+
///
|
|
128
|
+
counts: PackedUnsignedIntegerArrayType(Count),
|
|
129
|
+
|
|
130
|
+
/// Each set has a Clock: a counter that cycles between each of the set's ways (i.e. slots).
|
|
131
|
+
///
|
|
132
|
+
/// On cache write, entries are checked for occupancy (or eviction) beginning from the
|
|
133
|
+
/// clock's position, wrapping around.
|
|
134
|
+
///
|
|
135
|
+
/// The algorithm implemented is "CLOCK Nth-Chance" — each way has more than one bit,
|
|
136
|
+
/// to give ways more than one chance before eviction.
|
|
137
|
+
///
|
|
138
|
+
/// * A similar algorithm called "RRIParoo" is described in
|
|
139
|
+
/// "Kangaroo: Caching Billions of Tiny Objects on Flash".
|
|
140
|
+
/// * For more general information on CLOCK algorithms, see:
|
|
141
|
+
/// https://en.wikipedia.org/wiki/Page_replacement_algorithm.
|
|
142
|
+
clocks: PackedUnsignedIntegerArrayType(Clock),
|
|
143
|
+
|
|
144
|
+
pub const Options = struct { name: []const u8 };
|
|
145
|
+
|
|
146
|
+
pub fn init(
|
|
147
|
+
allocator: mem.Allocator,
|
|
148
|
+
value_count_max: u64,
|
|
149
|
+
options: Options,
|
|
150
|
+
) !SetAssociativeCache {
|
|
151
|
+
const sets = @divExact(value_count_max, layout.ways);
|
|
152
|
+
|
|
153
|
+
assert(value_count_max > 0);
|
|
154
|
+
assert(value_count_max >= layout.ways);
|
|
155
|
+
assert(value_count_max % layout.ways == 0);
|
|
156
|
+
|
|
157
|
+
const values_size_max = value_count_max * @sizeOf(Value);
|
|
158
|
+
assert(values_size_max >= layout.cache_line_size);
|
|
159
|
+
assert(values_size_max % layout.cache_line_size == 0);
|
|
160
|
+
|
|
161
|
+
const counts_size = @divExact(value_count_max * layout.clock_bits, 8);
|
|
162
|
+
assert(counts_size >= layout.cache_line_size);
|
|
163
|
+
assert(counts_size % layout.cache_line_size == 0);
|
|
164
|
+
|
|
165
|
+
// Each clock hand is guaranteed (by comptime asserts) to not span multiple cache lines.
|
|
166
|
+
// But in order to shrink the lower-bound cache size, we do not require that `clocks`
|
|
167
|
+
// itself is a multiple of the cache line size.
|
|
168
|
+
const clocks_size = @divExact(sets * clock_hand_bits, 8);
|
|
169
|
+
maybe(clocks_size >= layout.cache_line_size);
|
|
170
|
+
maybe(clocks_size % layout.cache_line_size == 0);
|
|
171
|
+
|
|
172
|
+
assert(value_count_max % value_count_max_multiple == 0);
|
|
173
|
+
|
|
174
|
+
const tags = try allocator.alloc(Tag, value_count_max);
|
|
175
|
+
errdefer allocator.free(tags);
|
|
176
|
+
|
|
177
|
+
const values = try allocator.alignedAlloc(
|
|
178
|
+
Value,
|
|
179
|
+
value_alignment,
|
|
180
|
+
value_count_max,
|
|
181
|
+
);
|
|
182
|
+
errdefer allocator.free(values);
|
|
183
|
+
|
|
184
|
+
const counts = try allocator.alloc(u64, @divExact(counts_size, @sizeOf(u64)));
|
|
185
|
+
errdefer allocator.free(counts);
|
|
186
|
+
|
|
187
|
+
const clocks = try allocator.alloc(u64, div_ceil(clocks_size, @sizeOf(u64)));
|
|
188
|
+
errdefer allocator.free(clocks);
|
|
189
|
+
|
|
190
|
+
// Explicitly allocated so that get / get_index can be `*const SetAssociativeCache`.
|
|
191
|
+
const metrics = try allocator.create(Metrics);
|
|
192
|
+
errdefer allocator.destroy(metrics);
|
|
193
|
+
|
|
194
|
+
var self = SetAssociativeCache{
|
|
195
|
+
.name = options.name,
|
|
196
|
+
.sets = sets,
|
|
197
|
+
.tags = tags,
|
|
198
|
+
.values = values,
|
|
199
|
+
.counts = .{ .words = counts },
|
|
200
|
+
.clocks = .{ .words = clocks },
|
|
201
|
+
.metrics = metrics,
|
|
202
|
+
};
|
|
203
|
+
|
|
204
|
+
self.reset();
|
|
205
|
+
|
|
206
|
+
return self;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
pub fn deinit(self: *SetAssociativeCache, allocator: mem.Allocator) void {
|
|
210
|
+
assert(self.sets > 0);
|
|
211
|
+
self.sets = 0;
|
|
212
|
+
|
|
213
|
+
allocator.free(self.tags);
|
|
214
|
+
allocator.free(self.values);
|
|
215
|
+
allocator.free(self.counts.words);
|
|
216
|
+
allocator.free(self.clocks.words);
|
|
217
|
+
allocator.destroy(self.metrics);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
pub fn reset(self: *SetAssociativeCache) void {
|
|
221
|
+
@memset(self.tags, 0);
|
|
222
|
+
@memset(self.counts.words, 0);
|
|
223
|
+
@memset(self.clocks.words, 0);
|
|
224
|
+
self.metrics.* = .{};
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
pub fn get_index(self: *const SetAssociativeCache, key: Key) ?usize {
|
|
228
|
+
const set = self.associate(key);
|
|
229
|
+
if (self.search(set, key)) |way| {
|
|
230
|
+
self.metrics.hits += 1;
|
|
231
|
+
const count = self.counts.get(set.offset + way);
|
|
232
|
+
self.counts.set(set.offset + way, count +| 1);
|
|
233
|
+
return set.offset + way;
|
|
234
|
+
} else {
|
|
235
|
+
self.metrics.misses += 1;
|
|
236
|
+
return null;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
pub fn get(self: *const SetAssociativeCache, key: Key) ?*align(value_alignment) Value {
|
|
241
|
+
const index = self.get_index(key) orelse return null;
|
|
242
|
+
return @alignCast(&self.values[index]);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/// Remove a key from the set associative cache if present.
|
|
246
|
+
/// Returns the removed value, if any.
|
|
247
|
+
pub fn remove(self: *SetAssociativeCache, key: Key) ?Value {
|
|
248
|
+
const set = self.associate(key);
|
|
249
|
+
const way = self.search(set, key) orelse return null;
|
|
250
|
+
|
|
251
|
+
const removed: Value = set.values[way];
|
|
252
|
+
self.counts.set(set.offset + way, 0);
|
|
253
|
+
set.values[way] = undefined;
|
|
254
|
+
|
|
255
|
+
return removed;
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/// Hint that the key is less likely to be accessed in the future, without actually removing
|
|
259
|
+
/// it from the cache.
|
|
260
|
+
pub fn demote(self: *SetAssociativeCache, key: Key) void {
|
|
261
|
+
const set = self.associate(key);
|
|
262
|
+
const way = self.search(set, key) orelse return;
|
|
263
|
+
|
|
264
|
+
self.counts.set(set.offset + way, 1);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
/// If the key is present in the set, returns the way. Otherwise returns null.
|
|
268
|
+
inline fn search(self: *const SetAssociativeCache, set: Set, key: Key) ?u16 {
|
|
269
|
+
const ways: u16 = search_tags(set.tags, set.tag);
|
|
270
|
+
if (ways == 0) return null;
|
|
271
|
+
|
|
272
|
+
// Iterate over all ways to help the OOO execution.
|
|
273
|
+
for (0..layout.ways) |way| {
|
|
274
|
+
if ((ways >> @as(u4, @intCast(way)) & 1) == 1 and
|
|
275
|
+
self.counts.get(set.offset + way) > 0)
|
|
276
|
+
{
|
|
277
|
+
if (key_from_value(&set.values[way]) == key) {
|
|
278
|
+
return @intCast(way);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
return null;
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
/// Where each set bit represents the index of a way that has the same tag.
|
|
286
|
+
const Ways = meta.Int(.unsigned, layout.ways);
|
|
287
|
+
|
|
288
|
+
inline fn search_tags(tags: *const [layout.ways]Tag, tag: Tag) Ways {
|
|
289
|
+
const x: @Vector(layout.ways, Tag) = tags.*;
|
|
290
|
+
const y: @Vector(layout.ways, Tag) = @splat(tag);
|
|
291
|
+
|
|
292
|
+
const result: @Vector(layout.ways, bool) = x == y;
|
|
293
|
+
return @bitCast(result);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/// Upsert a value, evicting an older entry if needed. The evicted value, if an update or
|
|
297
|
+
/// insert was performed and the index at which the value was inserted is returned.
|
|
298
|
+
pub fn upsert(self: *SetAssociativeCache, value: *const Value) struct {
|
|
299
|
+
index: usize,
|
|
300
|
+
updated: UpdateOrInsert,
|
|
301
|
+
evicted: ?Value,
|
|
302
|
+
} {
|
|
303
|
+
const key = key_from_value(value);
|
|
304
|
+
const set = self.associate(key);
|
|
305
|
+
if (self.search(set, key)) |way| {
|
|
306
|
+
// Overwrite the old entry for this key.
|
|
307
|
+
self.counts.set(set.offset + way, 1);
|
|
308
|
+
const evicted = set.values[way];
|
|
309
|
+
set.values[way] = value.*;
|
|
310
|
+
return .{
|
|
311
|
+
.index = set.offset + way,
|
|
312
|
+
.updated = .update,
|
|
313
|
+
.evicted = evicted,
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
const clock_index = @divExact(set.offset, layout.ways);
|
|
318
|
+
|
|
319
|
+
var way = self.clocks.get(clock_index);
|
|
320
|
+
comptime assert(math.maxInt(@TypeOf(way)) == layout.ways - 1);
|
|
321
|
+
comptime assert(@as(@TypeOf(way), math.maxInt(@TypeOf(way))) +% 1 == 0);
|
|
322
|
+
|
|
323
|
+
// The maximum number of iterations happens when every slot in the set has the maximum
|
|
324
|
+
// count. In this case, the loop will iterate until all counts have been decremented
|
|
325
|
+
// to 1. Then in the next iteration it will decrement a count to 0 and break.
|
|
326
|
+
const clock_iterations_max = layout.ways * (math.maxInt(Count) - 1);
|
|
327
|
+
|
|
328
|
+
var evicted: ?Value = null;
|
|
329
|
+
var safety_count: usize = 0;
|
|
330
|
+
while (safety_count <= clock_iterations_max) : ({
|
|
331
|
+
safety_count += 1;
|
|
332
|
+
way +%= 1;
|
|
333
|
+
}) {
|
|
334
|
+
var count = self.counts.get(set.offset + way);
|
|
335
|
+
if (count == 0) break; // Way is already free.
|
|
336
|
+
|
|
337
|
+
count -= 1;
|
|
338
|
+
self.counts.set(set.offset + way, count);
|
|
339
|
+
if (count == 0) {
|
|
340
|
+
// Way has become free.
|
|
341
|
+
evicted = set.values[way];
|
|
342
|
+
break;
|
|
343
|
+
}
|
|
344
|
+
} else {
|
|
345
|
+
unreachable;
|
|
346
|
+
}
|
|
347
|
+
assert(self.counts.get(set.offset + way) == 0);
|
|
348
|
+
|
|
349
|
+
set.tags[way] = set.tag;
|
|
350
|
+
set.values[way] = value.*;
|
|
351
|
+
self.counts.set(set.offset + way, 1);
|
|
352
|
+
self.clocks.set(clock_index, way +% 1);
|
|
353
|
+
|
|
354
|
+
return .{
|
|
355
|
+
.index = set.offset + way,
|
|
356
|
+
.updated = .insert,
|
|
357
|
+
.evicted = evicted,
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
const Set = struct {
|
|
362
|
+
tag: Tag,
|
|
363
|
+
offset: u64,
|
|
364
|
+
tags: *[layout.ways]Tag,
|
|
365
|
+
values: *[layout.ways]Value,
|
|
366
|
+
|
|
367
|
+
fn inspect(set: Set, sac: SetAssociativeCache) void {
|
|
368
|
+
const clock_index = @divExact(set.offset, layout.ways);
|
|
369
|
+
std.debug.print(
|
|
370
|
+
\\{{
|
|
371
|
+
\\ tag={}
|
|
372
|
+
\\ offset={}
|
|
373
|
+
\\ clock_hand={}
|
|
374
|
+
, .{
|
|
375
|
+
set.tag,
|
|
376
|
+
set.offset,
|
|
377
|
+
sac.clocks.get(clock_index),
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
std.debug.print("\n tags={}", .{set.tags[0]});
|
|
381
|
+
for (set.tags[1..]) |tag| std.debug.print(", {}", .{tag});
|
|
382
|
+
|
|
383
|
+
std.debug.print("\n values={}", .{set.values[0]});
|
|
384
|
+
for (set.values[1..]) |value| std.debug.print(", {}", .{value});
|
|
385
|
+
|
|
386
|
+
std.debug.print("\n counts={}", .{sac.counts.get(set.offset)});
|
|
387
|
+
var i: usize = 1;
|
|
388
|
+
while (i < layout.ways) : (i += 1) {
|
|
389
|
+
std.debug.print(", {}", .{sac.counts.get(set.offset + i)});
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
std.debug.print("\n}}\n", .{});
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
|
|
396
|
+
inline fn associate(self: *const SetAssociativeCache, key: Key) Set {
|
|
397
|
+
const entropy = hash(key);
|
|
398
|
+
|
|
399
|
+
const tag: Tag = @truncate(entropy);
|
|
400
|
+
const index = fastrange(entropy, self.sets);
|
|
401
|
+
const offset = index * layout.ways;
|
|
402
|
+
|
|
403
|
+
return .{
|
|
404
|
+
.tag = tag,
|
|
405
|
+
.offset = offset,
|
|
406
|
+
.tags = self.tags[offset..][0..layout.ways],
|
|
407
|
+
.values = self.values[offset..][0..layout.ways],
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
pub fn inspect() void {
|
|
412
|
+
std.debug.print("\nKey={} Value={} ways={} tag_bits={} clock_bits={} " ++
|
|
413
|
+
"clock_hand_bits={} tags_per_line={} clocks_per_line={} " ++
|
|
414
|
+
"clock_hands_per_line={}\n", .{
|
|
415
|
+
@bitSizeOf(Key),
|
|
416
|
+
@sizeOf(Value),
|
|
417
|
+
layout.ways,
|
|
418
|
+
layout.tag_bits,
|
|
419
|
+
layout.clock_bits,
|
|
420
|
+
clock_hand_bits,
|
|
421
|
+
tags_per_line,
|
|
422
|
+
clocks_per_line,
|
|
423
|
+
clock_hands_per_line,
|
|
424
|
+
});
|
|
425
|
+
}
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
pub const UpdateOrInsert = enum { update, insert };
|
|
430
|
+
|
|
431
|
+
fn set_associative_cache_test(
|
|
432
|
+
comptime Key: type,
|
|
433
|
+
comptime Value: type,
|
|
434
|
+
comptime context: type,
|
|
435
|
+
comptime layout: Layout,
|
|
436
|
+
) type {
|
|
437
|
+
const testing = std.testing;
|
|
438
|
+
const expect = testing.expect;
|
|
439
|
+
const expectEqual = testing.expectEqual;
|
|
440
|
+
|
|
441
|
+
const log = false;
|
|
442
|
+
|
|
443
|
+
const SAC = SetAssociativeCacheType(
|
|
444
|
+
Key,
|
|
445
|
+
Value,
|
|
446
|
+
context.key_from_value,
|
|
447
|
+
context.hash,
|
|
448
|
+
layout,
|
|
449
|
+
);
|
|
450
|
+
|
|
451
|
+
return struct {
|
|
452
|
+
fn run() !void {
|
|
453
|
+
if (log) SAC.inspect();
|
|
454
|
+
|
|
455
|
+
// TODO Add a nice calculator method to help solve the minimum value_count_max required:
|
|
456
|
+
var sac = try SAC.init(testing.allocator, 16 * 16 * 8, .{ .name = "test" });
|
|
457
|
+
defer sac.deinit(testing.allocator);
|
|
458
|
+
|
|
459
|
+
for (sac.tags) |tag| try testing.expectEqual(@as(SAC.Tag, 0), tag);
|
|
460
|
+
for (sac.counts.words) |word| try testing.expectEqual(@as(u64, 0), word);
|
|
461
|
+
for (sac.clocks.words) |word| try testing.expectEqual(@as(u64, 0), word);
|
|
462
|
+
|
|
463
|
+
// Fill up the first set entirely.
|
|
464
|
+
{
|
|
465
|
+
var i: usize = 0;
|
|
466
|
+
while (i < layout.ways) : (i += 1) {
|
|
467
|
+
try expectEqual(i, sac.clocks.get(0));
|
|
468
|
+
|
|
469
|
+
const key = i * sac.sets;
|
|
470
|
+
_ = sac.upsert(&key);
|
|
471
|
+
try expect(sac.counts.get(i) == 1);
|
|
472
|
+
try expectEqual(key, sac.get(key).?.*);
|
|
473
|
+
try expect(sac.counts.get(i) == 2);
|
|
474
|
+
}
|
|
475
|
+
try expect(sac.clocks.get(0) == 0);
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
if (log) sac.associate(0).inspect(sac);
|
|
479
|
+
|
|
480
|
+
// Insert another element into the first set, causing key 0 to be evicted.
|
|
481
|
+
{
|
|
482
|
+
const key = layout.ways * sac.sets;
|
|
483
|
+
_ = sac.upsert(&key);
|
|
484
|
+
try expect(sac.counts.get(0) == 1);
|
|
485
|
+
try expectEqual(key, sac.get(key).?.*);
|
|
486
|
+
try expect(sac.counts.get(0) == 2);
|
|
487
|
+
|
|
488
|
+
try expectEqual(@as(?*Value, null), sac.get(0));
|
|
489
|
+
|
|
490
|
+
{
|
|
491
|
+
var i: usize = 1;
|
|
492
|
+
while (i < layout.ways) : (i += 1) {
|
|
493
|
+
try expect(sac.counts.get(i) == 1);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (log) sac.associate(0).inspect(sac);
|
|
499
|
+
|
|
500
|
+
// Ensure removal works.
|
|
501
|
+
{
|
|
502
|
+
const key = 5 * sac.sets;
|
|
503
|
+
assert(sac.get(key).?.* == key);
|
|
504
|
+
try expect(sac.counts.get(5) == 2);
|
|
505
|
+
|
|
506
|
+
_ = sac.remove(key);
|
|
507
|
+
try expectEqual(@as(?*Value, null), sac.get(key));
|
|
508
|
+
try expect(sac.counts.get(5) == 0);
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
sac.reset();
|
|
512
|
+
|
|
513
|
+
for (sac.tags) |tag| try testing.expectEqual(@as(SAC.Tag, 0), tag);
|
|
514
|
+
for (sac.counts.words) |word| try testing.expectEqual(@as(u64, 0), word);
|
|
515
|
+
for (sac.clocks.words) |word| try testing.expectEqual(@as(u64, 0), word);
|
|
516
|
+
|
|
517
|
+
// Fill up the first set entirely, maxing out the count for each slot.
|
|
518
|
+
{
|
|
519
|
+
var i: usize = 0;
|
|
520
|
+
while (i < layout.ways) : (i += 1) {
|
|
521
|
+
try expectEqual(i, sac.clocks.get(0));
|
|
522
|
+
|
|
523
|
+
const key = i * sac.sets;
|
|
524
|
+
_ = sac.upsert(&key);
|
|
525
|
+
try expect(sac.counts.get(i) == 1);
|
|
526
|
+
var j: usize = 2;
|
|
527
|
+
while (j <= math.maxInt(SAC.Count)) : (j += 1) {
|
|
528
|
+
try expectEqual(key, sac.get(key).?.*);
|
|
529
|
+
try expect(sac.counts.get(i) == j);
|
|
530
|
+
}
|
|
531
|
+
try expectEqual(key, sac.get(key).?.*);
|
|
532
|
+
try expect(sac.counts.get(i) == math.maxInt(SAC.Count));
|
|
533
|
+
}
|
|
534
|
+
try expect(sac.clocks.get(0) == 0);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
if (log) sac.associate(0).inspect(sac);
|
|
538
|
+
|
|
539
|
+
// Insert another element into the first set, causing key 0 to be evicted.
|
|
540
|
+
{
|
|
541
|
+
const key = layout.ways * sac.sets;
|
|
542
|
+
_ = sac.upsert(&key);
|
|
543
|
+
try expect(sac.counts.get(0) == 1);
|
|
544
|
+
try expectEqual(key, sac.get(key).?.*);
|
|
545
|
+
try expect(sac.counts.get(0) == 2);
|
|
546
|
+
|
|
547
|
+
try expectEqual(@as(?*Value, null), sac.get(0));
|
|
548
|
+
|
|
549
|
+
{
|
|
550
|
+
var i: usize = 1;
|
|
551
|
+
while (i < layout.ways) : (i += 1) {
|
|
552
|
+
try expect(sac.counts.get(i) == 1);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
if (log) sac.associate(0).inspect(sac);
|
|
558
|
+
}
|
|
559
|
+
};
|
|
560
|
+
}
|
|
561
|
+
|
|
562
|
+
test "SetAssociativeCache: eviction" {
|
|
563
|
+
const Key = u64;
|
|
564
|
+
const Value = u64;
|
|
565
|
+
|
|
566
|
+
const context = struct {
|
|
567
|
+
inline fn key_from_value(value: *const Value) Key {
|
|
568
|
+
return value.*;
|
|
569
|
+
}
|
|
570
|
+
inline fn hash(key: Key) u64 {
|
|
571
|
+
return key;
|
|
572
|
+
}
|
|
573
|
+
};
|
|
574
|
+
|
|
575
|
+
try set_associative_cache_test(Key, Value, context, .{}).run();
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
test "SetAssociativeCache: hash collision" {
|
|
579
|
+
const Key = u64;
|
|
580
|
+
const Value = u64;
|
|
581
|
+
|
|
582
|
+
const context = struct {
|
|
583
|
+
inline fn key_from_value(value: *const Value) Key {
|
|
584
|
+
return value.*;
|
|
585
|
+
}
|
|
586
|
+
/// This hash function is intentionally broken to simulate hash collision.
|
|
587
|
+
inline fn hash(key: Key) u64 {
|
|
588
|
+
_ = key;
|
|
589
|
+
return 0;
|
|
590
|
+
}
|
|
591
|
+
inline fn equal(a: Key, b: Key) bool {
|
|
592
|
+
return a == b;
|
|
593
|
+
}
|
|
594
|
+
};
|
|
595
|
+
|
|
596
|
+
try set_associative_cache_test(Key, Value, context, .{}).run();
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/// A little simpler than PackedIntArray in the std lib, restricted to little endian 64-bit words,
|
|
600
|
+
/// and using words exactly without padding.
|
|
601
|
+
fn PackedUnsignedIntegerArrayType(comptime UInt: type) type {
|
|
602
|
+
const Word = u64;
|
|
603
|
+
|
|
604
|
+
assert(builtin.target.cpu.arch.endian() == .little);
|
|
605
|
+
assert(@typeInfo(UInt).int.signedness == .unsigned);
|
|
606
|
+
assert(@typeInfo(UInt).int.bits < @bitSizeOf(u8));
|
|
607
|
+
assert(math.isPowerOfTwo(@typeInfo(UInt).int.bits));
|
|
608
|
+
|
|
609
|
+
const word_bits = @bitSizeOf(Word);
|
|
610
|
+
const uint_bits = @bitSizeOf(UInt);
|
|
611
|
+
const uints_per_word = @divExact(word_bits, uint_bits);
|
|
612
|
+
|
|
613
|
+
// An index bounded by the number of unsigned integers that fit exactly into a word.
|
|
614
|
+
const WordIndex = meta.Int(.unsigned, math.log2_int(u64, uints_per_word));
|
|
615
|
+
assert(math.maxInt(WordIndex) == uints_per_word - 1);
|
|
616
|
+
|
|
617
|
+
// An index bounded by the number of bits (not unsigned integers) that fit exactly into a word.
|
|
618
|
+
const BitsIndex = math.Log2Int(Word);
|
|
619
|
+
assert(math.maxInt(BitsIndex) == @bitSizeOf(Word) - 1);
|
|
620
|
+
assert(math.maxInt(BitsIndex) == word_bits - 1);
|
|
621
|
+
assert(math.maxInt(BitsIndex) == uint_bits * (math.maxInt(WordIndex) + 1) - 1);
|
|
622
|
+
|
|
623
|
+
return struct {
|
|
624
|
+
const PackedUnsignedIntegerArray = @This();
|
|
625
|
+
|
|
626
|
+
words: []Word,
|
|
627
|
+
|
|
628
|
+
/// Returns the unsigned integer at `index`.
|
|
629
|
+
pub inline fn get(self: PackedUnsignedIntegerArray, index: u64) UInt {
|
|
630
|
+
// This truncate is safe since we want to mask the right-shifted word by exactly a UInt:
|
|
631
|
+
return @as(UInt, @truncate(self.word(index).* >> bits_index(index)));
|
|
632
|
+
}
|
|
633
|
+
|
|
634
|
+
/// Sets the unsigned integer at `index` to `value`.
|
|
635
|
+
pub inline fn set(self: PackedUnsignedIntegerArray, index: u64, value: UInt) void {
|
|
636
|
+
const w = self.word(index);
|
|
637
|
+
w.* &= ~mask(index);
|
|
638
|
+
w.* |= @as(Word, value) << bits_index(index);
|
|
639
|
+
}
|
|
640
|
+
|
|
641
|
+
inline fn mask(index: u64) Word {
|
|
642
|
+
return @as(Word, math.maxInt(UInt)) << bits_index(index);
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
inline fn word(self: PackedUnsignedIntegerArray, index: u64) *Word {
|
|
646
|
+
return &self.words[@divFloor(index, uints_per_word)];
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
inline fn bits_index(index: u64) BitsIndex {
|
|
650
|
+
// If uint_bits=2, then it's normal for the maximum return value value to be 62, even
|
|
651
|
+
// where BitsIndex allows up to 63 (inclusive) for a 64-bit word. This is because 62 is
|
|
652
|
+
// the bit index of the highest 2-bit UInt (e.g. bit index + bit length == 64).
|
|
653
|
+
comptime assert(uint_bits * (math.maxInt(WordIndex) + 1) == math.maxInt(BitsIndex) + 1);
|
|
654
|
+
|
|
655
|
+
return @as(BitsIndex, uint_bits) * @as(WordIndex, @truncate(index));
|
|
656
|
+
}
|
|
657
|
+
};
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
test "PackedUnsignedIntegerArray: unit" {
|
|
661
|
+
const expectEqual = std.testing.expectEqual;
|
|
662
|
+
|
|
663
|
+
var words = [8]u64{ 0, 0b10110010, 0, 0, 0, 0, 0, 0 };
|
|
664
|
+
|
|
665
|
+
var p: PackedUnsignedIntegerArrayType(u2) = .{
|
|
666
|
+
.words = &words,
|
|
667
|
+
};
|
|
668
|
+
|
|
669
|
+
try expectEqual(@as(u2, 0b10), p.get(32 + 0));
|
|
670
|
+
try expectEqual(@as(u2, 0b00), p.get(32 + 1));
|
|
671
|
+
try expectEqual(@as(u2, 0b11), p.get(32 + 2));
|
|
672
|
+
try expectEqual(@as(u2, 0b10), p.get(32 + 3));
|
|
673
|
+
|
|
674
|
+
p.set(0, 0b01);
|
|
675
|
+
try expectEqual(@as(u64, 0b00000001), words[0]);
|
|
676
|
+
try expectEqual(@as(u2, 0b01), p.get(0));
|
|
677
|
+
p.set(1, 0b10);
|
|
678
|
+
try expectEqual(@as(u64, 0b00001001), words[0]);
|
|
679
|
+
try expectEqual(@as(u2, 0b10), p.get(1));
|
|
680
|
+
p.set(2, 0b11);
|
|
681
|
+
try expectEqual(@as(u64, 0b00111001), words[0]);
|
|
682
|
+
try expectEqual(@as(u2, 0b11), p.get(2));
|
|
683
|
+
p.set(3, 0b11);
|
|
684
|
+
try expectEqual(@as(u64, 0b11111001), words[0]);
|
|
685
|
+
try expectEqual(@as(u2, 0b11), p.get(3));
|
|
686
|
+
p.set(3, 0b01);
|
|
687
|
+
try expectEqual(@as(u64, 0b01111001), words[0]);
|
|
688
|
+
try expectEqual(@as(u2, 0b01), p.get(3));
|
|
689
|
+
p.set(3, 0b00);
|
|
690
|
+
try expectEqual(@as(u64, 0b00111001), words[0]);
|
|
691
|
+
try expectEqual(@as(u2, 0b00), p.get(3));
|
|
692
|
+
|
|
693
|
+
p.set(4, 0b11);
|
|
694
|
+
try expectEqual(
|
|
695
|
+
@as(u64, 0b0000000000000000000000000000000000000000000000000000001100111001),
|
|
696
|
+
words[0],
|
|
697
|
+
);
|
|
698
|
+
p.set(31, 0b11);
|
|
699
|
+
try expectEqual(
|
|
700
|
+
@as(u64, 0b1100000000000000000000000000000000000000000000000000001100111001),
|
|
701
|
+
words[0],
|
|
702
|
+
);
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
fn ContextType(comptime UInt: type) type {
|
|
706
|
+
const testing = std.testing;
|
|
707
|
+
|
|
708
|
+
return struct {
|
|
709
|
+
const Context = @This();
|
|
710
|
+
|
|
711
|
+
const Array = PackedUnsignedIntegerArrayType(UInt);
|
|
712
|
+
prng: *stdx.PRNG,
|
|
713
|
+
|
|
714
|
+
array: Array,
|
|
715
|
+
reference: []UInt,
|
|
716
|
+
|
|
717
|
+
fn init(prng: *stdx.PRNG, len: usize) !Context {
|
|
718
|
+
const words = try testing.allocator.alloc(u64, @divExact(len * @bitSizeOf(UInt), 64));
|
|
719
|
+
errdefer testing.allocator.free(words);
|
|
720
|
+
|
|
721
|
+
const reference = try testing.allocator.alloc(UInt, len);
|
|
722
|
+
errdefer testing.allocator.free(reference);
|
|
723
|
+
|
|
724
|
+
@memset(words, 0);
|
|
725
|
+
@memset(reference, 0);
|
|
726
|
+
|
|
727
|
+
return .{
|
|
728
|
+
.prng = prng,
|
|
729
|
+
.array = Array{ .words = words },
|
|
730
|
+
.reference = reference,
|
|
731
|
+
};
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
fn deinit(context: *Context) void {
|
|
735
|
+
testing.allocator.free(context.array.words);
|
|
736
|
+
testing.allocator.free(context.reference);
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
fn run(context: *Context) !void {
|
|
740
|
+
var iterations: usize = 0;
|
|
741
|
+
while (iterations < 10_000) : (iterations += 1) {
|
|
742
|
+
const index = context.prng.index(context.reference);
|
|
743
|
+
const value = context.prng.int(UInt);
|
|
744
|
+
|
|
745
|
+
context.array.set(index, value);
|
|
746
|
+
context.reference[index] = value;
|
|
747
|
+
|
|
748
|
+
try context.verify();
|
|
749
|
+
}
|
|
750
|
+
}
|
|
751
|
+
|
|
752
|
+
fn verify(context: *Context) !void {
|
|
753
|
+
for (context.reference, 0..) |value, index| {
|
|
754
|
+
try testing.expectEqual(value, context.array.get(index));
|
|
755
|
+
}
|
|
756
|
+
}
|
|
757
|
+
};
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
test "PackedUnsignedIntegerArray: fuzz" {
|
|
761
|
+
const seed = 42;
|
|
762
|
+
var prng = stdx.PRNG.from_seed(seed);
|
|
763
|
+
|
|
764
|
+
inline for (.{ u1, u2, u4 }) |UInt| {
|
|
765
|
+
const Context = ContextType(UInt);
|
|
766
|
+
|
|
767
|
+
var context = try Context.init(&prng, 1024);
|
|
768
|
+
defer context.deinit();
|
|
769
|
+
|
|
770
|
+
try context.run();
|
|
771
|
+
}
|
|
772
|
+
}
|
|
773
|
+
|
|
774
|
+
fn search_tags_test(comptime Key: type, comptime Value: type, comptime layout: Layout) type {
|
|
775
|
+
const testing = std.testing;
|
|
776
|
+
|
|
777
|
+
const log = false;
|
|
778
|
+
|
|
779
|
+
const context = struct {
|
|
780
|
+
inline fn key_from_value(value: *const Value) Key {
|
|
781
|
+
return value.*;
|
|
782
|
+
}
|
|
783
|
+
inline fn hash(key: Key) u64 {
|
|
784
|
+
return key;
|
|
785
|
+
}
|
|
786
|
+
inline fn equal(a: Key, b: Key) bool {
|
|
787
|
+
return a == b;
|
|
788
|
+
}
|
|
789
|
+
};
|
|
790
|
+
|
|
791
|
+
const SAC = SetAssociativeCacheType(
|
|
792
|
+
Key,
|
|
793
|
+
Value,
|
|
794
|
+
context.key_from_value,
|
|
795
|
+
context.hash,
|
|
796
|
+
layout,
|
|
797
|
+
);
|
|
798
|
+
|
|
799
|
+
const reference = struct {
|
|
800
|
+
inline fn search_tags(tags: *[layout.ways]SAC.Tag, tag: SAC.Tag) SAC.Ways {
|
|
801
|
+
var bits: SAC.Ways = 0;
|
|
802
|
+
var count: usize = 0;
|
|
803
|
+
for (tags, 0..) |t, i| {
|
|
804
|
+
if (t == tag) {
|
|
805
|
+
const bit: math.Log2Int(SAC.Ways) = @intCast(i);
|
|
806
|
+
bits |= (@as(SAC.Ways, 1) << bit);
|
|
807
|
+
count += 1;
|
|
808
|
+
}
|
|
809
|
+
}
|
|
810
|
+
assert(@popCount(bits) == count);
|
|
811
|
+
return bits;
|
|
812
|
+
}
|
|
813
|
+
};
|
|
814
|
+
|
|
815
|
+
return struct {
|
|
816
|
+
fn run(prng: *stdx.PRNG) !void {
|
|
817
|
+
if (log) SAC.inspect();
|
|
818
|
+
|
|
819
|
+
var iterations: usize = 0;
|
|
820
|
+
while (iterations < 10_000) : (iterations += 1) {
|
|
821
|
+
var tags: [layout.ways]SAC.Tag = undefined;
|
|
822
|
+
prng.fill(mem.asBytes(&tags));
|
|
823
|
+
|
|
824
|
+
const tag = prng.int(SAC.Tag);
|
|
825
|
+
|
|
826
|
+
var indexes: [layout.ways]usize = undefined;
|
|
827
|
+
for (&indexes, 0..) |*x, i| x.* = i;
|
|
828
|
+
prng.shuffle(usize, &indexes);
|
|
829
|
+
|
|
830
|
+
const matches_count_min = prng.int_inclusive(u32, layout.ways);
|
|
831
|
+
for (indexes[0..matches_count_min]) |index| {
|
|
832
|
+
tags[index] = tag;
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
const expected = reference.search_tags(&tags, tag);
|
|
836
|
+
const actual = SAC.search_tags(&tags, tag);
|
|
837
|
+
if (log) std.debug.print("expected: {b:0>16}, actual: {b:0>16}\n", .{
|
|
838
|
+
expected,
|
|
839
|
+
actual,
|
|
840
|
+
});
|
|
841
|
+
try testing.expectEqual(expected, actual);
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
};
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
test "SetAssociativeCache: search_tags()" {
|
|
848
|
+
const seed = 42;
|
|
849
|
+
|
|
850
|
+
const Key = u64;
|
|
851
|
+
const Value = u64;
|
|
852
|
+
|
|
853
|
+
var prng = stdx.PRNG.from_seed(seed);
|
|
854
|
+
|
|
855
|
+
inline for ([_]u64{ 2, 4, 16 }) |ways| {
|
|
856
|
+
inline for ([_]u64{ 8, 16 }) |tag_bits| {
|
|
857
|
+
const case = search_tags_test(Key, Value, .{
|
|
858
|
+
.ways = ways,
|
|
859
|
+
.tag_bits = tag_bits,
|
|
860
|
+
});
|
|
861
|
+
|
|
862
|
+
try case.run(&prng);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
}
|