< Summary

Information
Class: Pozitron.QuerySpecification.LikeMemoryEvaluator
Assembly: Pozitron.QuerySpecification
File(s): /home/runner/work/QuerySpecification/QuerySpecification/src/QuerySpecification/Evaluators/LikeMemoryEvaluator.cs
Tag: 67_15587897385
Line coverage
100%
Covered lines: 44
Uncovered lines: 0
Coverable lines: 44
Total lines: 141
Line coverage: 100%
Branch coverage
100%
Covered branches: 32
Total branches: 32
Branch coverage: 100%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
.cctor()100%11100%
.ctor()100%11100%
Evaluate(...)100%66100%
.ctor(...)100%11100%
Clone()100%11100%
Dispose()100%22100%
MoveNext()100%88100%
IsValid(...)100%88100%
IsValidInOrGroup(T,System.ReadOnlySpan`1<Pozitron.QuerySpecification.SpecItem>)100%88100%

File(s)

/home/runner/work/QuerySpecification/QuerySpecification/src/QuerySpecification/Evaluators/LikeMemoryEvaluator.cs

#LineLine coverage
 1using System.Diagnostics;
 2
 3namespace Pozitron.QuerySpecification;
 4
 5/*
 6    public IEnumerable<T> Evaluate<T>(IEnumerable<T> source, Specification<T> specification)
 7    {
 8        foreach (var likeGroup in specification.LikeExpressions.GroupBy(x => x.Group))
 9        {
 10            source = source.Where(x => likeGroup.Any(c => c.KeySelectorFunc(x)?.Like(c.Pattern) ?? false));
 11        }
 12        return source;
 13    }
 14    This was the previous implementation. We're trying to avoid allocations of LikeExpressions, GroupBy and LINQ.
 15    The new implementation preserves the behavior and reduces allocations drastically.
 16    We've implemented a custom iterator. Also, instead of GroupBy, we have a single array sorted by group, and we slice 
 17    For source of 1000 items, the allocations are reduced from 257.872 bytes to only 64 bytes (the cost of the iterator 
 18 */
 19
 20/// <summary>
 21/// Represents an in-memory evaluator for "Like" expressions.
 22/// </summary>
 23[EvaluatorDiscovery(Order = 20)]
 24public sealed class LikeMemoryEvaluator : IMemoryEvaluator
 25{
 26    /// <summary>
 27    /// Gets the singleton instance of the <see cref="LikeMemoryEvaluator"/> class.
 28    /// </summary>
 229    public static LikeMemoryEvaluator Instance = new();
 430    private LikeMemoryEvaluator() { }
 31
 32    /// <inheritdoc/>
 33    public IEnumerable<T> Evaluate<T>(IEnumerable<T> source, Specification<T> specification)
 34    {
 1735        var compiledItems = specification.GetCompiledItems();
 1836        if (compiledItems.Length == 0) return source;
 37
 4438        var startIndexLikeItems = Array.FindIndex(compiledItems, item => item.Type == ItemType.Like);
 2339        if (startIndexLikeItems == -1) return source;
 40
 41        // The like items are contiguously placed as a last segment in the array and are already sorted by group.
 942        return new SpecLikeIterator<T>(source, compiledItems, startIndexLikeItems);
 43    }
 44
 45    private sealed class SpecLikeIterator<TSource> : Iterator<TSource>
 46    {
 47        private readonly IEnumerable<TSource> _source;
 48        private readonly SpecItem[] _compiledItems;
 49        private readonly int _startIndex;
 50
 51        private IEnumerator<TSource>? _enumerator;
 52
 1053        public SpecLikeIterator(IEnumerable<TSource> source, SpecItem[] compiledItems, int startIndex)
 54        {
 55            Debug.Assert(source != null);
 56            Debug.Assert(compiledItems != null);
 1057            _source = source;
 1058            _compiledItems = compiledItems;
 1059            _startIndex = startIndex;
 1060        }
 61
 62        public override Iterator<TSource> Clone()
 163            => new SpecLikeIterator<TSource>(_source, _compiledItems, _startIndex);
 64
 65        public override void Dispose()
 66        {
 2067            if (_enumerator is not null)
 68            {
 1069                _enumerator.Dispose();
 1070                _enumerator = null;
 71            }
 2072            base.Dispose();
 2073        }
 74
 75        public override bool MoveNext()
 76        {
 3577            switch (_state)
 78            {
 79                case 1:
 1080                    _enumerator = _source.GetEnumerator();
 1081                    _state = 2;
 82                    goto case 2;
 83                case 2:
 84                    Debug.Assert(_enumerator is not null);
 3585                    var likeItems = _compiledItems.AsSpan()[_startIndex.._compiledItems.Length];
 4586                    while (_enumerator.MoveNext())
 87                    {
 3588                        TSource sourceItem = _enumerator.Current;
 3589                        if (IsValid(sourceItem, likeItems))
 90                        {
 2591                            _current = sourceItem;
 2592                            return true;
 93                        }
 94                    }
 95
 1096                    Dispose();
 97                    break;
 98            }
 99
 10100            return false;
 101        }
 102
 103        private static bool IsValid<T>(T sourceItem, ReadOnlySpan<SpecItem> span)
 104        {
 35105            var valid = true;
 35106            var groupStart = 0;
 107
 150108            for (var i = 1; i <= span.Length; i++)
 109            {
 110                // If we reached the end of the span or the group has changed, we slice and process the group.
 50111                if (i == span.Length || span[i].Bag != span[groupStart].Bag)
 112                {
 39113                    var validOrGroup = IsValidInOrGroup(sourceItem, span[groupStart..i]);
 39114                    if ((valid = valid && validOrGroup) is false)
 115                    {
 116                        break;
 117                    }
 29118                    groupStart = i;
 119                }
 120            }
 121
 35122            return valid;
 123
 124            static bool IsValidInOrGroup(T sourceItem, ReadOnlySpan<SpecItem> span)
 125            {
 39126                var validOrGroup = false;
 143127                foreach (var specItem in span)
 128                {
 47129                    if (specItem.Reference is not SpecLikeCompiled<T> specLike) continue;
 130
 47131                    if (specLike.KeySelector(sourceItem)?.Like(specLike.Pattern) ?? false)
 132                    {
 29133                        validOrGroup = true;
 29134                        break;
 135                    }
 136                }
 39137                return validOrGroup;
 138            }
 139        }
 140    }
 141}