Commit 1d0b599
authored
improvement: avoid quadratic cost evaluating in-list filters at runtime (ash-project#2802)
* test: characterise the equality `in` means at runtime
`Ash.Query.Operator.In.evaluate/1` decides membership with `Comp.equal?/2`,
which is semantic rather than structural equality: `1` equals `1.0`, a
`Decimal` equals both the integer and the string spelling the same number,
an `Ash.CiString` equals a binary differing only in case, and — since OTP 27
made `0.0` and `-0.0` distinct terms — signed zeroes still equal each other.
None of that was covered. Pin it down before touching the function, so that
any later change to how membership is decided has to keep meaning the same
thing.
The `Version` struct stands in for the comparability any application can add
for its own types with `Ash.Type.Comparable.defcomparable/3`, which is why no
amount of knowledge about built-in types can substitute for asking `Comp`.
* improvement: decide `in` by set membership before comparing members
`Ash.Query.Operator.In.new/2` builds a `MapSet` out of the member list, but
`evaluate/1` never used it as one — it walked the members, calling
`Comp.equal?/2` on each. The set bought nothing, and every record put through
a runtime filter paid a scan of the whole list.
That is quadratic exactly where it hurts. `Ash.Filter.Runtime` evaluates the
filter once per record, and a relationship load frames as
`source_attribute in [N parent ids]` over the N records it selects: N records
by N members. Nearly all of that time is spent in `Comp.new/2`, which resolves
the `Comparable` implementation by concatenating a module name at runtime,
once per member per record.
A set miss cannot settle the question, because `Comp.equal?/2` matches values
that a `MapSet` holds apart and applications can add further pairings with
`Ash.Type.Comparable.defcomparable/3`. A set hit can settle it: equality is
reflexive, so a member that is the same term is a member that compares equal.
Take that hit in constant time, and fall back to the scan only on a miss.
Filtering N records against N ids, the shape a relationship load takes:
records before after
500 79.9 ms 0.4 ms
1,000 380 ms 0.7 ms
2,000 1.42 s 1.5 ms
4,000 5.55 s 3.2 ms
8,000 21.7 s 5.9 ms
Per-record cost is now flat rather than growing with the size of the list.
Records whose value is absent from the list still walk the members; that is
the price of semantic equality staying semantic.1 parent eb7f5ef commit 1d0b599
2 files changed
Lines changed: 158 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
30 | 30 | | |
31 | 31 | | |
32 | 32 | | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
33 | 42 | | |
34 | 43 | | |
35 | 44 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
5 | 19 | | |
6 | 20 | | |
7 | 21 | | |
| 22 | + | |
| 23 | + | |
8 | 24 | | |
9 | 25 | | |
| 26 | + | |
| 27 | + | |
10 | 28 | | |
11 | 29 | | |
12 | 30 | | |
| |||
30 | 48 | | |
31 | 49 | | |
32 | 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 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
| 168 | + | |
| 169 | + | |
| 170 | + | |
| 171 | + | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
| 180 | + | |
| 181 | + | |
33 | 182 | | |
0 commit comments