Community
Participate
Working Groups
The pivot model is 4 times slower than the non-pivot model for: let i : Integer = 10000000 in Sequence{1..i}->iterate(j : Integer; acc : Integer = 0 | acc + j)*2/i-1 but 5 times faster for: Sequence{1..10000}->iterate(i : Integer; acc : Sequence(Integer) = Sequence{1, 1} | acc->including(acc->at(i) + acc->at(i+1)))->last() I suspect this is because the pivot model iterations have a semi-smart collection accumulator that can be updated in place. The same does not apply to the simple Integer in the first example. The accumulator is only semi-smart; to be truly smart it should detect that the ->including() is the last use, so the old collection can be re-used avoiding the copy and add. Is the degraded comparative pivot model speed solely attributable to use of BigInteger and/or dynamic dispatch? The 4 times slower is surprising. ----- Introduce smart IteratorValues so that the Iterator changes in-place. Introduce smart non-collection accumulators. Detect an ability to do collection re-use.
For Sequence{3..20000}->iterate(i : Integer ; acc : Sequence(Integer) = Sequence{0, 1} | acc->append(acc->at(i - 2) + acc->at(i - 1)))->last() the Indigo Xtext OCL Console takes about 7 seconds. The same using the WIP direct Java code generator takes 3.5 seconds. The direct Java code generator has inline dispatching and int-sized integers where possible. If, the write-after-last-read append usage is manually exploited, an in-place Accumulator can be used and the speed improves to just under 200ms. Clearly Collection thrashing is the dominant problem once code gen is good. Therefore introduce a write-after-last-read analysis to support use of Collection Accumulators. If 3.3 seconds is due to Collection thrashing, the non Collection speed up is 7-3.5=3.5 to 200ms, so the direct Java may be 10 to 20 times faster.
(In reply to comment #0) > The pivot model is 4 times slower than the non-pivot model for: > > let i : Integer = 10000000 in Sequence{1..i}->iterate(j : Integer; acc : > Integer = 0 | acc + j)*2/i-1 Hmm. Doing this in the Indigo Consoles, the non-pivot takes about 12 seconds and the pivot a few minutes! Using the direct Java code generator, with a lazy SequenceRangeValue and int/long/BigInteger Values as appropriate takes 8 seconds, which is at last faster, even though using arbitrary number ranges and dynamic dispatch. Maybe an updating accumulator can do better still.
A Fibonacci value may be calculated by: > Sequence{1..10000}->iterate(i : Integer; acc : Sequence(Integer) = Sequence{1, > 1} | acc->including(acc->at(i) + acc->at(i+1)))->last() [This dummy comment just gets Fibonacci and performance and recursion as Bugzilla hits.]
A smart accumulator is a localized solution. A lazy collection can do more. The Pivot architecture has a SetValue (interface) with typically a Collection of underlying elements. Since we use EMF, the elements are often the EList.data, so the Set isn't a true Set. This should be clearer with underlying ListAsSet, SmallSet, UnenforcedSet capabilities to handle use of an unederlying list, optimizing for fewer that ten =-identified elements, trusting that the source guarantees uniqueness. If we ensure that all CollectionValues are used by known Variables, we can detect singly used Collections to allow mutation. If a mutation occurs to a shared Collection we can create an Array-based copy for the immutable collection, rather than yet another ArrayList/HashSet-based copy for the mutable. LazyCollections such as an AsEcoreCollection can perform to-ecore conversion on the fly avoiding the need to reify both old and new forms. Cascades of such conversions may be optimized to remove redundancy. Knowledge of multiple / single client Variables may determine whether the on-the-fly computation should be cached in an array for re-use by a further variable. The above are all helpful to OCL used in bulk but much more valuable to QVT where static analysis can help with many of the usage style guarantees.
(In reply to Ed Willink from comment #4) > A smart accumulator is a localized solution. A lazy collection can do more. Not necessarily Sequence{1..10000}->iterate(i : Integer; acc : Sequence(Integer) = Sequence{1, 1} | acc->including(acc->at(i) + acc->at(i+1)))->last() could result in a daisy chain of 10,000 lazy IncludingIterators that might be eagerly evaluated for an operation such as at().No real benefit. Perhaps the lazy structure provides an opportunity to reflect on the evaluation before performing it. Need some way to do this for a wide variety of evaluations rather than just a growing list of peepholes.
(In reply to Ed Willink from comment #5) > Not necessarily > > Sequence{1..10000}->iterate(i : Integer; acc : Sequence(Integer) = > Sequence{1, 1} | acc->including(acc->at(i) + acc->at(i+1)))->last() The daisy of chain of iterators hazard can be avoided by recognizing that the final access of a variable such as "acc" can be replaced by an eager mutable operation i.e. rewrite acc->including(acc->at(i) + acc->at(i+1)) as: acc->mutableIncluding(acc->at(i) + acc->at(i+1)) This rewrite could be localized in FeatureCallExp if there was a FeatureCallExp.implementation to resolve redirect, but currently there is only Feature.implementation (no site-specific choices). Until Bug 341944 makes that possible: ? modify OperationCallExp.referredOperation - needs a target Operation ? only support the CG equivalent ? have a Map lookup in visitOperationCallExp ? have a context awareness in Including.Instance
(In reply to Ed Willink from comment #6) > Until Bug 341944 makes that possible: Correction Bug 509309 Does addition of a transient require a major version change. ? add the transient as manual rather than genmodelled code ? --- If we have the transient, how is it populated? 'mutable' may not be the only context sensitive optimization. ? a LibraryFeature has an optional Strategy which if not null may have an extensible API to determine context sensitive alternatives.