Skip to content

Commit 8891758

Browse files
committed
ZJIT: Pin the profiling metadata layout the memdiet merge depends on
TypeDistribution is 32 bytes and ProfileEntry 32; with the stack's 8-bucket distributions an inline [ProfiledType; 8] layout would be 152, and the boxed tail only costs the 3.6% of distributions that actually go polymorphic. Nothing else fails if a future change widens the inline part or puts a Vec back in ProfileEntry -- it just costs tens of megabytes on a compile-heavy workload -- so assert the sizes.
1 parent 087b444 commit 8891758

1 file changed

Lines changed: 32 additions & 1 deletion

File tree

zjit/src/profile.rs

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1479,7 +1479,38 @@ impl IseqProfile {
14791479

14801480
#[cfg(test)]
14811481
mod tests {
1482-
use crate::cruby::*;
1482+
use super::*;
1483+
1484+
/// ZJIT retains one of these per profiled operand of every profiled instruction, and
1485+
/// most stay monomorphic for the life of the process, so their size is the single
1486+
/// biggest lever on ZJIT's non-code memory. Pin it: widening the inline part of
1487+
/// `Distribution`, or letting `ProfileEntry` carry a `Vec` again, costs megabytes on a
1488+
/// compile-heavy workload without anything else failing.
1489+
#[test]
1490+
fn profiling_metadata_stays_small() {
1491+
// Bucket 0 inline (16B) + two u16 counts + a null tail pointer.
1492+
assert_eq!(size_of::<TypeDistribution>(), 32);
1493+
assert_eq!(size_of::<ProfileEntry>(), 32,
1494+
"opnd_types must stay a Box<[_]> (16 bytes), not a Vec (24)");
1495+
1496+
// What the same distribution would cost with all 8 buckets inline, which is
1497+
// what it cost before the tail was boxed: [ProfiledType; 8] + [u16; 8] + other.
1498+
const INLINE_SIZE: usize = 8 * size_of::<ProfiledType>() + 8 * size_of::<NumProfiles>()
1499+
+ size_of::<NumProfiles>();
1500+
assert!(INLINE_SIZE >= 146 && size_of::<TypeDistribution>() * 4 < INLINE_SIZE,
1501+
"the inline layout was {INLINE_SIZE} bytes; boxing has to be worth much more than a small constant");
1502+
1503+
// The tail a distribution pays for only once it goes polymorphic.
1504+
let mut dist = TypeDistribution::new();
1505+
let a = ProfiledType::object(VALUE(8));
1506+
let b = ProfiledType::object(VALUE(16));
1507+
dist.observe(a);
1508+
assert_eq!(dist.heap_size(), 0, "a monomorphic distribution owns no heap");
1509+
dist.observe(b);
1510+
assert_eq!(dist.heap_size(), 8 * size_of::<ProfiledType>() + 8 * size_of::<NumProfiles>(),
1511+
"the boxed tail is buckets 1..8 plus their counts, with index 0 left unused so \
1512+
bucket numbering lines up with Distribution's");
1513+
}
14831514

14841515
#[test]
14851516
fn can_profile_block_handler() {

0 commit comments

Comments
 (0)