NMFCode / NMF

This repository contains the entire code for the .NET Modeling Framework
BSD 3-Clause "New" or "Revised" License
36 stars 15 forks source link

GC allocations accessing INotifyEnumerable #42

Closed friuns2 closed 5 years ago

friuns2 commented 6 years ago

Is it possible to make zero allocations when reading collection? some wrapper like BufferedCollection that would be updated when collection changed

image

georghinkel commented 6 years ago

From the stack trace that you posted, you are using the non-incremental interface. This is just a wrapper around traditional Linq to Objects. You can achieve few allocations when reading the collection if you switch to an incremental mode. Zero allocations is usually not possible as soon as you start iterating a collection because you need to create an iterator.

Show me your query (or a broken down version of it) and I can explain you in a bit more detail how things work.

friuns2 commented 6 years ago

i think its incremental mode there is ObserableExtensions.FirstOrDefault() called and its using way less resources compared to linq



public class Test2 : MonoBehaviour
{

    ObservableCollection<Test> cc = new ObservableCollection<Test>();

    void Start()
    {
        for (int i = 0; i < 1000; i++)
            cc.Add(new Test { a = Random.value });
        INotifyCollection<Test> dd = cc.WithUpdates();
        ff = dd.OrderByDescending(a => a.a);
    }
    IOrderableNotifyEnumerable<Test> ff;
    void Update()
    {
        ff.FirstOrDefault();
    }

}

[Serializable]
public class Test : INotifyPropertyChanged
{
    public float a;
    public void SetA(float a)
    {
        this.a = a;
        OnPropertyChanged(nameof(a));
    }

    public event PropertyChangedEventHandler PropertyChanged;
    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string PropertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(PropertyName));
    }
    public override string ToString()
    {
        return a.ToString();
    }
}
georghinkel commented 6 years ago

Actually, FirstOrDefault is executed in batch mode, but OrderByDescending is executed in incremental mode. That is, if you execute FirstOrDefault, it computes the value every time, but based on cached collection of OrderByDescending, which simply uses a SortedDictionary internally. So unlike Linq to Objects, you would not sort your collection all over again, but maintain a binary search tree. The reason you see a SelectManySingleSelectorIterator in your stack trace is that that sorted dictionary stores a list of values (because multiple elements could have the same key).

georghinkel commented 6 years ago

So essentially, this sorted dictionary already is a buffered collection, but with a little bit of structure by the keys that you use for sorting. I don't see how this can be omitted.