diff --git a/docs/docusaurus/docs/code/accountsdb.md b/docs/docusaurus/docs/code/accountsdb.md index 10a453abf7..ccace480a6 100644 --- a/docs/docusaurus/docs/code/accountsdb.md +++ b/docs/docusaurus/docs/code/accountsdb.md @@ -18,7 +18,6 @@ Other files include: - `fuzz.zig`: accounts-db's fuzzer - `download.zig`: downloading snapshots - `buffer_pool.zig`: buffer pool for reading from account files -- `swiss_map.zig`: high-performance swissmap hashmap implementation ## Usage @@ -145,16 +144,14 @@ For a random seed, ommit the seed argument. For infinite fuzzing, omit the numbe ## Benchmarking -We also have a few benchmarks for the database: +We also have a couple benchmarks for the database: - read/write benchmarks: this benchmarks the read/write speed for accounts (can use the `accounts_db_readwrite` flag for the benchmarking binary) - load and validate from a snapshot: this benchmarks the speed of loading and validating a snapshot (can use the `accounts_db_snapshot` flag for the benchmarking binary with the `-e` flag) -- swissmap benchmarks: benchmarks the swissmap hashmap implementation against stdlib hashmap -(can use the `swissmap` flag for the benchmarking binary) -The benchmarking code can be found in the structs `BenchmarkAccountsDB`, `BenchmarkAccountsDBSnapshotLoad`, -and `BenchmarkSwissMap` respectively. +The benchmarking code can be found in the structs `BenchmarkAccountsDB` and `BenchmarkAccountsDBSnapshotLoad` respectively. + ## Architecture Notes diff --git a/src/accountsdb/README.md b/src/accountsdb/README.md index 10a453abf7..ccace480a6 100644 --- a/src/accountsdb/README.md +++ b/src/accountsdb/README.md @@ -18,7 +18,6 @@ Other files include: - `fuzz.zig`: accounts-db's fuzzer - `download.zig`: downloading snapshots - `buffer_pool.zig`: buffer pool for reading from account files -- `swiss_map.zig`: high-performance swissmap hashmap implementation ## Usage @@ -145,16 +144,14 @@ For a random seed, ommit the seed argument. For infinite fuzzing, omit the numbe ## Benchmarking -We also have a few benchmarks for the database: +We also have a couple benchmarks for the database: - read/write benchmarks: this benchmarks the read/write speed for accounts (can use the `accounts_db_readwrite` flag for the benchmarking binary) - load and validate from a snapshot: this benchmarks the speed of loading and validating a snapshot (can use the `accounts_db_snapshot` flag for the benchmarking binary with the `-e` flag) -- swissmap benchmarks: benchmarks the swissmap hashmap implementation against stdlib hashmap -(can use the `swissmap` flag for the benchmarking binary) -The benchmarking code can be found in the structs `BenchmarkAccountsDB`, `BenchmarkAccountsDBSnapshotLoad`, -and `BenchmarkSwissMap` respectively. +The benchmarking code can be found in the structs `BenchmarkAccountsDB` and `BenchmarkAccountsDBSnapshotLoad` respectively. + ## Architecture Notes diff --git a/src/accountsdb/db.zig b/src/accountsdb/db.zig index 7326344c4c..7cc8457aed 100644 --- a/src/accountsdb/db.zig +++ b/src/accountsdb/db.zig @@ -357,11 +357,12 @@ pub const AccountsDB = struct { errdefer collapsed_manifest.deinit(self.allocator); if (should_fastload) { - var timer = try sig.time.Timer.start(); - var fastload_dir = try self.snapshot_dir.makeOpenPath("fastload_state", .{}); - defer fastload_dir.close(); - try self.fastload(fastload_dir, collapsed_manifest.accounts_db_fields); - self.logger.info().logf("fastload: total time: {s}", .{timer.read()}); + @panic("TODO(david): re-implement fastloading"); + // var timer = try sig.time.Timer.start(); + // var fastload_dir = try self.snapshot_dir.makeOpenPath("fastload_state", .{}); + // defer fastload_dir.close(); + // try self.fastload(fastload_dir, collapsed_manifest.accounts_db_fields); + // self.logger.info().logf("fastload: total time: {s}", .{timer.read()}); } else { var load_timer = try sig.time.Timer.start(); try self.loadFromSnapshot( @@ -375,9 +376,10 @@ pub const AccountsDB = struct { // no need to re-save if we just loaded from a fastload if (save_index and !should_fastload) { - var timer = try sig.time.Timer.start(); - _ = try self.saveStateForFastload(); - self.logger.info().logf("saveStateForFastload: total time: {s}", .{timer.read()}); + @panic("TODO(david): re-implement fastloading"); + // var timer = try sig.time.Timer.start(); + // _ = try self.saveStateForFastload(); + // self.logger.info().logf("saveStateForFastload: total time: {s}", .{timer.read()}); } if (validate) { @@ -467,83 +469,83 @@ pub const AccountsDB = struct { return collapsed_manifest; } - pub fn saveStateForFastload( - self: *AccountsDB, - ) !void { - const zone = tracy.Zone.init(@src(), .{ .name = "accountsdb fastsaveStateForFastloadload" }); - defer zone.deinit(); - - self.logger.info().log("running saveStateForFastload..."); - var fastload_dir = try self.snapshot_dir.makeOpenPath("fastload_state", .{}); - defer fastload_dir.close(); - try self.account_index.saveToDisk(fastload_dir); - } - - pub fn fastload( - self: *AccountsDB, - dir: std.fs.Dir, - snapshot_manifest: AccountsDbFields, - ) !void { - const zone = tracy.Zone.init(@src(), .{ .name = "accountsdb fastload" }); - defer zone.deinit(); - - self.logger.info().log("running fastload..."); - - var accounts_dir = try self.snapshot_dir.openDir("accounts", .{}); - defer accounts_dir.close(); - - const n_account_files = snapshot_manifest.file_map.count(); - self.logger.info().logf("found {d} account files", .{n_account_files}); - std.debug.assert(n_account_files > 0); - - const file_map, var file_map_lg = self.file_map.writeWithLock(); - defer file_map_lg.unlock(); - try file_map.ensureTotalCapacity(self.allocator, n_account_files); - - self.logger.info().log("loading account files"); - const file_info_map = snapshot_manifest.file_map; - for (file_info_map.keys(), file_info_map.values()) |slot, file_info| { - // read accounts file - var accounts_file = blk: { - const file_name_bounded = sig.utils.fmt.boundedFmt( - "{d}.{d}", - .{ slot, file_info.id.toInt() }, - ); - const file_name = file_name_bounded.constSlice(); - const accounts_file = accounts_dir.openFile(file_name, .{ - .mode = .read_write, - }) catch |err| { - self.logger.err().logf( - "Failed to open accounts/{s}: {s}", - .{ file_name, @errorName(err) }, - ); - return err; - }; - errdefer accounts_file.close(); - - break :blk AccountFile.init(accounts_file, file_info, slot) catch |err| { - self.logger.err().logf( - "failed to *open* AccountsFile {s}: {s}\n", - .{ file_name, @errorName(err) }, - ); - return err; - }; - }; - errdefer accounts_file.deinit(); - - // NOTE: no need to validate since we are fast loading - - // track file - const file_id = file_info.id; - file_map.putAssumeCapacityNoClobber(file_id, accounts_file); - self.largest_file_id = FileId.max(self.largest_file_id, file_id); - _ = self.largest_rooted_slot.fetchMax(slot, .release); - self.largest_flushed_slot.store(self.largest_rooted_slot.load(.acquire), .release); - } - - // NOTE: index loading was the most expensive part which we fastload here - try self.account_index.loadFromDisk(dir); - } + // pub fn saveStateForFastload( + // self: *AccountsDB, + // ) !void { + // const zone = tracy.Zone.init(@src(), .{ .name = "accountsdb fastsaveStateForFastloadload" }); + // defer zone.deinit(); + + // self.logger.info().log("running saveStateForFastload..."); + // var fastload_dir = try self.snapshot_dir.makeOpenPath("fastload_state", .{}); + // defer fastload_dir.close(); + // try self.account_index.saveToDisk(fastload_dir); + // } + + // pub fn fastload( + // self: *AccountsDB, + // dir: std.fs.Dir, + // snapshot_manifest: AccountsDbFields, + // ) !void { + // const zone = tracy.Zone.init(@src(), .{ .name = "accountsdb fastload" }); + // defer zone.deinit(); + + // self.logger.info().log("running fastload..."); + + // var accounts_dir = try self.snapshot_dir.openDir("accounts", .{}); + // defer accounts_dir.close(); + + // const n_account_files = snapshot_manifest.file_map.count(); + // self.logger.info().logf("found {d} account files", .{n_account_files}); + // std.debug.assert(n_account_files > 0); + + // const file_map, var file_map_lg = self.file_map.writeWithLock(); + // defer file_map_lg.unlock(); + // try file_map.ensureTotalCapacity(self.allocator, n_account_files); + + // self.logger.info().log("loading account files"); + // const file_info_map = snapshot_manifest.file_map; + // for (file_info_map.keys(), file_info_map.values()) |slot, file_info| { + // // read accounts file + // var accounts_file = blk: { + // const file_name_bounded = sig.utils.fmt.boundedFmt( + // "{d}.{d}", + // .{ slot, file_info.id.toInt() }, + // ); + // const file_name = file_name_bounded.constSlice(); + // const accounts_file = accounts_dir.openFile(file_name, .{ + // .mode = .read_write, + // }) catch |err| { + // self.logger.err().logf( + // "Failed to open accounts/{s}: {s}", + // .{ file_name, @errorName(err) }, + // ); + // return err; + // }; + // errdefer accounts_file.close(); + + // break :blk AccountFile.init(accounts_file, file_info, slot) catch |err| { + // self.logger.err().logf( + // "failed to *open* AccountsFile {s}: {s}\n", + // .{ file_name, @errorName(err) }, + // ); + // return err; + // }; + // }; + // errdefer accounts_file.deinit(); + + // // NOTE: no need to validate since we are fast loading + + // // track file + // const file_id = file_info.id; + // file_map.putAssumeCapacityNoClobber(file_id, accounts_file); + // self.largest_file_id = FileId.max(self.largest_file_id, file_id); + // _ = self.largest_rooted_slot.fetchMax(slot, .release); + // self.largest_flushed_slot.store(self.largest_rooted_slot.load(.acquire), .release); + // } + + // // NOTE: index loading was the most expensive part which we fastload here + // try self.account_index.loadFromDisk(dir); + // } /// loads the account files and generates the account index from a snapshot pub fn loadFromSnapshot( @@ -1013,6 +1015,7 @@ pub const AccountsDB = struct { .data_len = self.account_index.pubkey_ref_map.numberOfShards(), .max_threads = n_threads, .params = .{ + self.allocator, self.logger, &self.account_index, thread_dbs, @@ -1082,6 +1085,7 @@ pub const AccountsDB = struct { /// combines multiple thread indexes into the given index. /// each bin is also sorted by pubkey. pub fn mergeThreadIndexesMultiThread( + allocator: std.mem.Allocator, logger: Logger, index: *AccountIndex, thread_dbs: []const AccountsDB, @@ -1102,7 +1106,7 @@ pub const AccountsDB = struct { for (shard_start_index..shard_end_index, 1..) |shard_index, iteration_count| { // sum size across threads - var shard_n_accounts: usize = 0; + var shard_n_accounts: u32 = 0; for (thread_dbs) |*thread_db| { const pubkey_ref_map = &thread_db.account_index.pubkey_ref_map; shard_n_accounts += pubkey_ref_map.getShardCount(shard_index); @@ -1113,7 +1117,10 @@ pub const AccountsDB = struct { const shard_map, var shard_map_lg = index.pubkey_ref_map.getShardFromIndex(shard_index).writeWithLock(); defer shard_map_lg.unlock(); - try shard_map.ensureTotalCapacity(shard_n_accounts); + try shard_map.ensureTotalCapacity( + allocator, + shard_n_accounts, + ); } for (thread_dbs) |*thread_db| { @@ -1966,8 +1973,10 @@ pub const AccountsDB = struct { account_file: *AccountFile, n_accounts: usize, ) !void { - const shard_counts = - try self.allocator.alloc(usize, self.account_index.pubkey_ref_map.numberOfShards()); + const shard_counts = try self.allocator.alloc( + usize, + self.account_index.pubkey_ref_map.numberOfShards(), + ); defer self.allocator.free(shard_counts); @memset(shard_counts, 0); @@ -2147,7 +2156,7 @@ pub const AccountsDB = struct { const shard_map, var shard_map_lg = shard_map_rw.writeWithLock(); defer shard_map_lg.unlock(); - try shard_map.ensureTotalCapacity(1 + shard_map.count()); + try shard_map.ensureTotalCapacity(self.allocator, 1 + shard_map.count()); } // update index @@ -2372,8 +2381,10 @@ pub const AccountsDB = struct { } // prealloc the ref map space - const shard_counts = - try self.allocator.alloc(usize, self.account_index.pubkey_ref_map.numberOfShards()); + const shard_counts = try self.allocator.alloc( + u64, + self.account_index.pubkey_ref_map.numberOfShards(), + ); defer self.allocator.free(shard_counts); @memset(shard_counts, 0); @@ -3098,7 +3109,7 @@ pub fn indexAndValidateAccountFile( buffer_pool: *BufferPool, accounts_file: *AccountFile, shard_calculator: PubkeyShardCalculator, - shard_counts: ?[]usize, + shard_counts: ?[]u64, account_refs: *ArrayListUnmanaged(AccountRef), geyser_storage: ?*GeyserTmpStorage, ) ValidateAccountFileError!void { @@ -4052,8 +4063,6 @@ pub const BenchmarkAccountsDBSnapshotLoad = struct { pub fn loadAndVerifySnapshot(units: BenchTimeUnit, bench_args: BenchArgs) !struct { load_time: u64, validate_time: u64, - fastload_save_time: u64, - fastload_time: u64, } { const allocator = std.heap.c_allocator; var print_logger = sig.trace.DirectPrintLogger.init(allocator, .debug); @@ -4073,8 +4082,6 @@ pub const BenchmarkAccountsDBSnapshotLoad = struct { return .{ .load_time = zero_duration.asNanos(), .validate_time = zero_duration.asNanos(), - .fastload_save_time = zero_duration.asNanos(), - .fastload_time = zero_duration.asNanos(), }; }; defer snapshot_dir.close(); @@ -4090,7 +4097,6 @@ pub const BenchmarkAccountsDBSnapshotLoad = struct { const collapsed_manifest = try full_inc_manifest.collapse(allocator); const loading_duration, // - const fastload_save_duration, // const validate_duration // = duration_blk: { var accounts_db = try AccountsDB.init(.{ @@ -4113,11 +4119,11 @@ pub const BenchmarkAccountsDBSnapshotLoad = struct { ); const loading_duration = load_timer.read(); - const fastload_save_duration = blk: { - var timer = try sig.time.Timer.start(); - try accounts_db.saveStateForFastload(); - break :blk timer.read(); - }; + // const fastload_save_duration = blk: { + // var timer = try sig.time.Timer.start(); + // try accounts_db.saveStateForFastload(); + // break :blk timer.read(); + // }; const full_manifest = full_inc_manifest.full; const maybe_inc_persistence = if (full_inc_manifest.incremental) |inc| @@ -4139,34 +4145,32 @@ pub const BenchmarkAccountsDBSnapshotLoad = struct { }); const validate_duration = validate_timer.read(); - break :duration_blk .{ loading_duration, fastload_save_duration, validate_duration }; + break :duration_blk .{ loading_duration, validate_duration }; }; - const fastload_duration = blk: { - var fastload_accounts_db = try AccountsDB.init(.{ - .allocator = allocator, - .logger = .from(logger), - .snapshot_dir = snapshot_dir, - .geyser_writer = null, - .gossip_view = null, - .index_allocation = if (bench_args.use_disk) .disk else .ram, - .number_of_index_shards = 32, - }); - defer fastload_accounts_db.deinit(); - - var fastload_dir = try snapshot_dir.makeOpenPath("fastload_state", .{}); - defer fastload_dir.close(); - - var fastload_timer = try sig.time.Timer.start(); - try fastload_accounts_db.fastload(fastload_dir, collapsed_manifest.accounts_db_fields); - break :blk fastload_timer.read(); - }; + // const fastload_duration = blk: { + // var fastload_accounts_db = try AccountsDB.init(.{ + // .allocator = allocator, + // .logger = .from(logger), + // .snapshot_dir = snapshot_dir, + // .geyser_writer = null, + // .gossip_view = null, + // .index_allocation = if (bench_args.use_disk) .disk else .ram, + // .number_of_index_shards = 32, + // }); + // defer fastload_accounts_db.deinit(); + + // var fastload_dir = try snapshot_dir.makeOpenPath("fastload_state", .{}); + // defer fastload_dir.close(); + + // var fastload_timer = try sig.time.Timer.start(); + // try fastload_accounts_db.fastload(fastload_dir, collapsed_manifest.accounts_db_fields); + // break :blk fastload_timer.read(); + // }; return .{ .load_time = units.convertDuration(loading_duration), .validate_time = units.convertDuration(validate_duration), - .fastload_save_time = units.convertDuration(fastload_save_duration), - .fastload_time = units.convertDuration(fastload_duration), }; } }; diff --git a/src/accountsdb/index.zig b/src/accountsdb/index.zig index 3fc9553eb5..a2fd475e16 100644 --- a/src/accountsdb/index.zig +++ b/src/accountsdb/index.zig @@ -9,7 +9,6 @@ const RwMux = sig.sync.RwMux; const Slot = sig.core.time.Slot; const FileId = sig.accounts_db.accounts_file.FileId; -const SwissMap = sig.accounts_db.swiss_map.SwissMap; const createAndMmapFile = sig.utils.allocators.createAndMmapFile; @@ -285,7 +284,7 @@ pub const AccountIndex = struct { if (shard_map.capacity() < shard_map.count() + 1) { // caller is generally expected to ensure capacity first self.logger.info().log("index: shard growing unexpectedly"); - try shard_map.ensureTotalCapacity(shard_map.capacity() + 1); + try shard_map.ensureTotalCapacity(self.allocator, shard_map.capacity() + 1); } const map_entry = shard_map.getOrPutAssumeCapacity(account_ref.pubkey); @@ -373,203 +372,201 @@ pub const AccountIndex = struct { switch (head_ref.getParentRefOf(slot)) { .null => return error.SlotNotFound, .head => head_ref.ref_ptr = head_ref.ref_ptr.next_ptr orelse { - _ = pubkey_ref_map.remove(pubkey.*) catch |err| return switch (err) { - error.KeyNotFound => error.PubkeyNotFound, - }; + if (!pubkey_ref_map.remove(pubkey.*)) return error.PubkeyNotFound; return; }, .parent => |parent| parent.next_ptr = if (parent.next_ptr) |ref| ref.next_ptr else null, } } - pub fn loadFromDisk(self: *Self, dir: std.fs.Dir) !void { - const zone = tracy.Zone.init(@src(), .{ .name = "accountsdb loadFromDisk" }); - defer zone.deinit(); - - // manager must be empty - std.debug.assert(self.reference_manager.capacity == 0); - - self.logger.info().log("running account_index.loadFromDisk"); - const reference_file = try dir.openFile("index.bin", .{}); - const size = (try reference_file.stat()).size; - const index_memory = try std.posix.mmap( - null, - size, - std.posix.PROT.READ, - std.posix.MAP{ .TYPE = .PRIVATE }, - reference_file.handle, - 0, - ); - defer std.posix.munmap(index_memory); - - var offset: u64 = 0; - const ref_map_size = - std.mem.readInt(u64, index_memory[offset..][0..@sizeOf(u64)], .little); - offset += @sizeOf(u64); - const reference_size = - std.mem.readInt(u64, index_memory[offset..][0..@sizeOf(u64)], .little); - offset += @sizeOf(u64); - const records_size = - std.mem.readInt(u64, index_memory[offset..][0..@sizeOf(u64)], .little); - offset += @sizeOf(u64); - - // [shard0 length, shard0 data, shard1 length, shard1 data, ...] - const map_memory = index_memory[offset..][0..ref_map_size]; - offset += ref_map_size; - // [AccountRef, AccountRef, ...] - const reference_memory = index_memory[offset..][0..reference_size]; - offset += reference_size; - // [Bincode(Record), Bincode(Record), ...] - const records_memory = index_memory[offset..][0..records_size]; - offset += records_size; - - // load the []AccountRef - self.logger.info().log("loading account references"); - const references = try sig.bincode.readFromSlice( - // NOTE: this still ensure reference memory is either on disk or ram - // even though the state we are loading from is on disk. - // with bincode this will only be one alloc! - self.reference_manager.memory_allocator, - []AccountRef, - reference_memory, - .{}, - ); - try self.reference_manager.memory.append( - self.reference_manager.memory_allocator, - references, - ); - self.reference_manager.capacity += references.len; - - // update the pointers of the references - self.logger.info().log("organizing manager memory"); - for (references) |*ref| { - const next_index = ref.next_index orelse continue; - - ref.next_ptr = &references[next_index]; - references[next_index].prev_ptr = ref; - } - - // load the records - self.logger.info().log("loading manager records"); - const records = try sig.bincode.readFromSlice( - self.reference_manager.records_allocator, - @TypeOf(self.reference_manager.records), - records_memory, - .{}, - ); - for (records.items) |*record| { - record.buf = references[record.global_index..][0..record.len]; - // NOTE: since we flattened the references memory list, all - // records have the same memory_index - record.memory_index = 0; - } - self.reference_manager.records = records; - - // load the pubkey_ref_map - self.logger.info().log("loading pubkey -> ref map"); - offset = 0; - for (self.pubkey_ref_map.shards) |*shard_rw| { - const shard, var lock = shard_rw.writeWithLock(); - defer lock.unlock(); - - const shard_len = std.mem.readInt(u64, map_memory[offset..][0..@sizeOf(u64)], .little); - offset += @sizeOf(u64); - if (shard_len == 0) continue; - - const memory = try self.allocator.alloc(u8, shard_len); - @memcpy(memory, map_memory[offset..][0..shard_len]); - offset += shard_len; - - // update the shard - shard.deinit(); // NOTE: this shouldnt do anything but we keep for correctness - shard.* = ShardedPubkeyRefMap.PubkeyRefMap.initFromMemory( - shard.allocator, - memory, - ); - - // update the pointers of the reference_heads - var shard_iter = shard.iterator(); - while (shard_iter.next()) |entry| { - entry.value_ptr.ref_ptr = &references[entry.value_ptr.ref_index]; - } - } - } - - pub fn saveToDisk(self: *Self, dir: std.fs.Dir) !void { - self.logger.info().log("saving state to disk..."); - self.logger.info().log("saving pubkey -> reference map"); - // write the pubkey_ref_map (populating this is very expensive) - var shard_data_total: u64 = 0; - for (self.pubkey_ref_map.shards) |*shard_rw| { - const shard, var lock = shard_rw.readWithLock(); - defer lock.unlock(); - shard_data_total += shard.unmanaged.memory.len; - } - const n_shards = self.pubkey_ref_map.numberOfShards(); - - // [shard0 length, shard0 data, shard1 length, shard1 data, ...] - const ref_map_size = @sizeOf(u64) * n_shards + shard_data_total; - // [AccountRef, AccountRef, ...] - const reference_size = @sizeOf(u64) + self.reference_manager.capacity * @sizeOf(AccountRef); - // [Bincode(Record), Bincode(Record), ...] - const records_size = sig.bincode.sizeOf(self.reference_manager.records.items, .{}); - const index_memory = try createAndMmapFile( - dir, - "index.bin", - ref_map_size + reference_size + records_size + 3 * @sizeOf(u64), - ); - defer std.posix.munmap(index_memory); - - var offset: u64 = 0; - std.mem.writeInt(u64, index_memory[offset..][0..@sizeOf(u64)], ref_map_size, .little); - offset += @sizeOf(u64); - std.mem.writeInt(u64, index_memory[offset..][0..@sizeOf(u64)], reference_size, .little); - offset += @sizeOf(u64); - std.mem.writeInt(u64, index_memory[offset..][0..@sizeOf(u64)], records_size, .little); - offset += @sizeOf(u64); - - const map_memory = index_memory[offset..][0..ref_map_size]; - offset += ref_map_size; - const reference_memory = index_memory[offset..][0..reference_size]; - offset += reference_size; - const records_memory = index_memory[offset..][0..records_size]; - offset += records_size; - - // write each shard's data - self.logger.info().log("saving pubkey_ref_map memory"); - offset = 0; - for (self.pubkey_ref_map.shards) |*shard_rw| { - const shard, var lock = shard_rw.readWithLock(); - defer lock.unlock(); - - const shard_memory = shard.unmanaged.memory; - std.mem.writeInt(u64, map_memory[offset..][0..@sizeOf(u64)], shard_memory.len, .little); - offset += @sizeOf(u64); - - @memcpy(map_memory[offset..][0..shard_memory.len], shard_memory); - offset += shard_memory.len; - } - - self.logger.info().log("saving account references"); - offset = 0; - // collapse [][]AccountRef into a single slice - std.mem.writeInt( - u64, - reference_memory[offset..][0..@sizeOf(u64)], - self.reference_manager.capacity, - .little, - ); - offset += @sizeOf(u64); - for (self.reference_manager.memory.items) |ref_block| { - for (ref_block) |ref| { - const n = try sig.bincode.writeToSlice(reference_memory[offset..], ref, .{}); - offset += n.len; - } - } - - self.logger.info().log("saving reference_manager records"); - _ = try sig.bincode.writeToSlice(records_memory, self.reference_manager.records.items, .{}); - } + // pub fn loadFromDisk(self: *Self, dir: std.fs.Dir) !void { + // const zone = tracy.Zone.init(@src(), .{ .name = "accountsdb loadFromDisk" }); + // defer zone.deinit(); + + // // manager must be empty + // std.debug.assert(self.reference_manager.capacity == 0); + + // self.logger.info().log("running account_index.loadFromDisk"); + // const reference_file = try dir.openFile("index.bin", .{}); + // const size = (try reference_file.stat()).size; + // const index_memory = try std.posix.mmap( + // null, + // size, + // std.posix.PROT.READ, + // std.posix.MAP{ .TYPE = .PRIVATE }, + // reference_file.handle, + // 0, + // ); + // defer std.posix.munmap(index_memory); + + // var offset: u64 = 0; + // const ref_map_size = + // std.mem.readInt(u64, index_memory[offset..][0..@sizeOf(u64)], .little); + // offset += @sizeOf(u64); + // const reference_size = + // std.mem.readInt(u64, index_memory[offset..][0..@sizeOf(u64)], .little); + // offset += @sizeOf(u64); + // const records_size = + // std.mem.readInt(u64, index_memory[offset..][0..@sizeOf(u64)], .little); + // offset += @sizeOf(u64); + + // // [shard0 length, shard0 data, shard1 length, shard1 data, ...] + // const map_memory = index_memory[offset..][0..ref_map_size]; + // offset += ref_map_size; + // // [AccountRef, AccountRef, ...] + // const reference_memory = index_memory[offset..][0..reference_size]; + // offset += reference_size; + // // [Bincode(Record), Bincode(Record), ...] + // const records_memory = index_memory[offset..][0..records_size]; + // offset += records_size; + + // // load the []AccountRef + // self.logger.info().log("loading account references"); + // const references = try sig.bincode.readFromSlice( + // // NOTE: this still ensure reference memory is either on disk or ram + // // even though the state we are loading from is on disk. + // // with bincode this will only be one alloc! + // self.reference_manager.memory_allocator, + // []AccountRef, + // reference_memory, + // .{}, + // ); + // try self.reference_manager.memory.append( + // self.reference_manager.memory_allocator, + // references, + // ); + // self.reference_manager.capacity += references.len; + + // // update the pointers of the references + // self.logger.info().log("organizing manager memory"); + // for (references) |*ref| { + // const next_index = ref.next_index orelse continue; + + // ref.next_ptr = &references[next_index]; + // references[next_index].prev_ptr = ref; + // } + + // // load the records + // self.logger.info().log("loading manager records"); + // const records = try sig.bincode.readFromSlice( + // self.reference_manager.records_allocator, + // @TypeOf(self.reference_manager.records), + // records_memory, + // .{}, + // ); + // for (records.items) |*record| { + // record.buf = references[record.global_index..][0..record.len]; + // // NOTE: since we flattened the references memory list, all + // // records have the same memory_index + // record.memory_index = 0; + // } + // self.reference_manager.records = records; + + // // load the pubkey_ref_map + // self.logger.info().log("loading pubkey -> ref map"); + // offset = 0; + // for (self.pubkey_ref_map.shards) |*shard_rw| { + // const shard, var lock = shard_rw.writeWithLock(); + // defer lock.unlock(); + + // const shard_len = std.mem.readInt(u64, map_memory[offset..][0..@sizeOf(u64)], .little); + // offset += @sizeOf(u64); + // if (shard_len == 0) continue; + + // const memory = try self.allocator.alloc(u8, shard_len); + // @memcpy(memory, map_memory[offset..][0..shard_len]); + // offset += shard_len; + + // // update the shard + // shard.deinit(self.allocator); // NOTE: this shouldnt do anything but we keep for correctness + // shard.* = ShardedPubkeyRefMap.PubkeyRefMap.initFromMemory( + // shard.allocator, + // memory, + // ); + + // // update the pointers of the reference_heads + // var shard_iter = shard.iterator(); + // while (shard_iter.next()) |entry| { + // entry.value_ptr.ref_ptr = &references[entry.value_ptr.ref_index]; + // } + // } + // } + + // pub fn saveToDisk(self: *Self, dir: std.fs.Dir) !void { + // self.logger.info().log("saving state to disk..."); + // self.logger.info().log("saving pubkey -> reference map"); + // // write the pubkey_ref_map (populating this is very expensive) + // var shard_data_total: u64 = 0; + // for (self.pubkey_ref_map.shards) |*shard_rw| { + // const shard, var lock = shard_rw.readWithLock(); + // defer lock.unlock(); + // shard_data_total += shard.unmanaged.memory.len; + // } + // const n_shards = self.pubkey_ref_map.numberOfShards(); + + // // [shard0 length, shard0 data, shard1 length, shard1 data, ...] + // const ref_map_size = @sizeOf(u64) * n_shards + shard_data_total; + // // [AccountRef, AccountRef, ...] + // const reference_size = @sizeOf(u64) + self.reference_manager.capacity * @sizeOf(AccountRef); + // // [Bincode(Record), Bincode(Record), ...] + // const records_size = sig.bincode.sizeOf(self.reference_manager.records.items, .{}); + // const index_memory = try createAndMmapFile( + // dir, + // "index.bin", + // ref_map_size + reference_size + records_size + 3 * @sizeOf(u64), + // ); + // defer std.posix.munmap(index_memory); + + // var offset: u64 = 0; + // std.mem.writeInt(u64, index_memory[offset..][0..@sizeOf(u64)], ref_map_size, .little); + // offset += @sizeOf(u64); + // std.mem.writeInt(u64, index_memory[offset..][0..@sizeOf(u64)], reference_size, .little); + // offset += @sizeOf(u64); + // std.mem.writeInt(u64, index_memory[offset..][0..@sizeOf(u64)], records_size, .little); + // offset += @sizeOf(u64); + + // const map_memory = index_memory[offset..][0..ref_map_size]; + // offset += ref_map_size; + // const reference_memory = index_memory[offset..][0..reference_size]; + // offset += reference_size; + // const records_memory = index_memory[offset..][0..records_size]; + // offset += records_size; + + // // write each shard's data + // self.logger.info().log("saving pubkey_ref_map memory"); + // offset = 0; + // for (self.pubkey_ref_map.shards) |*shard_rw| { + // const shard, var lock = shard_rw.readWithLock(); + // defer lock.unlock(); + + // const shard_memory = shard.unmanaged.memory; + // std.mem.writeInt(u64, map_memory[offset..][0..@sizeOf(u64)], shard_memory.len, .little); + // offset += @sizeOf(u64); + + // @memcpy(map_memory[offset..][0..shard_memory.len], shard_memory); + // offset += shard_memory.len; + // } + + // self.logger.info().log("saving account references"); + // offset = 0; + // // collapse [][]AccountRef into a single slice + // std.mem.writeInt( + // u64, + // reference_memory[offset..][0..@sizeOf(u64)], + // self.reference_manager.capacity, + // .little, + // ); + // offset += @sizeOf(u64); + // for (self.reference_manager.memory.items) |ref_block| { + // for (ref_block) |ref| { + // const n = try sig.bincode.writeToSlice(reference_memory[offset..], ref, .{}); + // offset += n.len; + // } + // } + + // self.logger.info().log("saving reference_manager records"); + // _ = try sig.bincode.writeToSlice(records_memory, self.reference_manager.records.items, .{}); + // } }; pub const AccountReferenceHead = struct { @@ -635,13 +632,12 @@ pub const ShardedPubkeyRefMap = struct { // map from pubkey -> account reference head (of linked list) pub const RwPubkeyRefMap = RwMux(PubkeyRefMap); - pub const PubkeyRefMap = SwissMap(Pubkey, AccountReferenceHead, hash, eql); - pub inline fn hash(key: Pubkey) u64 { - return std.mem.readInt(u64, key.data[0..8], .little); - } - pub inline fn eql(key1: Pubkey, key2: Pubkey) bool { - return key1.equals(&key2); - } + pub const PubkeyRefMap = std.HashMapUnmanaged( + Pubkey, + AccountReferenceHead, + Pubkey.HashContext, + std.hash_map.default_max_load_percentage, + ); pub const ShardWriteLock = RwPubkeyRefMap.WLockGuard; pub const ShardReadLock = RwPubkeyRefMap.RLockGuard; @@ -655,7 +651,7 @@ pub const ShardedPubkeyRefMap = struct { // shard the pubkey map into shards to reduce lock contention const shards = try allocator.alloc(RwPubkeyRefMap, number_of_shards); errdefer allocator.free(number_of_shards); - @memset(shards, RwPubkeyRefMap.init(PubkeyRefMap.init(allocator))); + @memset(shards, RwPubkeyRefMap.init(.empty)); const shard_calculator = PubkeyShardCalculator.init(number_of_shards); return .{ @@ -669,7 +665,7 @@ pub const ShardedPubkeyRefMap = struct { for (self.shards) |*shard_rw| { const shard, var shard_lg = shard_rw.writeWithLock(); defer shard_lg.unlock(); - shard.deinit(); + shard.deinit(self.allocator); } self.allocator.free(self.shards); } @@ -682,7 +678,7 @@ pub const ShardedPubkeyRefMap = struct { if (count > 0) { const shard_map, var lock = self.getShardFromIndex(index).writeWithLock(); defer lock.unlock(); - try shard_map.ensureTotalCapacity(count); + try shard_map.ensureTotalCapacity(self.allocator, @intCast(count)); } } } @@ -701,7 +697,10 @@ pub const ShardedPubkeyRefMap = struct { const shard_map, var lock = self.getShardFromIndex(index).writeWithLock(); defer lock.unlock(); // ! - try shard_map.ensureTotalCapacity(count + shard_map.count()); + try shard_map.ensureTotalCapacity( + self.allocator, + @intCast(count + shard_map.count()), + ); } } } @@ -710,7 +709,7 @@ pub const ShardedPubkeyRefMap = struct { for (self.shards) |*shard_rw| { const shard, var shard_lg = shard_rw.writeWithLock(); defer shard_lg.unlock(); - try shard.ensureTotalCapacity(size_per_shard); + try shard.ensureTotalCapacity(self.allocator, size_per_shard); } } @@ -756,7 +755,7 @@ pub const ShardedPubkeyRefMap = struct { return self.getShardFromIndex(self.getShardIndex(pubkey)); } - pub fn getShardCount(self: *const Self, index: u64) u64 { + pub fn getShardCount(self: *const Self, index: u64) u32 { const shard, var lock = self.getShardFromIndex(index).readWithLock(); defer lock.unlock(); return shard.count(); @@ -850,121 +849,121 @@ pub const ReferenceAllocator = union(Tag) { } }; -test "save and load account index state -- multi linked list" { - const allocator = std.testing.allocator; - var save_dir = std.testing.tmpDir(.{}); - defer save_dir.cleanup(); - - var index = try AccountIndex.init( - allocator, - .noop, - .{ .ram = .{ .allocator = allocator } }, - 8, - ); - defer index.deinit(); - - try index.expandRefCapacity(10); - try index.pubkey_ref_map.ensureTotalCapacityPerShard(10); - - const ref_block, _ = try index.reference_manager.alloc(2); - - var ref_a = AccountRef.DEFAULT; - ref_a.slot = 10; - ref_block[0] = ref_a; - // - // pubkey -> a - index.indexRefAssumeCapacity(&ref_block[0], 0); - - var ref_b = AccountRef.DEFAULT; - ref_b.slot = 20; - ref_b.location = .{ .File = .{ .file_id = FileId.fromInt(1), .offset = 20 } }; - ref_block[1] = ref_b; - - // pubkey -> a -> b - index.indexRefAssumeCapacity(&ref_block[1], 1); - - { - const ref_head, var ref_head_lg = index.pubkey_ref_map.getRead(&ref_a.pubkey).?; - defer ref_head_lg.unlock(); - _, const ref_max_slot = ref_head.highestRootedSlot(100); - try std.testing.expectEqual(20, ref_max_slot); - } - - // save the state - try index.saveToDisk(save_dir.dir); - - var index2 = try AccountIndex.init( - allocator, - .noop, - .{ .ram = .{ .allocator = allocator } }, - 8, - ); - defer index2.deinit(); - - // load the state - // NOTE: this will work even if something is wrong - // because were using the same pointers because its the same run - try index2.loadFromDisk(save_dir.dir); - { - const ref_head, var ref_head_lg = index2.pubkey_ref_map.getRead(&ref_a.pubkey).?; - defer ref_head_lg.unlock(); - _, const ref_max_slot = ref_head.highestRootedSlot(100); - try std.testing.expectEqual(20, ref_max_slot); - } -} - -test "save and load account index state" { - const allocator = std.testing.allocator; - var save_dir = std.testing.tmpDir(.{}); - defer save_dir.cleanup(); - - var index = try AccountIndex.init( - allocator, - .noop, - .{ .ram = .{ .allocator = allocator } }, - 8, - ); - defer index.deinit(); - try index.expandRefCapacity(1); - try index.pubkey_ref_map.ensureTotalCapacityPerShard(100); - - const ref_block, _ = try index.reference_manager.alloc(1); - var ref_a = AccountRef.DEFAULT; - ref_a.slot = 10; - ref_block[0] = ref_a; - - // pubkey -> a - index.indexRefAssumeCapacity(&ref_block[0], 0); - - { - const ref_head, var ref_head_lg = index.pubkey_ref_map.getRead(&ref_a.pubkey).?; - defer ref_head_lg.unlock(); - _, const ref_max_slot = ref_head.highestRootedSlot(100); - try std.testing.expectEqual(10, ref_max_slot); - } - - // save the state - try index.saveToDisk(save_dir.dir); - - var index2 = try AccountIndex.init( - allocator, - .noop, - .{ .ram = .{ .allocator = allocator } }, - 8, - ); - defer index2.deinit(); - - // load the state - // NOTE: this will work even if something is wrong - // because were using the same pointers because its the same run - try index2.loadFromDisk(save_dir.dir); - { - const ref_head, var ref_head_lg = index2.pubkey_ref_map.getRead(&ref_a.pubkey).?; - defer ref_head_lg.unlock(); - _, const ref_max_slot = ref_head.highestRootedSlot(10); - try std.testing.expectEqual(10, ref_max_slot); - } -} +// test "save and load account index state -- multi linked list" { +// const allocator = std.testing.allocator; +// var save_dir = std.testing.tmpDir(.{}); +// defer save_dir.cleanup(); + +// var index = try AccountIndex.init( +// allocator, +// .noop, +// .{ .ram = .{ .allocator = allocator } }, +// 8, +// ); +// defer index.deinit(); + +// try index.expandRefCapacity(10); +// try index.pubkey_ref_map.ensureTotalCapacityPerShard(10); + +// const ref_block, _ = try index.reference_manager.alloc(2); + +// var ref_a = AccountRef.DEFAULT; +// ref_a.slot = 10; +// ref_block[0] = ref_a; +// // +// // pubkey -> a +// index.indexRefAssumeCapacity(&ref_block[0], 0); + +// var ref_b = AccountRef.DEFAULT; +// ref_b.slot = 20; +// ref_b.location = .{ .File = .{ .file_id = FileId.fromInt(1), .offset = 20 } }; +// ref_block[1] = ref_b; + +// // pubkey -> a -> b +// index.indexRefAssumeCapacity(&ref_block[1], 1); + +// { +// const ref_head, var ref_head_lg = index.pubkey_ref_map.getRead(&ref_a.pubkey).?; +// defer ref_head_lg.unlock(); +// _, const ref_max_slot = ref_head.highestRootedSlot(100); +// try std.testing.expectEqual(20, ref_max_slot); +// } + +// // save the state +// try index.saveToDisk(save_dir.dir); + +// var index2 = try AccountIndex.init( +// allocator, +// .noop, +// .{ .ram = .{ .allocator = allocator } }, +// 8, +// ); +// defer index2.deinit(); + +// // load the state +// // NOTE: this will work even if something is wrong +// // because were using the same pointers because its the same run +// try index2.loadFromDisk(save_dir.dir); +// { +// const ref_head, var ref_head_lg = index2.pubkey_ref_map.getRead(&ref_a.pubkey).?; +// defer ref_head_lg.unlock(); +// _, const ref_max_slot = ref_head.highestRootedSlot(100); +// try std.testing.expectEqual(20, ref_max_slot); +// } +// } + +// test "save and load account index state" { +// const allocator = std.testing.allocator; +// var save_dir = std.testing.tmpDir(.{}); +// defer save_dir.cleanup(); + +// var index = try AccountIndex.init( +// allocator, +// .noop, +// .{ .ram = .{ .allocator = allocator } }, +// 8, +// ); +// defer index.deinit(); +// try index.expandRefCapacity(1); +// try index.pubkey_ref_map.ensureTotalCapacityPerShard(100); + +// const ref_block, _ = try index.reference_manager.alloc(1); +// var ref_a = AccountRef.DEFAULT; +// ref_a.slot = 10; +// ref_block[0] = ref_a; + +// // pubkey -> a +// index.indexRefAssumeCapacity(&ref_block[0], 0); + +// { +// const ref_head, var ref_head_lg = index.pubkey_ref_map.getRead(&ref_a.pubkey).?; +// defer ref_head_lg.unlock(); +// _, const ref_max_slot = ref_head.highestRootedSlot(100); +// try std.testing.expectEqual(10, ref_max_slot); +// } + +// // save the state +// try index.saveToDisk(save_dir.dir); + +// var index2 = try AccountIndex.init( +// allocator, +// .noop, +// .{ .ram = .{ .allocator = allocator } }, +// 8, +// ); +// defer index2.deinit(); + +// // load the state +// // NOTE: this will work even if something is wrong +// // because were using the same pointers because its the same run +// try index2.loadFromDisk(save_dir.dir); +// { +// const ref_head, var ref_head_lg = index2.pubkey_ref_map.getRead(&ref_a.pubkey).?; +// defer ref_head_lg.unlock(); +// _, const ref_max_slot = ref_head.highestRootedSlot(10); +// try std.testing.expectEqual(10, ref_max_slot); +// } +// } test "account index update/remove reference" { const allocator = std.testing.allocator; diff --git a/src/accountsdb/lib.zig b/src/accountsdb/lib.zig index ceed5732f2..e546f5bae7 100644 --- a/src/accountsdb/lib.zig +++ b/src/accountsdb/lib.zig @@ -8,7 +8,6 @@ pub const fuzz_snapshot = @import("fuzz_snapshot.zig"); pub const index = @import("index.zig"); pub const manager = @import("manager.zig"); pub const snapshots = @import("snapshots.zig"); -pub const swiss_map = @import("swiss_map.zig"); pub const AccountStore = account_store.AccountStore; pub const AccountReader = account_store.AccountReader; diff --git a/src/accountsdb/swiss_map.zig b/src/accountsdb/swiss_map.zig deleted file mode 100644 index 9de66c2055..0000000000 --- a/src/accountsdb/swiss_map.zig +++ /dev/null @@ -1,932 +0,0 @@ -//! custom hashmap used for the index's map -//! based on google's swissmap - -const builtin = @import("builtin"); -const std = @import("std"); -const sig = @import("../sig.zig"); - -const accounts_db = sig.accounts_db; - -const BenchTimeUnit = @import("../benchmarks.zig").BenchTimeUnit; - -pub fn SwissMap( - comptime Key: type, - comptime Value: type, - comptime hash_fn: fn (Key) callconv(.@"inline") u64, - comptime eq_fn: fn (Key, Key) callconv(.@"inline") bool, -) type { - return struct { - allocator: std.mem.Allocator, - unmanaged: Unmanaged, - const Self = @This(); - - pub const Unmanaged = SwissMapUnmanaged(Key, Value, hash_fn, eq_fn); - - pub fn init(allocator: std.mem.Allocator) Self { - return .{ - .allocator = allocator, - .unmanaged = Unmanaged.init(), - }; - } - - pub fn initCapacity(allocator: std.mem.Allocator, n: usize) !Self { - var unmanaged = Unmanaged.init(); - try unmanaged.ensureTotalCapacity(allocator, n); - return .{ - .allocator = allocator, - .unmanaged = unmanaged, - }; - } - - pub fn initFromMemory(allocator: std.mem.Allocator, memory: []u8) Self { - return .{ - .allocator = allocator, - .unmanaged = Unmanaged.initFromMemory(memory), - }; - } - - pub fn ensureTotalCapacity(self: *Self, n: usize) std.mem.Allocator.Error!void { - return @call( - .always_inline, - Unmanaged.ensureTotalCapacity, - .{ &self.unmanaged, self.allocator, n }, - ); - } - - pub fn deinit(self: *Self) void { - return @call(.always_inline, Unmanaged.deinit, .{ &self.unmanaged, self.allocator }); - } - - pub const Iterator = Unmanaged.Iterator; - - pub inline fn iterator(self: *const @This()) Iterator { - return self.unmanaged.iterator(); - } - - pub inline fn count(self: *const @This()) usize { - return self.unmanaged.count(); - } - - pub inline fn capacity(self: *const @This()) usize { - return self.unmanaged.capacity(); - } - - pub const GetOrPutResult = Unmanaged.GetOrPutResult; - - pub fn remove(self: *@This(), key: Key) error{KeyNotFound}!Value { - return @call(.always_inline, Unmanaged.remove, .{ &self.unmanaged, key }); - } - - pub fn get(self: *const @This(), key: Key) ?Value { - return @call(.always_inline, Unmanaged.get, .{ &self.unmanaged, key }); - } - - pub fn getPtr(self: *const @This(), key: Key) ?*Value { - return @call(.always_inline, Unmanaged.getPtr, .{ &self.unmanaged, key }); - } - - /// puts a key into the index with the value - /// note: this assumes the key is not already in the index, if it is, then - /// the map might contain two keys, and the behavior is undefined - pub fn putAssumeCapacity(self: *Self, key: Key, value: Value) void { - return @call( - .always_inline, - Unmanaged.putAssumeCapacity, - .{ &self.unmanaged, key, value }, - ); - } - - pub fn getOrPutAssumeCapacity(self: *Self, key: Key) GetOrPutResult { - return @call( - .always_inline, - Unmanaged.getOrPutAssumeCapacity, - .{ &self.unmanaged, key }, - ); - } - }; -} - -pub fn SwissMapUnmanaged( - comptime Key: type, - comptime Value: type, - comptime hash_fn: fn (Key) callconv(.@"inline") u64, - comptime eq_fn: fn (Key, Key) callconv(.@"inline") bool, -) type { - return struct { - groups: [][GROUP_SIZE]KeyValue, - states: []@Vector(GROUP_SIZE, u8), - bit_mask: usize, - // underlying memory - memory: []u8, - _count: usize = 0, - _capacity: usize = 0, - const Self = @This(); - - const GROUP_SIZE = 16; - - pub const State = packed struct(u8) { - state: enum(u1) { empty_or_deleted, occupied }, - control_bytes: u7, - }; - - // specific state/control_bytes values - pub const EMPTY_STATE = State{ - .state = .empty_or_deleted, - .control_bytes = 0b0000000, - }; - pub const DELETED_STATE = State{ - .state = .empty_or_deleted, - .control_bytes = 0b1111111, - }; - pub const OCCUPIED_STATE = State{ - .state = .occupied, - .control_bytes = 0, - }; - - const EMPTY_STATE_VEC: @Vector(GROUP_SIZE, u8) = @splat(@bitCast(EMPTY_STATE)); - const DELETED_STATE_VEC: @Vector(GROUP_SIZE, u8) = @splat(@bitCast(DELETED_STATE)); - const OCCUPIED_STATE_VEC: @Vector(GROUP_SIZE, u8) = @splat(@bitCast(OCCUPIED_STATE)); - - pub const KeyValue = struct { - key: Key, - value: Value, - }; - - pub const KeyValuePtr = struct { - key_ptr: *Key, - value_ptr: *Value, - }; - - pub fn init() Self { - return Self{ - .groups = undefined, - .states = undefined, - .memory = undefined, - .bit_mask = 0, - }; - } - - pub fn initCapacity(allocator: std.mem.Allocator, n: usize) std.mem.Allocator.Error!Self { - var self = init(allocator); - try self.ensureTotalCapacity(allocator, n); - return self; - } - - pub fn initFromMemory(memory: []u8) Self { - var self = init(); - - // from ensureTotalCapacity: - // memory.len === n_groups * (@sizeOf([GROUP_SIZE]KeyValue) + @sizeOf([GROUP_SIZE]State)) - const n_groups = - memory.len / (@sizeOf([GROUP_SIZE]KeyValue) + @sizeOf([GROUP_SIZE]State)); - - const group_size = n_groups * @sizeOf([GROUP_SIZE]KeyValue); - const group_ptr: [*][GROUP_SIZE]KeyValue = @alignCast(@ptrCast(memory.ptr)); - const groups = group_ptr[0..n_groups]; - const states_ptr: [*]@Vector(GROUP_SIZE, u8) = - @alignCast(@ptrCast(memory.ptr + group_size)); - const states = states_ptr[0..n_groups]; - - self._capacity = n_groups * GROUP_SIZE; - self.groups = groups; - self.states = states; - self.memory = memory; - self.bit_mask = n_groups - 1; - - self._count = 0; - for (0..self.groups.len) |i| { - const state_vec = self.states[i]; - for (0..GROUP_SIZE) |j| { - const state: State = @bitCast(state_vec[j]); - if (state.state == .occupied) { - self._count += 1; - } - } - } - - return self; - } - - pub fn ensureTotalCapacity( - self: *Self, - allocator: std.mem.Allocator, - n: usize, - ) std.mem.Allocator.Error!void { - if (n <= self._capacity) { - return; - } - - if (self._capacity == 0) { - const n_groups = @max(std.math.pow(u64, 2, std.math.log2(n) + 1) / GROUP_SIZE, 1); - const group_size = n_groups * @sizeOf([GROUP_SIZE]KeyValue); - const ctrl_size = n_groups * @sizeOf([GROUP_SIZE]State); - const size = group_size + ctrl_size; - - const memory = try allocator.alloc(u8, size); - errdefer comptime unreachable; - @memset(memory, 0); - - const group_ptr: [*][GROUP_SIZE]KeyValue = @alignCast(@ptrCast(memory.ptr)); - const groups = group_ptr[0..n_groups]; - const states_ptr: [*]@Vector(GROUP_SIZE, u8) = - @alignCast(@ptrCast(memory.ptr + group_size)); - const states = states_ptr[0..n_groups]; - - self._capacity = n_groups * GROUP_SIZE; - std.debug.assert(self._capacity >= n); - self.groups = groups; - self.states = states; - self.memory = memory; - self.bit_mask = n_groups - 1; - } else { - // recompute the size - const n_groups = @max(std.math.pow(u64, 2, std.math.log2(n) + 1) / GROUP_SIZE, 1); - - const group_size = n_groups * @sizeOf([GROUP_SIZE]KeyValue); - const ctrl_size = n_groups * @sizeOf([GROUP_SIZE]State); - const size = group_size + ctrl_size; - - const memory = try allocator.alloc(u8, size); - errdefer comptime unreachable; - @memset(memory, 0); - - const group_ptr: [*][GROUP_SIZE]KeyValue = @alignCast(@ptrCast(memory.ptr)); - const groups = group_ptr[0..n_groups]; - const states_ptr: [*]@Vector(GROUP_SIZE, u8) = - @alignCast(@ptrCast(memory.ptr + group_size)); - const states = states_ptr[0..n_groups]; - - var new_self = Self{ - .groups = groups, - .states = states, - .memory = memory, - .bit_mask = n_groups - 1, - ._capacity = n_groups * GROUP_SIZE, - }; - - var iter = self.iterator(); - while (iter.next()) |kv| { - new_self.putAssumeCapacity(kv.key_ptr.*, kv.value_ptr.*); - } - - self.deinit(allocator); // release old memory - - self._capacity = new_self._capacity; - self.groups = new_self.groups; - self.states = new_self.states; - self.memory = new_self.memory; - self.bit_mask = new_self.bit_mask; - } - } - - pub fn deinit(self: *Self, allocator: std.mem.Allocator) void { - if (self._capacity > 0) { - allocator.free(self.memory); - } - } - - pub const Iterator = struct { - hm: *const Self, - group_index: usize = 0, - position: usize = 0, - - pub fn next(it: *Iterator) ?KeyValuePtr { - const self = it.hm; - - if (self.capacity() == 0) return null; - - while (true) { - if (it.group_index == self.groups.len) { - return null; - } - - const state_vec = self.states[it.group_index]; - - const occupied_states = state_vec & OCCUPIED_STATE_VEC; - if (reduceOrWorkaround(occupied_states) != 0) { - for (it.position..GROUP_SIZE) |j| { - defer it.position += 1; - if (occupied_states[j] != 0) { - return .{ - .key_ptr = &self.groups[it.group_index][j].key, - .value_ptr = &self.groups[it.group_index][j].value, - }; - } - } - } - it.position = 0; - it.group_index += 1; - } - } - }; - - pub inline fn iterator(self: *const @This()) Iterator { - return .{ .hm = self }; - } - - pub inline fn count(self: *const @This()) usize { - return self._count; - } - - pub inline fn capacity(self: *const @This()) usize { - return self._capacity; - } - - pub const GetOrPutResult = struct { - found_existing: bool, - value_ptr: *Value, - }; - - pub fn remove( - self: *@This(), - key: Key, - ) error{KeyNotFound}!Value { - if (self._capacity == 0) return error.KeyNotFound; - const hash = hash_fn(key); - var group_index = hash & self.bit_mask; - - const control_bytes: u7 = @intCast(hash >> (64 - 7)); - const key_state = State{ - .state = .occupied, - .control_bytes = control_bytes, - }; - const key_vec: @Vector(GROUP_SIZE, u8) = @splat(@bitCast(key_state)); - - for (0..self.groups.len) |_| { - const state_vec = self.states[group_index]; - - const match_vec = key_vec == state_vec; - if (reduceOrWorkaround(match_vec)) { - inline for (0..GROUP_SIZE) |j| { - // remove here - if (match_vec[j] and eq_fn(self.groups[group_index][j].key, key)) { - const result = self.groups[group_index][j].value; - - // search works by searching each group starting from group_index until an empty state is found - // because if theres an empty state, the key DNE - // - // if theres an empty state in this group already, then the search would early exit anyway, - // so we can change this state to 'empty' as well. - // - // if theres no empty state in this group, then there could be additional keys in a higher group, - // which if we changed this state to empty would cause the search to early exit, - // so we need to change this state to 'deleted'. - // - const new_state = if (reduceOrWorkaround(EMPTY_STATE_VEC == state_vec)) - EMPTY_STATE - else - DELETED_STATE; - self.states[group_index][j] = @bitCast(new_state); - self._count -= 1; - return result; - } - } - } - - // if theres a free state, then the key DNE - const is_empty_vec = EMPTY_STATE_VEC == state_vec; - if (reduceOrWorkaround(is_empty_vec)) { - return error.KeyNotFound; - } - - // otherwise try the next group - group_index = (group_index + 1) & self.bit_mask; - } - - return error.KeyNotFound; - } - - pub fn get(self: *const @This(), key: Key) ?Value { - if (self.getPtr(key)) |ptr| { - return ptr.*; - } else { - return null; - } - } - - pub fn getPtr(self: *const @This(), key: Key) ?*Value { - if (self._capacity == 0) return null; - - const hash = hash_fn(key); - var group_index = hash & self.bit_mask; - - // what we are searching for (get) - const control_bytes: u7 = @intCast(hash >> (64 - 7)); - // PERF: this struct is represented by a u8 - const key_state = State{ - .state = .occupied, - .control_bytes = control_bytes, - }; - const key_vec: @Vector(GROUP_SIZE, u8) = @splat(@bitCast(key_state)); - - for (0..self.groups.len) |_| { - const state_vec = self.states[group_index]; - - // PERF: SIMD eq check: search for a match - const match_vec = key_vec == state_vec; - if (reduceOrWorkaround(match_vec)) { - inline for (0..GROUP_SIZE) |j| { - // PERF: SIMD eq check across pubkeys - if (match_vec[j] and eq_fn(self.groups[group_index][j].key, key)) { - return &self.groups[group_index][j].value; - } - } - } - - // PERF: SIMD eq check: if theres a free state, then the key DNE - const is_empty_vec = EMPTY_STATE_VEC == state_vec; - if (reduceOrWorkaround(is_empty_vec)) { - return null; - } - - // otherwise try the next group - group_index = (group_index + 1) & self.bit_mask; - } - return null; - } - - /// puts a key into the index with the value - /// note: this assumes the key is not already in the index, if it is, then - /// the map might contain two keys, and the behavior is undefined - pub fn putAssumeCapacity(self: *Self, key: Key, value: Value) void { - const hash = hash_fn(key); - var group_index = hash & self.bit_mask; - std.debug.assert(self._capacity > self._count); - - // what we are searching for (get) - const control_bytes: u7 = @intCast(hash >> (64 - 7)); - const key_state = State{ - .state = .occupied, - .control_bytes = control_bytes, - }; - - for (0..self.groups.len) |_| { - const state_vec = self.states[group_index]; - - // if theres an free then insert - // note: if theres atleast on empty state, then there wont be any deleted states - // due to how remove works, so we dont need to prioritize deleted over empty - const is_free_vec = ~state_vec & OCCUPIED_STATE_VEC; - if (reduceOrWorkaround(is_free_vec) != 0) { - _ = self.fill( - key, - value, - key_state, - group_index, - is_free_vec == @as(@Vector(GROUP_SIZE, u8), @splat(1)), - ); - return; - } - - // otherwise try the next group - group_index = (group_index + 1) & self.bit_mask; - } - unreachable; - } - - /// fills a group with a key value and increments count - /// where the fill index requires is_free_vec[index] == true - fn fill( - self: *Self, - key: Key, - value: Value, - key_state: State, - group_index: usize, - is_free_vec: @Vector(GROUP_SIZE, bool), - ) usize { - const invalid_state: @Vector(GROUP_SIZE, u8) = @splat(GROUP_SIZE); - const indices = selectWorkaround( - is_free_vec, - std.simd.iota(u8, GROUP_SIZE), - invalid_state, - ); - const index = reduceMinWorkaround(indices); - - self.groups[group_index][index] = .{ - .key = key, - .value = value, - }; - self.states[group_index][index] = @bitCast(key_state); - self._count += 1; - - return index; - } - - pub fn getOrPutAssumeCapacity(self: *Self, key: Key) GetOrPutResult { - const hash = hash_fn(key); - var group_index = hash & self.bit_mask; - - std.debug.assert(self._capacity > self._count); - - // what we are searching for (get) - const control_bytes: u7 = @intCast(hash >> (64 - 7)); - const key_state = State{ - .state = .occupied, - .control_bytes = control_bytes, - }; - const key_vec: @Vector(GROUP_SIZE, u8) = @splat(@bitCast(key_state)); - - for (0..self.groups.len) |_| { - const state_vec = self.states[group_index]; - - // SIMD eq search for a match (get) - const match_vec = key_vec == state_vec; - if (reduceOrWorkaround(match_vec)) { - inline for (0..GROUP_SIZE) |j| { - if (match_vec[j] and eq_fn(self.groups[group_index][j].key, key)) { - return .{ - .found_existing = true, - .value_ptr = &self.groups[group_index][j].value, - }; - } - } - } - - // note: we cant insert into deleted states because - // the value of the `get` part of this function - and - // because the key might exist in another group - const is_empty_vec = EMPTY_STATE_VEC == state_vec; - if (reduceOrWorkaround(is_empty_vec)) { - const index = self.fill( - key, - undefined, - key_state, - group_index, - is_empty_vec, - ); - return .{ - .found_existing = false, - .value_ptr = &self.groups[group_index][index].value, - }; - } - - // otherwise try the next group - group_index = (group_index + 1) & self.bit_mask; - } - unreachable; - } - }; -} - -// helper functions for using the experimental x86_64 self-hosted backend -// it doesn't implement a couple of builtin functions, so here they're -// manually reimplemented. NOTE: has no effect on LLVM based builds. - -fn reduceOrWorkaround(v: anytype) std.meta.Child(@TypeOf(v)) { - if (builtin.zig_backend != .stage2_x86_64) return @reduce(.Or, v); - - const Child = std.meta.Child(@TypeOf(v)); - if (Child == bool) { - var acc: bool = false; - for (0..16) |i| acc = acc or v[i]; - return acc; - } - - var acc: u8 = 0; - for (0..16) |i| acc |= v[i]; - return acc; -} - -fn reduceMinWorkaround(v: @Vector(16, u8)) u8 { - if (builtin.zig_backend != .stage2_x86_64) return @reduce(.Min, v); - - var min: u8 = v[0]; - for (1..16) |i| { - min = @min(min, v[i]); - } - return min; -} - -fn selectWorkaround( - pred: @Vector(16, bool), - a: @Vector(16, u8), - b: @Vector(16, u8), -) @Vector(16, u8) { - if (builtin.zig_backend != .stage2_x86_64) return @select(u8, pred, a, b); - - var output: @Vector(16, u8) = undefined; - for (0..16) |i| { - output[i] = if (pred[i]) a[i] else b[i]; - } - return output; -} - -test "swissmap load from memory" { - const MapT = SwissMap( - sig.core.Pubkey, - accounts_db.index.AccountRef, - accounts_db.index.ShardedPubkeyRefMap.hash, - accounts_db.index.ShardedPubkeyRefMap.eql, - ); - var map = MapT.init(std.testing.allocator); - defer map.deinit(); - - try map.ensureTotalCapacity(100); - - const ref = accounts_db.index.AccountRef.DEFAULT; - map.putAssumeCapacity(sig.core.Pubkey.ZEROES, ref); - - var map2 = MapT.initFromMemory(std.testing.allocator, map.unmanaged.memory); - - const get_ref = map2.get(sig.core.Pubkey.ZEROES) orelse return error.MissingAccount; - try std.testing.expectEqual(ref, get_ref); -} - -test "swissmap resize" { - var map = SwissMap( - sig.core.Pubkey, - accounts_db.index.AccountRef, - accounts_db.index.ShardedPubkeyRefMap.hash, - accounts_db.index.ShardedPubkeyRefMap.eql, - ).init(std.testing.allocator); - defer map.deinit(); - - try map.ensureTotalCapacity(100); - - const ref = accounts_db.index.AccountRef.DEFAULT; - map.putAssumeCapacity(sig.core.Pubkey.ZEROES, ref); - - // this will resize the map with the key still in there - try map.ensureTotalCapacity(200); - const get_ref = map.get(sig.core.Pubkey.ZEROES) orelse return error.MissingAccount; - try std.testing.expect(std.meta.eql(get_ref, ref)); -} - -test "swissmap read/write/delete" { - const allocator = std.testing.allocator; - - const n_accounts = 10_000; - const account_refs, const pubkeys = try generateData(allocator, n_accounts); - defer { - allocator.free(account_refs); - allocator.free(pubkeys); - } - - var map = try SwissMap( - sig.core.Pubkey, - *accounts_db.index.AccountRef, - accounts_db.index.ShardedPubkeyRefMap.hash, - accounts_db.index.ShardedPubkeyRefMap.eql, - ).initCapacity(allocator, n_accounts); - defer map.deinit(); - - // write all - for (0..account_refs.len) |i| { - const result = map.getOrPutAssumeCapacity(account_refs[i].pubkey); - try std.testing.expect(!result.found_existing); // shouldnt be found - result.value_ptr.* = &account_refs[i]; - } - - // read all - slots should be the same - for (0..account_refs.len) |i| { - const result = map.getOrPutAssumeCapacity(pubkeys[i]); - try std.testing.expect(result.found_existing); // should be found - try std.testing.expectEqual(result.value_ptr.*.slot, account_refs[i].slot); - } - - // remove half - for (0..account_refs.len / 2) |i| { - _ = try map.remove(pubkeys[i]); - } - - // read removed half - for (0..account_refs.len / 2) |i| { - const result = map.get(pubkeys[i]); - try std.testing.expect(result == null); - } - - // read remaining half - for (account_refs.len / 2..account_refs.len) |i| { - const result = map.get(pubkeys[i]); - try std.testing.expect(result != null); - try std.testing.expectEqual(result.?.slot, account_refs[i].slot); - } -} - -test "swissmap read/write" { - const allocator = std.testing.allocator; - - const n_accounts = 10_000; - const account_refs, const pubkeys = try generateData(allocator, n_accounts); - defer { - allocator.free(account_refs); - allocator.free(pubkeys); - } - - var map = try SwissMap( - sig.core.Pubkey, - *accounts_db.index.AccountRef, - accounts_db.index.ShardedPubkeyRefMap.hash, - accounts_db.index.ShardedPubkeyRefMap.eql, - ).initCapacity(allocator, n_accounts); - defer map.deinit(); - - // write all - for (0..account_refs.len) |i| { - const result = map.getOrPutAssumeCapacity(account_refs[i].pubkey); - try std.testing.expect(!result.found_existing); // shouldnt be found - result.value_ptr.* = &account_refs[i]; - } - - // read all - slots should be the same - for (0..account_refs.len) |i| { - const result = map.getOrPutAssumeCapacity(pubkeys[i]); - try std.testing.expect(result.found_existing); // should be found - try std.testing.expectEqual(result.value_ptr.*.slot, account_refs[i].slot); - } -} - -fn generateData(allocator: std.mem.Allocator, n_accounts: usize) !struct { - []accounts_db.index.AccountRef, - []sig.core.Pubkey, -} { - var prng = std.Random.DefaultPrng.init(0); - const random = prng.random(); - - const account_refs = try allocator.alloc(accounts_db.index.AccountRef, n_accounts); - const pubkeys = try allocator.alloc(sig.core.Pubkey, n_accounts); - for (account_refs, pubkeys) |*account_ref, *pubkey| { - account_ref.* = accounts_db.index.AccountRef.DEFAULT; - random.bytes(&account_ref.pubkey.data); - pubkey.* = account_ref.pubkey; - } - random.shuffle(sig.core.Pubkey, pubkeys); - - return .{ account_refs, pubkeys }; -} - -pub const BenchmarkSwissMap = struct { - pub const min_iterations = 1; - pub const max_iterations = 1_000; - - pub const BenchArgs = struct { - n_accounts: usize, - name: []const u8 = "", - }; - - pub const args = [_]BenchArgs{ - BenchArgs{ - .n_accounts = 1_000_000, - .name = "1m accounts", - }, - }; - - pub fn swissmapReadWriteBenchmark(units: BenchTimeUnit, bench_args: BenchArgs) !struct { - read_time: u64, - write_time: u64, - // // NOTE: these are useful for debugging, but not for CI/CD - // read_speedup_vs_std: f32, - // write_speedup_vs_std: f32, - } { - const allocator = if (builtin.is_test) std.testing.allocator else std.heap.c_allocator; - const n_accounts = bench_args.n_accounts; - - const account_refs, const pubkeys = try generateData(allocator, n_accounts); - defer { - allocator.free(account_refs); - allocator.free(pubkeys); - } - - const write_time, const read_time = try benchGetOrPut( - SwissMap( - sig.core.Pubkey, - *accounts_db.index.AccountRef, - accounts_db.index.ShardedPubkeyRefMap.hash, - accounts_db.index.ShardedPubkeyRefMap.eql, - ), - allocator, - account_refs, - pubkeys, - null, - ); - - // // NOTE : can uncomment this code to measure speedup vs std.HashMap - // // this is what we compare the swiss map to - // // this type was the best one I could find - // const InnerT = std.HashMap(sig.core.Pubkey, *accounts_db.index.AccountRef, struct { - // pub fn hash(self: @This(), key: sig.core.Pubkey) u64 { - // _ = self; - // return accounts_db.index.ShardedPubkeyRefMap.hash(key); - // } - // pub fn eql(self: @This(), key1: sig.core.Pubkey, key2: sig.core.Pubkey) bool { - // _ = self; - // return accounts_db.index.ShardedPubkeyRefMap.eql(key1, key2); - // } - // }, std.hash_map.default_max_load_percentage); - - // const std_write_time, const std_read_time = try benchGetOrPut( - // BenchHashMap(InnerT), - // allocator, - // account_refs, - // pubkeys, - // null, - // ); - - // // NOTE: if (speed_up < 1.0) "swissmap is slower" else "swissmap is faster"; - // const write_speedup = @as(f32, @floatFromInt(std_write_time.asNanos())) / @as(f32, @floatFromInt(write_time.asNanos())); - // const read_speedup = @as(f32, @floatFromInt(std_read_time.asNanos())) / @as(f32, @floatFromInt(read_time.asNanos())); - - return .{ - .read_time = units.convertDuration(read_time), - .write_time = units.convertDuration(write_time), - // .read_speedup_vs_std = read_speedup, - // .write_speedup_vs_std = write_speedup, - }; - } -}; - -fn benchGetOrPut( - comptime T: type, - allocator: std.mem.Allocator, - accounts: []accounts_db.index.AccountRef, - pubkeys: []sig.core.Pubkey, - read_amount: ?usize, -) !struct { sig.time.Duration, sig.time.Duration } { - var t = try T.initCapacity(allocator, accounts.len); - defer t.deinit(); - - var timer = try sig.time.Timer.start(); - for (0..accounts.len) |i| { - const result = t.getOrPutAssumeCapacity(accounts[i].pubkey); - if (!result.found_existing) { - result.value_ptr.* = &accounts[i]; - } else { - std.debug.panic("found something that shouldn't exist", .{}); - } - } - const write_time = timer.read(); - timer.reset(); - - var count: usize = 0; - const read_len = read_amount orelse accounts.len; - for (0..read_len) |i| { - const result = t.getOrPutAssumeCapacity(pubkeys[i]); - if (result.found_existing) { - count += result.value_ptr.*.slot; - } else { - std.debug.panic("not found", .{}); - } - } - std.mem.doNotOptimizeAway(count); - const read_time = timer.read(); - - return .{ write_time, read_time }; -} - -pub fn BenchHashMap(T: type) type { - return struct { - inner: T, - - // other T types that might be useful - // const T = std.AutoHashMap(Pubkey, *AccountRef); - // const T = std.AutoArrayHashMap(Pubkey, *AccountRef); - // const T = std.ArrayHashMap(Pubkey, *AccountRef, struct { - // pub fn hash(self: @This(), key: Pubkey) u32 { - // _ = self; - // return std.mem.readIntLittle(u32, key[0..4]); - // } - // pub fn eql(self: @This(), key1: Pubkey, key2: Pubkey, b_index: usize) bool { - // _ = b_index; - // _ = self; - // return equals(key1, key2); - // } - // }, false); - - const Self = @This(); - - pub fn deinit(self: *Self) void { - self.inner.deinit(); - } - - pub fn initCapacity(allocator: std.mem.Allocator, n: usize) !Self { - var refs = T.init(allocator); - try refs.ensureTotalCapacity(@intCast(n)); - return Self{ .inner = refs }; - } - - pub fn write(self: *Self, accounts: []accounts_db.index.AccountRef) !void { - for (0..accounts.len) |i| { - self.inner.putAssumeCapacity(accounts[i].pubkey, accounts[i]); - } - } - - pub fn read(self: *Self, pubkey: *sig.core.Pubkey) !usize { - if (self.inner.get(pubkey.*)) |acc| { - return 1 + @as(usize, @intCast(acc.offset)); - } else { - unreachable; - } - } - - pub fn getOrPutAssumeCapacity(self: *Self, pubkey: sig.core.Pubkey) T.GetOrPutResult { - const result = self.inner.getOrPutAssumeCapacity(pubkey); - return result; - } - }; -} - -test "bench swissmap read/write" { - _ = try BenchmarkSwissMap.swissmapReadWriteBenchmark(.nanos, .{ - .n_accounts = 1_000_000, - }); -} diff --git a/src/benchmarks.zig b/src/benchmarks.zig index d3556df770..2132e20e3b 100644 --- a/src/benchmarks.zig +++ b/src/benchmarks.zig @@ -33,7 +33,6 @@ const Benchmark = enum { gossip, ledger, socket_utils, - swissmap, sync, zksdk, }; @@ -152,17 +151,6 @@ pub fn main() !void { } } - if (filter == .swissmap or run_all_benchmarks) { - try benchmark( - allocator, - .from(logger), - @import("accountsdb/swiss_map.zig").BenchmarkSwissMap, - max_time_per_bench, - .nanos, - &maybe_metrics, - ); - } - if (std.mem.startsWith(u8, @tagName(filter), "accounts_db") or run_all_benchmarks) { var run_all = false; if (filter == .accounts_db or run_all_benchmarks) { diff --git a/src/core/pubkey.zig b/src/core/pubkey.zig index 8745948a6e..0bbf81be02 100644 --- a/src/core/pubkey.zig +++ b/src/core/pubkey.zig @@ -9,6 +9,17 @@ pub const Pubkey = extern struct { pub const ZEROES: Pubkey = .{ .data = .{0} ** SIZE }; + pub const HashContext = struct { + pub fn hash(_: HashContext, pubkey: Pubkey) u64 { + const key = std.mem.readInt(u64, pubkey.data[0..8], .little); + return std.hash.int(key); + } + + pub fn eql(_: HashContext, key1: Pubkey, key2: Pubkey) bool { + return key1.equals(&key2); + } + }; + pub fn fromPublicKey(public_key: *const std.crypto.sign.Ed25519.PublicKey) Pubkey { return .{ .data = public_key.bytes }; }