Skip to content

Commit 4d2f98f

Browse files
committed
ZJIT: Box the rarely-used profile side tables
IseqProfile holds two HashMaps -- super_cme for invokesuper receivers and splat_lengths for splat call sites -- that only ISEQs containing those instructions ever populate. An empty std HashMap allocates nothing but still occupies 48 bytes inline, so every payload paid 96 bytes to hold two empty tables. ZJIT allocates a payload for every ISEQ it merely profiles, which on an RDoc-over-stdlib workload was 28,614 payloads. Wrap both in Option<Box<_>>, taking IseqPayload from 152 to 72 bytes. mem_iseq_payload_bytes drops from 2.36 MB to 1.12 MB and the payloads still retained for freed ISEQs (mem_unaccounted_bytes) from 2.03 MB to 0.98 MB. Adapted for zjit/min: this branch has six side tables, not two, so all six are boxed -- `send_mid`, `forwarded_cis`, `block_handlers` and `block_fallbacks` alongside `super_cme` and `splat_lengths`. That is 288 bytes of empty `HashMap` headers per payload rather than 96. The mutable accessors this branch already routes every write through (`super_cme_mut()` and friends) become the `get_or_insert_with` sites, so no caller changes; the readers take `as_ref()?` and the object walks iterate `iter().flat_map(...)`.
1 parent 1a6a886 commit 4d2f98f

1 file changed

Lines changed: 90 additions & 68 deletions

File tree

zjit/src/profile.rs

Lines changed: 90 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -973,28 +973,32 @@ pub struct IseqProfile {
973973
/// Only instructions that have actually been profiled have entries here.
974974
entries: Vec<ProfileEntry>,
975975

976-
/// Method entries for `super` calls (stored as VALUE to be GC-safe)
977-
super_cme: HashMap<YarvInsnIdx, TypeDistribution>,
976+
/// Method entries for `super` calls (stored as VALUE to be GC-safe).
977+
/// Boxed because only ISEQs containing `invokesuper` ever use it, and an
978+
/// inline `HashMap` costs every payload 48 bytes to hold nothing.
979+
super_cme: Option<Box<HashMap<YarvInsnIdx, TypeDistribution>>>,
978980

979981
/// Method-name symbols observed as the first argument of `send`/`__send__` call sites
980982
/// (stored as VALUE to be GC-safe)
981-
send_mid: HashMap<YarvInsnIdx, TypeDistribution>,
983+
send_mid: Option<Box<HashMap<YarvInsnIdx, TypeDistribution>>>,
982984

983-
/// Observed lengths of caller splat arrays for call instructions.
984-
splat_lengths: HashMap<YarvInsnIdx, SplatLengthDistribution>,
985+
/// Observed lengths of caller splat arrays for call instructions. Boxed for
986+
/// the same reason as `super_cme`.
987+
splat_lengths: Option<Box<HashMap<YarvInsnIdx, SplatLengthDistribution>>>,
988+
989+
/// Callinfos observed in the `...` local at `sendforward` sites. Unlike the tables above,
990+
/// this one holds no objects: see `profile_forwarded_callinfo`.
991+
forwarded_cis: Option<Box<HashMap<YarvInsnIdx, ForwardedCiDistribution>>>,
985992

986993
/// Block handlers observed at `invokeblock` sites (stored as VALUE to be GC-safe).
987994
/// Kept out of `opnd_types` so that the entry's operand slots profile the yielded
988995
/// arguments instead: a `yield` to a Symbol block sends to the first argument, and
989996
/// that send only specializes if the argument's class was profiled.
990-
block_handlers: HashMap<YarvInsnIdx, TypeDistribution>,
991-
/// Callinfos observed in the `...` local at `sendforward` sites. Unlike the tables above,
992-
/// this one holds no objects: see `profile_forwarded_callinfo`.
993-
forwarded_cis: HashMap<YarvInsnIdx, ForwardedCiDistribution>,
997+
block_handlers: Option<Box<HashMap<YarvInsnIdx, TypeDistribution>>>,
994998

995999
/// Live-traffic re-profiling windows for `invokeblock` sites whose compiled dispatch keeps
9961000
/// reaching `rb_vm_invokeblock()`.
997-
block_fallbacks: HashMap<YarvInsnIdx, BlockFallbackEntry>,
1001+
block_fallbacks: Option<Box<HashMap<YarvInsnIdx, BlockFallbackEntry>>>,
9981002

9991003
/// Dense copy of every object the distributions above reference, which is what
10001004
/// GC marking actually walks. See [`IseqProfile::marked_objects`].
@@ -1017,12 +1021,12 @@ impl IseqProfile {
10171021
pub fn new() -> Self {
10181022
Self {
10191023
entries: Vec::new(),
1020-
super_cme: HashMap::new(),
1021-
send_mid: HashMap::new(),
1022-
splat_lengths: HashMap::new(),
1023-
block_handlers: HashMap::new(),
1024-
forwarded_cis: HashMap::new(),
1025-
block_fallbacks: HashMap::new(),
1024+
super_cme: None,
1025+
send_mid: None,
1026+
splat_lengths: None,
1027+
forwarded_cis: None,
1028+
block_handlers: None,
1029+
block_fallbacks: None,
10261030
marked_objects: Vec::new(),
10271031
// Nothing recorded yet, so the (empty) dense copy is already accurate.
10281032
marked_objects_stale: false,
@@ -1053,47 +1057,47 @@ impl IseqProfile {
10531057
}
10541058
}
10551059

1056-
/// Mutable access to the forwarded callinfo distributions. Needs no marking invalidation
1057-
/// because a packed callinfo is an immediate, not an object, and a heap one is recorded as
1058-
/// `None`.
1059-
fn forwarded_cis_mut(&mut self) -> &mut HashMap<YarvInsnIdx, ForwardedCiDistribution> {
1060-
&mut self.forwarded_cis
1061-
}
1062-
10631060
/// Mutable access to the `invokesuper` method-entry distributions. Goes through
10641061
/// here so that recording a CME cannot skip invalidating the dense copy GC
10651062
/// marking walks.
10661063
fn super_cme_mut(&mut self) -> &mut HashMap<YarvInsnIdx, TypeDistribution> {
10671064
self.marked_objects_stale = true;
1068-
&mut self.super_cme
1065+
self.super_cme.get_or_insert_with(Default::default)
10691066
}
10701067

10711068
/// Mutable access to the `send`/`__send__` method-name distributions. Same reason
10721069
/// as [`Self::super_cme_mut`].
10731070
fn send_mid_mut(&mut self) -> &mut HashMap<YarvInsnIdx, TypeDistribution> {
10741071
self.marked_objects_stale = true;
1075-
&mut self.send_mid
1072+
self.send_mid.get_or_insert_with(Default::default)
10761073
}
10771074

10781075
/// Mutable access to the splat length distributions. Unlike the two above this
10791076
/// needs no invalidation: a `SplatLengthDistribution` holds array lengths, not
10801077
/// objects, so GC marking never looks at it.
10811078
fn splat_lengths_mut(&mut self) -> &mut HashMap<YarvInsnIdx, SplatLengthDistribution> {
1082-
&mut self.splat_lengths
1079+
self.splat_lengths.get_or_insert_with(Default::default)
1080+
}
1081+
1082+
/// Mutable access to the forwarded callinfo distributions. Needs no marking invalidation
1083+
/// for the same reason `splat_lengths_mut` does not: a packed callinfo is an immediate, not
1084+
/// an object, and a heap one is recorded as `None`.
1085+
fn forwarded_cis_mut(&mut self) -> &mut HashMap<YarvInsnIdx, ForwardedCiDistribution> {
1086+
self.forwarded_cis.get_or_insert_with(Default::default)
10831087
}
10841088

10851089
/// Mutable access to the `invokeblock` block-handler distributions. Same reason
10861090
/// as [`Self::super_cme_mut`].
10871091
fn block_handlers_mut(&mut self) -> &mut HashMap<YarvInsnIdx, TypeDistribution> {
10881092
self.marked_objects_stale = true;
1089-
&mut self.block_handlers
1093+
self.block_handlers.get_or_insert_with(Default::default)
10901094
}
10911095

10921096
/// Mutable access to the `invokeblock` re-profiling windows. Same reason as
10931097
/// [`Self::super_cme_mut`]: the windows hold block handlers, which are objects.
10941098
fn block_fallbacks_mut(&mut self) -> &mut HashMap<YarvInsnIdx, BlockFallbackEntry> {
10951099
self.marked_objects_stale = true;
1096-
&mut self.block_fallbacks
1100+
self.block_fallbacks.get_or_insert_with(Default::default)
10971101
}
10981102

10991103
/// Record one block handler that reached a compiled `yield`'s generic fallback, and say
@@ -1206,7 +1210,7 @@ impl IseqProfile {
12061210
}
12071211

12081212
pub fn get_splat_length_summary(&self, insn_idx: YarvInsnIdx) -> Option<SplatLengthDistributionSummary> {
1209-
self.splat_lengths.get(&insn_idx)
1213+
self.splat_lengths.as_ref()?.get(&insn_idx)
12101214
.map(SplatLengthDistributionSummary::new)
12111215
}
12121216

@@ -1220,29 +1224,29 @@ impl IseqProfile {
12201224
}
12211225
}
12221226

1223-
/// The distribution of block handlers observed at an `invokeblock` site.
1224-
pub fn get_block_handlers(&self, insn_idx: YarvInsnIdx) -> Option<TypeDistributionSummary> {
1225-
self.block_handlers.get(&insn_idx).map(TypeDistributionSummary::new)
1226-
}
1227-
1228-
/// The distribution of callinfos a `sendforward` site was seen forwarding. See
1229-
/// [`profile_forwarded_callinfo`].
1230-
pub fn get_forwarded_callinfos(&self, insn_idx: YarvInsnIdx) -> Option<ForwardedCiDistributionSummary> {
1231-
self.forwarded_cis.get(&insn_idx).map(ForwardedCiDistributionSummary::new)
1232-
}
1233-
12341227
/// The whole distribution of frame method entries seen at an `invokesuper` site. A site with
12351228
/// more than one is a `super` inside a method body that several classes run, most often a
12361229
/// module method reached through more than one includer; each such method entry resolves
12371230
/// `super` to a different target.
12381231
pub fn get_super_method_entries(&self, insn_idx: YarvInsnIdx) -> Option<TypeDistributionSummary> {
1239-
let entry = self.super_cme.get(&insn_idx)?;
1232+
let entry = self.super_cme.as_ref()?.get(&insn_idx)?;
12401233
Some(TypeDistributionSummary::new(entry))
12411234
}
12421235

12431236
/// Get the distribution of method-name symbols seen at a `send`/`__send__` call site.
12441237
pub fn get_send_method_names(&self, insn_idx: YarvInsnIdx) -> Option<TypeDistributionSummary> {
1245-
self.send_mid.get(&insn_idx).map(TypeDistributionSummary::new)
1238+
self.send_mid.as_ref()?.get(&insn_idx).map(TypeDistributionSummary::new)
1239+
}
1240+
1241+
/// The distribution of callinfos a `sendforward` site was seen forwarding. See
1242+
/// [`profile_forwarded_callinfo`].
1243+
pub fn get_forwarded_callinfos(&self, insn_idx: YarvInsnIdx) -> Option<ForwardedCiDistributionSummary> {
1244+
self.forwarded_cis.as_ref()?.get(&insn_idx).map(ForwardedCiDistributionSummary::new)
1245+
}
1246+
1247+
/// The distribution of block handlers observed at an `invokeblock` site.
1248+
pub fn get_block_handlers(&self, insn_idx: YarvInsnIdx) -> Option<TypeDistributionSummary> {
1249+
self.block_handlers.as_ref()?.get(&insn_idx).map(TypeDistributionSummary::new)
12461250
}
12471251

12481252
/// Bytes this profile owns on the Rust heap, excluding the `IseqProfile`
@@ -1265,20 +1269,38 @@ impl IseqProfile {
12651269
}
12661270
}
12671271
}
1268-
out.bytes += hash_table_bytes::<(YarvInsnIdx, TypeDistribution)>(self.super_cme.capacity());
1269-
out.bytes += hash_table_bytes::<(YarvInsnIdx, TypeDistribution)>(self.send_mid.capacity());
1270-
out.bytes += hash_table_bytes::<(YarvInsnIdx, SplatLengthDistribution)>(self.splat_lengths.capacity());
1271-
out.bytes += hash_table_bytes::<(YarvInsnIdx, ForwardedCiDistribution)>(self.forwarded_cis.capacity());
1272-
out.bytes += hash_table_bytes::<(YarvInsnIdx, TypeDistribution)>(self.block_handlers.capacity());
1273-
out.bytes += hash_table_bytes::<(YarvInsnIdx, BlockFallbackEntry)>(self.block_fallbacks.capacity());
1274-
// Boxed distribution tails, wherever they live.
1275-
out.bytes += self.super_cme.values().map(TypeDistribution::heap_size).sum::<usize>();
1276-
out.bytes += self.send_mid.values().map(TypeDistribution::heap_size).sum::<usize>();
1277-
out.bytes += self.splat_lengths.values().map(SplatLengthDistribution::heap_size).sum::<usize>();
1278-
out.bytes += self.forwarded_cis.values().map(ForwardedCiDistribution::heap_size).sum::<usize>();
1279-
out.bytes += self.block_handlers.values().map(TypeDistribution::heap_size).sum::<usize>();
1280-
out.bytes += self.block_fallbacks.values()
1281-
.map(|entry| entry.dist.heap_size() + entry.symbol_recv.heap_size()).sum::<usize>();
1272+
// The boxed side tables: the `HashMap` header itself once boxed, the bucket
1273+
// array, and each distribution's own boxed tail.
1274+
if let Some(table) = self.super_cme.as_ref() {
1275+
out.bytes += size_of::<HashMap<YarvInsnIdx, TypeDistribution>>()
1276+
+ hash_table_bytes::<(YarvInsnIdx, TypeDistribution)>(table.capacity())
1277+
+ table.values().map(TypeDistribution::heap_size).sum::<usize>();
1278+
}
1279+
if let Some(table) = self.send_mid.as_ref() {
1280+
out.bytes += size_of::<HashMap<YarvInsnIdx, TypeDistribution>>()
1281+
+ hash_table_bytes::<(YarvInsnIdx, TypeDistribution)>(table.capacity())
1282+
+ table.values().map(TypeDistribution::heap_size).sum::<usize>();
1283+
}
1284+
if let Some(table) = self.splat_lengths.as_ref() {
1285+
out.bytes += size_of::<HashMap<YarvInsnIdx, SplatLengthDistribution>>()
1286+
+ hash_table_bytes::<(YarvInsnIdx, SplatLengthDistribution)>(table.capacity())
1287+
+ table.values().map(SplatLengthDistribution::heap_size).sum::<usize>();
1288+
}
1289+
if let Some(table) = self.forwarded_cis.as_ref() {
1290+
out.bytes += size_of::<HashMap<YarvInsnIdx, ForwardedCiDistribution>>()
1291+
+ hash_table_bytes::<(YarvInsnIdx, ForwardedCiDistribution)>(table.capacity())
1292+
+ table.values().map(ForwardedCiDistribution::heap_size).sum::<usize>();
1293+
}
1294+
if let Some(table) = self.block_handlers.as_ref() {
1295+
out.bytes += size_of::<HashMap<YarvInsnIdx, TypeDistribution>>()
1296+
+ hash_table_bytes::<(YarvInsnIdx, TypeDistribution)>(table.capacity())
1297+
+ table.values().map(TypeDistribution::heap_size).sum::<usize>();
1298+
}
1299+
if let Some(table) = self.block_fallbacks.as_ref() {
1300+
out.bytes += size_of::<HashMap<YarvInsnIdx, BlockFallbackEntry>>()
1301+
+ hash_table_bytes::<(YarvInsnIdx, BlockFallbackEntry)>(table.capacity())
1302+
+ table.values().map(|entry| entry.dist.heap_size() + entry.symbol_recv.heap_size()).sum::<usize>();
1303+
}
12821304
out
12831305
}
12841306

@@ -1294,25 +1316,25 @@ impl IseqProfile {
12941316
}
12951317
}
12961318

1297-
for super_cme_values in self.super_cme.values() {
1319+
for super_cme_values in self.super_cme.iter().flat_map(|map| map.values()) {
12981320
for profiled_type in super_cme_values.each_item() {
12991321
callback(profiled_type.class)
13001322
}
13011323
}
13021324

1303-
for handler_values in self.block_handlers.values() {
1304-
for profiled_type in handler_values.each_item() {
1325+
for send_mid_values in self.send_mid.iter().flat_map(|map| map.values()) {
1326+
for profiled_type in send_mid_values.each_item() {
13051327
callback(profiled_type.class)
13061328
}
13071329
}
13081330

1309-
for send_mid_values in self.send_mid.values() {
1310-
for profiled_type in send_mid_values.each_item() {
1331+
for handler_values in self.block_handlers.iter().flat_map(|map| map.values()) {
1332+
for profiled_type in handler_values.each_item() {
13111333
callback(profiled_type.class)
13121334
}
13131335
}
13141336

1315-
for fallback in self.block_fallbacks.values() {
1337+
for fallback in self.block_fallbacks.iter().flat_map(|map| map.values()) {
13161338
for profiled_type in fallback.dist.each_item().chain(fallback.symbol_recv.each_item()) {
13171339
callback(profiled_type.class)
13181340
}
@@ -1383,25 +1405,25 @@ impl IseqProfile {
13831405
}
13841406

13851407
// Update CME references if they move during compaction.
1386-
for super_cme_values in self.super_cme.values_mut() {
1408+
for super_cme_values in self.super_cme.iter_mut().flat_map(|map| map.values_mut()) {
13871409
for ref mut profiled_type in super_cme_values.each_item_mut() {
13881410
callback(&mut profiled_type.class)
13891411
}
13901412
}
13911413

1392-
for handler_values in self.block_handlers.values_mut() {
1393-
for ref mut profiled_type in handler_values.each_item_mut() {
1414+
for send_mid_values in self.send_mid.iter_mut().flat_map(|map| map.values_mut()) {
1415+
for ref mut profiled_type in send_mid_values.each_item_mut() {
13941416
callback(&mut profiled_type.class)
13951417
}
13961418
}
13971419

1398-
for send_mid_values in self.send_mid.values_mut() {
1399-
for ref mut profiled_type in send_mid_values.each_item_mut() {
1420+
for handler_values in self.block_handlers.iter_mut().flat_map(|map| map.values_mut()) {
1421+
for ref mut profiled_type in handler_values.each_item_mut() {
14001422
callback(&mut profiled_type.class)
14011423
}
14021424
}
14031425

1404-
for fallback in self.block_fallbacks.values_mut() {
1426+
for fallback in self.block_fallbacks.iter_mut().flat_map(|map| map.values_mut()) {
14051427
for ref mut profiled_type in fallback.dist.each_item_mut() {
14061428
callback(&mut profiled_type.class)
14071429
}

0 commit comments

Comments
 (0)