InternPool: improve hashing performance

Key.PtrType is now an extern struct so that hashing it can be done by
reinterpreting bytes directly. It also uses the same representation for
type_pointer Tag encoding and the Key. Accessing pointer attributes now
requires packed struct access, however, many operations are now a copy
of a u32 rather than several independent fields.

This function moves the top two most used Key variants - pointer types
and pointer values - to use a single-shot hash function that branches
for small keys instead of calling memcpy.

As a result, perf against merge-base went from 1.17x ± 0.04 slower to
1.12x ± 0.04 slower. After the pointer value hashing was changed, total
CPU instructions spent in memcpy went from 4.40% to 4.08%, and after
additionally improving pointer type hashing, it further decreased to
3.72%.
This commit is contained in:
Andrew Kelley
2023-05-30 20:23:51 -07:00
parent c7d65fa368
commit 82f6f164a1
8 changed files with 527 additions and 392 deletions

View File

@@ -6430,8 +6430,10 @@ pub fn populateTestFunctions(
// func
try mod.intern(.{ .ptr = .{
.ty = try mod.intern(.{ .ptr_type = .{
.elem_type = test_decl.ty.toIntern(),
.is_const = true,
.child = test_decl.ty.toIntern(),
.flags = .{
.is_const = true,
},
} }),
.addr = .{ .decl = test_decl_index },
} }),
@@ -6466,9 +6468,11 @@ pub fn populateTestFunctions(
{
const new_ty = try mod.ptrType(.{
.elem_type = test_fn_ty.toIntern(),
.is_const = true,
.size = .Slice,
.child = test_fn_ty.toIntern(),
.flags = .{
.is_const = true,
.size = .Slice,
},
});
const new_val = decl.val;
const new_init = try mod.intern(.{ .ptr = .{
@@ -6681,65 +6685,68 @@ pub fn optionalType(mod: *Module, child_type: InternPool.Index) Allocator.Error!
pub fn ptrType(mod: *Module, info: InternPool.Key.PtrType) Allocator.Error!Type {
var canon_info = info;
const have_elem_layout = info.elem_type.toType().layoutIsResolved(mod);
const have_elem_layout = info.child.toType().layoutIsResolved(mod);
if (info.size == .C) canon_info.is_allowzero = true;
if (info.flags.size == .C) canon_info.flags.is_allowzero = true;
// Canonicalize non-zero alignment. If it matches the ABI alignment of the pointee
// type, we change it to 0 here. If this causes an assertion trip because the
// pointee type needs to be resolved more, that needs to be done before calling
// this ptr() function.
if (info.alignment.toByteUnitsOptional()) |info_align| {
if (have_elem_layout and info_align == info.elem_type.toType().abiAlignment(mod)) {
canon_info.alignment = .none;
if (info.flags.alignment.toByteUnitsOptional()) |info_align| {
if (have_elem_layout and info_align == info.child.toType().abiAlignment(mod)) {
canon_info.flags.alignment = .none;
}
}
switch (info.vector_index) {
switch (info.flags.vector_index) {
// Canonicalize host_size. If it matches the bit size of the pointee type,
// we change it to 0 here. If this causes an assertion trip, the pointee type
// needs to be resolved before calling this ptr() function.
.none => if (have_elem_layout and info.host_size != 0) {
const elem_bit_size = info.elem_type.toType().bitSize(mod);
assert(info.bit_offset + elem_bit_size <= info.host_size * 8);
if (info.host_size * 8 == elem_bit_size) {
canon_info.host_size = 0;
.none => if (have_elem_layout and info.packed_offset.host_size != 0) {
const elem_bit_size = info.child.toType().bitSize(mod);
assert(info.packed_offset.bit_offset + elem_bit_size <= info.packed_offset.host_size * 8);
if (info.packed_offset.host_size * 8 == elem_bit_size) {
canon_info.packed_offset.host_size = 0;
}
},
.runtime => {},
_ => assert(@enumToInt(info.vector_index) < info.host_size),
_ => assert(@enumToInt(info.flags.vector_index) < info.packed_offset.host_size),
}
return (try intern(mod, .{ .ptr_type = canon_info })).toType();
}
pub fn singleMutPtrType(mod: *Module, child_type: Type) Allocator.Error!Type {
return ptrType(mod, .{ .elem_type = child_type.toIntern() });
return ptrType(mod, .{ .child = child_type.toIntern() });
}
pub fn singleConstPtrType(mod: *Module, child_type: Type) Allocator.Error!Type {
return ptrType(mod, .{ .elem_type = child_type.toIntern(), .is_const = true });
return ptrType(mod, .{
.child = child_type.toIntern(),
.flags = .{
.is_const = true,
},
});
}
pub fn manyConstPtrType(mod: *Module, child_type: Type) Allocator.Error!Type {
return ptrType(mod, .{ .elem_type = child_type.toIntern(), .size = .Many, .is_const = true });
return ptrType(mod, .{
.child = child_type.toIntern(),
.flags = .{
.size = .Many,
.is_const = true,
},
});
}
pub fn adjustPtrTypeChild(mod: *Module, ptr_ty: Type, new_child: Type) Allocator.Error!Type {
const info = Type.ptrInfoIp(&mod.intern_pool, ptr_ty.toIntern());
return mod.ptrType(.{
.elem_type = new_child.toIntern(),
.child = new_child.toIntern(),
.sentinel = info.sentinel,
.alignment = info.alignment,
.host_size = info.host_size,
.bit_offset = info.bit_offset,
.vector_index = info.vector_index,
.size = info.size,
.is_const = info.is_const,
.is_volatile = info.is_volatile,
.is_allowzero = info.is_allowzero,
.address_space = info.address_space,
.flags = info.flags,
.packed_offset = info.packed_offset,
});
}