Optimize method chains by updating intermediate objects - #17685
Draft
mame wants to merge 9 commits into
Draft
Conversation
A flag asserting the only reference to the object is a single value-stack slot, so an optimized consumer may reuse it destructively. ObjectSpace.each_object unmarks before yielding.
Preparatory refactoring: split out the buffer allocation part so that later changes can allocate a string with a capacity larger than its initial length. No behavioral change.
prism marks + sends whose result feeds directly into another + as opt_plus_fresh: the producer allocates with growth headroom and tags the result STR_FRESH, and the existing opt_plus appends in place when its receiver carries the tag. A literal head is duplicated by dupstring_fresh, which requests twice the length so the size-pool rounding leaves headroom; too-long literals fall back to the plain sharing duplication, unarmed.
Same scheme as String: opt_plus_fresh allocates the chain head with growth headroom and marks it ARY_FRESH, the existing opt_plus concatenates onto a marked receiver in place, and the chain end restores the exact-capacity result.
|
The following files are maintained in the following upstream repositories:
Please file a pull request to the above instead. Thank you! |
prism marks every send whose result directly becomes the receiver of another send with advisory callinfo bits (VM_CALL_FRESH_PROD/CONS). Whether anything happens is decided when the call cache is filled: both the callinfo bit and the fresh_producer/fresh_consumer bit on the method entry must be set. Marked builtins arm STR_FRESH on their result, and consumers call the bang counterpart on a receiver that carries it. A consumer resolving anywhere else drops the flag before the receiver can escape.
Array consumers get flat fresh implementations that skip the bangs' modify/enumerator/repair overhead. Fresh-array producers join the table: literals, String#split/chars/lines, Range#to_a and Enumerable#flat_map/sort_by. Under a JIT, array.rb replaces map/select with Ruby definitions, which trips the kill switch at boot: Array chains are then simply left alone.
merge/transform_values/select/reject/compact consume a fresh receiver in place; Hash#invert joins as a producer.
f(*xs.map { }) and foo(**opts.merge(defaults)) mark the producer; the
callee-side rest/kwrest setup takes the fresh operand as *_SPLAT_MUT,
eliding the defensive dup and never leaking the mark. This is the
runtime generalization of VM_CALL_ARGS_SPLAT_MUT / KW_SPLAT_MUT.
The _fresh producer insns get arming twins of their plain gen functions, and + sites marked as chain members substitute the consume/arm entry points. Marked builtin sends just call the plain cfunc in JITed code: sound but inactive (the interpreter does the in-place reuse). ZJIT side-exits on the _fresh insns.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is an experimental optimization that speeds up typical method chains by destructively updating their intermediate objects:
ary.map {}.select {}runs likeary.map {}.select! {}str.strip.upcaseruns likestr.strip.upcase!"foo" + str + strruns like"foo" << str << strIntermediate String/Array/Hash objects whose only reference is a single value-stack slot are tagged with a FRESH flag, and the next built-in call in the chain reuses them in place. Whether a call is optimized is decided via the method cache: both an advisory callinfo bit and a flag on the built-in's method entry must be set, so redefined methods naturally opt out.
Common idioms get 1.05-1.5x faster (
"foo" + str1.5x,ary + ary + ary2.1x,ary.uniq.sort.reverse1.06x), and non-optimized code is unaffected.Still a PoC. All of btest and the relevant test-all suites pass.