Skip to content

Conversation

akarnokd
Copy link
Collaborator

This PR adds dedicated, lock-free implementation for Amb(params IObservable<T>[] sources) and Amb(this IEnumerable<IObservable<T>> sources).

The original internal implementation was chaining the two-argument Ambs which was both wasteful and possibly prone to stack overflow due to the nesting.

var n = 0;
var sources = new IObservable<T>[8];

try
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just use ToList or ToArray here.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This basically avoids the final copy in ToArray to the actual size as the internal expanding array will have slack otherwise. We can simply use n to limit the evaluation of the array here.

Copy link
Collaborator

@danielcweber danielcweber May 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No please, slack is just fine, this is not the place to save bytes, we're possibly re-allocating and copying here more often than needed, the Linq-internal ToArray does the trick just fine with a good resize-strategy.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, use ToList then. Should do the same without extra copy.

Volatile.Write(ref winner, -1);
}

internal static IDisposable Create(IObserver<T> observer, IObservable<T>[] sources, int n)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

n is pretty much redundant, right?

return parent;
}

internal void Subscribe(IObservable<T>[] sources, int n)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use observers.Length for n


public void Dispose()
{
var o = observers;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you not use those intermediate values, observers is readonly so this might be optimized by JIT, and o.Length is constant since o is an array.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know the exact rules but even if they are readonly fields, it is possible the compiler would re-read the fields around those Volatile accesses all the time. This way, there is practically no chance the compiler inlines the read of the field back into all those places thus causing the aforementioned re-reads.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am pretty sure it won't since it's all constant anyway. Anyway, it's just a lot of noise.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is part of the optimized logic which also prevents bad decisions on the JIT's part, which I came to not expect much of right now.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What makes you think the JIT makes bad decisions?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like I can't convince you about this. I'll update the PR.

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I haven't done a lot of these micro-optimizations, and there might be good reasons to do this, but I have learned not to try to outsmart the JIT, because it usually does a pretty good job, and there is always a tradeoff wrt. readability, which is one of my concerns also. Plus, we want to get a lot of people involved in this, and not everybody can write that kind of code.

}
catch (Exception ex)
{
observer.OnError(ex);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Won't this be handled by SafeObserver?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think there is a try-catch guarding the IProducer.Run method. It would likely crash out of the Subscribe method and count as a badly implemented IObservable:

https://github.com/dotnet/reactive/blob/master/Rx.NET/Source/src/System.Reactive/Internal/Producer.cs#L59

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that's an issue, we should discuss that separately for all operators and put it in a central place because no other operator guards the Run like this.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

They do: https://github.com/dotnet/reactive/blob/master/Rx.NET/Source/src/System.Reactive/Linq/Observable/Defer.cs#L43

This nothing new and is there in operators that call delegates in their Subscribe/Run methods. Nothing to worry about.

Copy link
Collaborator

@danielcweber danielcweber May 29, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ok, because you're enumerating? Fine for now I guess, but if it's a pattern, it should be....well, a pattern.

@danielcweber danielcweber merged commit 68acb4b into dotnet:master May 29, 2018
@akarnokd akarnokd deleted the AmbMany branch May 30, 2018 13:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants