Commit 421d05c
committed
ZJIT: Back ivar shape-chain misses with a per-name shape table
ZJIT specializes an instance variable access by profiling the receiver's
shapes and emitting a chain of shape_id guards, one arm per profiled
shape. That covers a site that sees a handful of shapes. It does not
cover code like Shopify's Storefront Renderer, where one class has
hundreds of live shapes because instances stop at different points in a
long chain of conditionally-assigned ivars: the chain cannot be widened
far enough (each arm is code, and MAX_IVAR_RESPECIALIZATIONS bounds the
recompiles on purpose), so those receivers fall off the end of the chain
into a generic rb_ivar_get / rb_vm_getinstancevariable call. On SFR that
is 10.8M getivar and 2.4M setivar generic calls per ~350 requests, each
one walking the shape tree for an answer that only depends on the
receiver's shape id.
Put a lookup table between the guard chain and that call. Each ivar
*name* accessed by such a site gets one direct-mapped table of 8-byte
entries keyed by the raw shape id, holding the byte offset of the ivar
within the receiver. Reads probe it inline -- the shape id is already in
a register for the guard chain, so it is a multiply, a shift, a mask, two
32-bit loads, two compares and a load -- and produce the value with no
call at all, including when the answer is nil because the shape does not
have the ivar. Writes have no inline probe (a store also needs a frozen
check and a write barrier) but resolve out of the same table.
Keying by name, not by site: shape_id -> location of @name does not
depend on the site, so sites sharing an ivar warm each other's entries,
and the table size multiplies by the number of ivar names accessed
polymorphically rather than by the number of compiled sites (and their
recompiles).
Nothing needs invalidating. Shapes are immutable and the shape tree is
append-only, so shape_id -> index is a pure function; and every mutation
that could move an object's ivars -- adding one, remove_instance_variable,
freeze, object_id, going too-complex, compaction changing embedded
capacity or layout -- changes a bit of the object's shape_id, which is
stored unmasked as the key. The table holds no VALUEs, so the GC has
nothing to mark. Entries are single naturally-aligned words published
with one relaxed atomic store, so a concurrent ractor sees an old entry
or a new one, never a mix.
Measured with benchmark/zjit_ivar_megashape.rb (110-deep ivar chain,
220 shapes per name), instructions retired per access relative to
--zjit-disable-ivar-cache, for a cyclic access pattern (the worst case
for a direct-mapped table) and a Zipf-skewed one (what applications look
like):
cyclic skewed
attr_reader 0.37 0.30
plain read 0.47 0.36
absent read 0.33 0.41
attr_writer 0.43 0.48
plain write 0.35 0.40
monomorphic 1.00 1.00
Wall clock on the same benchmark moves 220 -> 26 ns/op for an absent
read and 55 -> 21 ns/op for an attr_reader. The monomorphic and low-
polymorphism paths are untouched by construction: only the codegen of
Insn::GetIvar and Insn::SetIvar changed, which a shape-specialized site
never emits. HIR and fast-path disassembly for a monomorphic site are
identical to before.
Memory is 8 bytes times --zjit-ivar-cache-entries (default 512, i.e.
4KiB) per ivar name, reported as mem_ivar_cache_bytes and
mem_ivar_cache_count in the mem_* breakdown. Undersizing the table is
not a graceful degradation -- a direct-mapped table smaller than the
shape working set misses on nearly every access -- so the default is
chosen from the measured curve; see DEFAULT_CACHE_ENTRIES.
New counters: getivar_cache_hit (served inline, no call),
getivar_cache_helper_hit, _fill, _evict, _uncacheable, _immediate, the
setivar_cache_* equivalents, and ivar_cache_alloc_count.
--zjit-disable-ivar-cache restores the previous code exactly, for A/B.
[zjit/min port note] Ported without mem_stats.rs (the memdiet accounting), keeping
master's plain-HashMap profile layout. The two emit_ivar_reprofile() call sites are
re-added by hand: upstream gates them on ShapeMiss::CallFallback, an enum that comes
from excluded work, so here they sit on the two paths that call the generic fallback
instead (no-profile under a no-exit policy, and a shape-chain miss), which is the
same set of sites for this tree.
[reorder port note] Ported ahead of the megamorphic send class table and
the ivar-reprofile machinery: send_cache options/counters/state and the
IvarReprofile/gen_ivar_reprofile pieces were dropped from conflict hunks;
they return with their own commits.1 parent 0c0b0cc commit 421d05c
8 files changed
Lines changed: 965 additions & 9 deletions
File tree
- benchmark
- zjit/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1223 | 1223 | | |
1224 | 1224 | | |
1225 | 1225 | | |
1226 | | - | |
| 1226 | + | |
1227 | 1227 | | |
1228 | 1228 | | |
1229 | 1229 | | |
| |||
1668 | 1668 | | |
1669 | 1669 | | |
1670 | 1670 | | |
1671 | | - | |
1672 | | - | |
| 1671 | + | |
| 1672 | + | |
| 1673 | + | |
| 1674 | + | |
| 1675 | + | |
| 1676 | + | |
| 1677 | + | |
| 1678 | + | |
| 1679 | + | |
| 1680 | + | |
| 1681 | + | |
| 1682 | + | |
| 1683 | + | |
| 1684 | + | |
| 1685 | + | |
| 1686 | + | |
| 1687 | + | |
| 1688 | + | |
| 1689 | + | |
| 1690 | + | |
| 1691 | + | |
| 1692 | + | |
| 1693 | + | |
| 1694 | + | |
| 1695 | + | |
| 1696 | + | |
| 1697 | + | |
| 1698 | + | |
| 1699 | + | |
| 1700 | + | |
| 1701 | + | |
| 1702 | + | |
| 1703 | + | |
| 1704 | + | |
| 1705 | + | |
| 1706 | + | |
| 1707 | + | |
| 1708 | + | |
| 1709 | + | |
| 1710 | + | |
| 1711 | + | |
| 1712 | + | |
| 1713 | + | |
| 1714 | + | |
| 1715 | + | |
| 1716 | + | |
1673 | 1717 | | |
1674 | 1718 | | |
1675 | 1719 | | |
| |||
1679 | 1723 | | |
1680 | 1724 | | |
1681 | 1725 | | |
1682 | | - | |
| 1726 | + | |
| 1727 | + | |
| 1728 | + | |
| 1729 | + | |
| 1730 | + | |
| 1731 | + | |
| 1732 | + | |
| 1733 | + | |
| 1734 | + | |
| 1735 | + | |
| 1736 | + | |
| 1737 | + | |
| 1738 | + | |
| 1739 | + | |
| 1740 | + | |
| 1741 | + | |
| 1742 | + | |
| 1743 | + | |
| 1744 | + | |
| 1745 | + | |
| 1746 | + | |
| 1747 | + | |
| 1748 | + | |
| 1749 | + | |
| 1750 | + | |
| 1751 | + | |
| 1752 | + | |
| 1753 | + | |
| 1754 | + | |
| 1755 | + | |
| 1756 | + | |
| 1757 | + | |
| 1758 | + | |
| 1759 | + | |
| 1760 | + | |
| 1761 | + | |
| 1762 | + | |
| 1763 | + | |
| 1764 | + | |
| 1765 | + | |
| 1766 | + | |
| 1767 | + | |
| 1768 | + | |
| 1769 | + | |
| 1770 | + | |
| 1771 | + | |
| 1772 | + | |
| 1773 | + | |
| 1774 | + | |
| 1775 | + | |
| 1776 | + | |
| 1777 | + | |
| 1778 | + | |
| 1779 | + | |
| 1780 | + | |
| 1781 | + | |
| 1782 | + | |
| 1783 | + | |
| 1784 | + | |
| 1785 | + | |
| 1786 | + | |
| 1787 | + | |
| 1788 | + | |
| 1789 | + | |
| 1790 | + | |
| 1791 | + | |
| 1792 | + | |
| 1793 | + | |
| 1794 | + | |
| 1795 | + | |
| 1796 | + | |
| 1797 | + | |
| 1798 | + | |
| 1799 | + | |
| 1800 | + | |
| 1801 | + | |
| 1802 | + | |
| 1803 | + | |
| 1804 | + | |
| 1805 | + | |
| 1806 | + | |
| 1807 | + | |
| 1808 | + | |
1683 | 1809 | | |
1684 | 1810 | | |
1685 | 1811 | | |
1686 | 1812 | | |
1687 | | - | |
1688 | | - | |
1689 | | - | |
1690 | | - | |
1691 | | - | |
| 1813 | + | |
| 1814 | + | |
| 1815 | + | |
| 1816 | + | |
| 1817 | + | |
| 1818 | + | |
| 1819 | + | |
| 1820 | + | |
1692 | 1821 | | |
| 1822 | + | |
| 1823 | + | |
| 1824 | + | |
1693 | 1825 | | |
1694 | 1826 | | |
1695 | 1827 | | |
| |||
0 commit comments