< Summary

Information
Class: Pozitron.QuerySpecification.LikeMemoryEvaluator
Assembly: Pozitron.QuerySpecification
File(s): /home/runner/work/QuerySpecification/QuerySpecification/src/QuerySpecification/Evaluators/LikeMemoryEvaluator.cs
Tag: 74_17335419374
Line coverage
100%
Covered lines: 41
Uncovered lines: 0
Coverable lines: 41
Total lines: 135
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    {
 1835        var compiledItems = specification.GetCompiledItems();
 1936        if (compiledItems.Length == 0) return source;
 37
 4638        var startIndexLikeItems = Array.FindIndex(compiledItems, item => item.Type == ItemType.Like);
 2439        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.
 1042        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
 1253        public SpecLikeIterator(IEnumerable<TSource> source, SpecItem[] compiledItems, int startIndex)
 54        {
 55            Debug.Assert(source != null);
 56            Debug.Assert(compiledItems != null);
 1257            _source = source;
 1258            _compiledItems = compiledItems;
 1259            _startIndex = startIndex;
 1260        }
 61
 62        public override Iterator<TSource> Clone()
 263            => new SpecLikeIterator<TSource>(_source, _compiledItems, _startIndex);
 64
 65        public override void Dispose()
 66        {
 2467            if (_enumerator is not null)
 68            {
 1269                _enumerator.Dispose();
 1270                _enumerator = null;
 71            }
 2472            base.Dispose();
 2473        }
 74
 75        public override bool MoveNext()
 76        {
 3977            switch (_state)
 78            {
 79                case 1:
 1280                    _enumerator = _source.GetEnumerator();
 1281                    _state = 2;
 82                    goto case 2;
 83                case 2:
 84                    Debug.Assert(_enumerator is not null);
 3985                    var likeItems = _compiledItems.AsSpan()[_startIndex.._compiledItems.Length];
 5586                    while (_enumerator.MoveNext())
 87                    {
 4388                        TSource sourceItem = _enumerator.Current;
 4389                        if (IsValid(sourceItem, likeItems))
 90                        {
 2791                            _current = sourceItem;
 2792                            return true;
 93                        }
 94                    }
 95
 1296                    Dispose();
 97                    break;
 98            }
 99
 12100            return false;
 101        }
 102
 103        private static bool IsValid<T>(T sourceItem, ReadOnlySpan<SpecItem> span)
 104        {
 43105            var groupStart = 0;
 170106            for (var i = 1; i <= span.Length; i++)
 107            {
 108                // If we reached the end of the span or the group has changed, we slice and process the group.
 58109                if (i == span.Length || span[i].Bag != span[groupStart].Bag)
 110                {
 47111                    if (IsValidInOrGroup(sourceItem, span[groupStart..i]) is false)
 112                    {
 16113                        return false;
 114                    }
 31115                    groupStart = i;
 116                }
 117            }
 27118            return true;
 119
 120            static bool IsValidInOrGroup(T sourceItem, ReadOnlySpan<SpecItem> span)
 121            {
 173122                foreach (var specItem in span)
 123                {
 55124                    if (specItem.Reference is not SpecLikeCompiled<T> specLike) continue;
 125
 55126                    if (specLike.KeySelector(sourceItem)?.Like(specLike.Pattern) ?? false)
 127                    {
 31128                        return true;
 129                    }
 130                }
 16131                return false;
 132            }
 133        }
 134    }
 135}