-
Notifications
You must be signed in to change notification settings - Fork 5.1k
Set collection capacity where information is available or for empty receives return unchangeable list #20571
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Thank you for your contribution @danielmarbach! We will review the pull request and get back to you soon. |
sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpReceiver.cs
Outdated
Show resolved
Hide resolved
…eceives return unchangeable list
2c0c5a0 to
908cc19
Compare
|
|
||
| return list.Cast<TValue>(); | ||
| List<TValue> values = null; | ||
| foreach (var item in list) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I wonder, do we save enough with this change that the intermediary list instantiation is worth it to get the count? We could do a yield return here and use an out param for the length, I think....
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We actually we would save something by changing the return type to the list and then there would be no enumerator allocation anymore. I initially wanted to go with that but then I remembered your enumerable all the things rule so I dismissed it.
But yeah yield return and counter would work too but still allocate the enumerator right?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it's safe to return this as a list-type here, as it's an internal class. We try to keep the public API as generic as we can to avoid boxing ourselves in with too-specific a contract. Nothing breaks if we try to change this later.
@JoshLove-msft: Any objections to potentially changing the return type here?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We could do a yield return here and use an out param for the length, I think....
iterators cannot have out parameters unfortunately
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did a quick benchmark
[Config(typeof(Config))]
public class EnumerableCount
{
private List<object> data;
private Consumer consumer;
private class Config : ManualConfig
{
public Config()
{
AddExporter(MarkdownExporter.GitHub);
AddDiagnoser(MemoryDiagnoser.Default);
AddJob(Job.Default.WithInvocationCount(1000000));
}
}
[IterationSetup]
public void Setup()
{
data = Enumerable.Repeat(new SomeClass(), NumberOfItems).Cast<object>().ToList();
consumer = new Consumer();
}
[Params(0, 1, 5)]
public int NumberOfItems { get; set; }
class SomeClass {}
[Benchmark(Baseline = true)]
public void Original()
{
GetValue<SomeClass>().Consume(consumer);
}
IEnumerable<TValue> GetValue<TValue>()
{
return data.Cast<TValue>();
}
[Benchmark]
public void List()
{
GetValueList<SomeClass>().Consume(consumer);
}
IEnumerable<TValue> GetValueList<TValue>()
{
List<TValue> values = null;
foreach (var item in data)
{
values ??= new List<TValue>(data.Count);
values.Add((TValue)item);
}
return values ?? Enumerable.Empty<TValue>();
}
[Benchmark]
public void ListReturnList()
{
GetValueListReturnList<SomeClass>().Consume(consumer);
}
List<TValue> GetValueListReturnList<TValue>()
{
var values = new List<TValue>(data.Count);
foreach (var item in data)
{
values.Add((TValue)item);
}
return values;
}
}
BenchmarkDotNet=v0.12.1, OS=Windows 10.0.19042
AMD Ryzen 9 3950X, 1 CPU, 32 logical and 16 physical cores
.NET Core SDK=5.0.202
[Host] : .NET Core 3.1.14 (CoreCLR 4.700.21.16201, CoreFX 4.700.21.16208), X64 RyuJIT
Job-UZLRLE : .NET Core 3.1.14 (CoreCLR 4.700.21.16201, CoreFX 4.700.21.16208), X64 RyuJIT
InvocationCount=1000000
| Method | NumberOfItems | Mean | Error | StdDev | Ratio | RatioSD | Gen 0 | Gen 1 | Gen 2 | Allocated |
|---|---|---|---|---|---|---|---|---|---|---|
| Original | 0 | 117.15 ns | 0.907 ns | 0.757 ns | 1.00 | 0.00 | 0.0110 | - | - | 96 B |
| List | 0 | 17.74 ns | 0.226 ns | 0.278 ns | 0.15 | 0.00 | - | - | - | - |
| ListReturnList | 0 | 32.90 ns | 0.561 ns | 0.623 ns | 0.28 | 0.01 | 0.0080 | - | - | 72 B |
| Original | 1 | 150.39 ns | 1.639 ns | 1.533 ns | 1.00 | 0.00 | 0.0110 | - | - | 96 B |
| List | 1 | 56.27 ns | 1.150 ns | 1.412 ns | 0.38 | 0.01 | 0.0120 | - | - | 104 B |
| ListReturnList | 1 | 51.97 ns | 0.755 ns | 0.706 ns | 0.35 | 0.00 | 0.0120 | - | - | 104 B |
| Original | 5 | 241.15 ns | 1.653 ns | 1.466 ns | 1.00 | 0.00 | 0.0110 | - | - | 96 B |
| List | 5 | 140.63 ns | 1.980 ns | 1.852 ns | 0.58 | 0.01 | 0.0160 | - | - | 136 B |
| ListReturnList | 5 | 141.84 ns | 2.861 ns | 5.443 ns | 0.56 | 0.02 | 0.0160 | - | - | 136 B |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Boah with outer loop for
[Config(typeof(Config))]
public class EnumerableCount
{
private List<object> data;
private class Config : ManualConfig
{
public Config()
{
AddExporter(MarkdownExporter.GitHub);
AddDiagnoser(MemoryDiagnoser.Default);
AddJob(Job.Default.WithInvocationCount(1600));
}
}
[IterationSetup]
public void Setup()
{
data = Enumerable.Repeat(new SomeClass(), NumberOfItems).Cast<object>().ToList();
}
[Params(0, 1, 10, 32, 50, 100, 1000)]
public int NumberOfItems { get; set; }
public class SomeClass {}
[Benchmark(Baseline = true)]
public List<SomeClass> Original()
{
var copyList = new List<SomeClass>();
foreach (var value in GetValue<SomeClass>())
{
copyList.Add(value);
}
return copyList;
}
IEnumerable<TValue> GetValue<TValue>()
{
return data.Cast<TValue>();
}
[Benchmark]
public List<SomeClass> List()
{
List<SomeClass> copyList = null;
var enumerable = GetValueList<SomeClass>();
foreach (var value in enumerable)
{
copyList ??= enumerable is IReadOnlyCollection<SomeClass> readOnlyList
? new List<SomeClass>(readOnlyList.Count)
: new List<SomeClass>();
copyList.Add(value);
}
return copyList;
}
IEnumerable<TValue> GetValueList<TValue>()
{
List<TValue> values = null;
foreach (var item in data)
{
values ??= new List<TValue>(data.Count);
values.Add((TValue)item);
}
return values ?? Enumerable.Empty<TValue>();
}
[Benchmark]
public List<SomeClass> ListReturnList()
{
List<SomeClass> copyList = null;
var enumerable = GetValueListReturnList<SomeClass>();
foreach (var value in enumerable)
{
copyList ??= enumerable is IReadOnlyCollection<SomeClass> readOnlyList
? new List<SomeClass>(readOnlyList.Count)
: new List<SomeClass>();
copyList.Add(value);
}
return copyList;
}
List<TValue> GetValueListReturnList<TValue>()
{
var values = new List<TValue>(data.Count);
foreach (var item in data)
{
values.Add((TValue)item);
}
return values;
}
[Benchmark]
public List<SomeClass> ListReturnListFor()
{
List<SomeClass> copyList = null;
var enumerable = GetValueListReturnListFor<SomeClass>();
foreach (var value in enumerable)
{
copyList ??= enumerable is IReadOnlyCollection<SomeClass> readOnlyList
? new List<SomeClass>(readOnlyList.Count)
: new List<SomeClass>();
copyList.Add(value);
}
return copyList;
}
[Benchmark]
public List<SomeClass> ListForReturnListFor()
{
List<SomeClass> copyList = null;
var enumerable = GetValueListReturnListFor<SomeClass>();
for (var index = 0; index < enumerable.Count; index++)
{
var value = enumerable[index];
copyList ??= enumerable is IReadOnlyCollection<SomeClass> readOnlyList
? new List<SomeClass>(readOnlyList.Count)
: new List<SomeClass>();
copyList.Add(value);
}
return copyList;
}
List<TValue> GetValueListReturnListFor<TValue>()
{
var values = new List<TValue>(data.Count);
for (var index = 0; index < data.Count; index++)
{
var item = data[index];
values.Add((TValue) item);
}
return values;
}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm somewhat ambivalent on the foreach -> for change. It is nice to squeeze out more perf but I feel like we should be able to rely on language constructs and count on them to improve in perf over time. I am fine with either way here, but in general I don't think we should replace all of our foreach usages.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For is a language construct ;) I'm aware that similar changes have been made in ASP.NET Core and the runtime as well in some places. Anyway I don't want to argue here I'm happy to go with the flow whatever you and the team prefer. I'm signing off now, so you can always edit the PR before merging
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah I mean to say that it would be unfortunate if we couldn't use constructs as fundamental as foreach because of perf issues. I'm okay with the for loop here but mostly just wanted to call this out as a potential point of discussion. As always, thank you so much for the awesome contribution @danielmarbach! We greatly appreciate the time and effort you have put into improving the SDK.
sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpReceiver.cs
Outdated
Show resolved
Hide resolved
sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpReceiver.cs
Outdated
Show resolved
Hide resolved
sdk/servicebus/Azure.Messaging.ServiceBus/src/Amqp/AmqpReceiver.cs
Outdated
Show resolved
Hide resolved
|
/azp run net - servicebus - tests |
|
Azure Pipelines successfully started running 1 pipeline(s). |
|
Hmmm why would this fail suddenly? |
Looks like it was just a transient failure in one of the tests. |
|
🎆 |


Alternative to #20550
All SDK Contribution checklist:
This checklist is used to make sure that common guidelines for a pull request are followed.
Draftmode if it is:General Guidelines and Best Practices
Testing Guidelines
SDK Generation Guidelines
*.csprojandAssemblyInfo.csfiles have been updated with the new version of the SDK. Please double check nuget.org current release version.Additional management plane SDK specific contribution checklist:
Note: Only applies to
Microsoft.Azure.Management.[RP]orAzure.ResourceManager.[RP]Management plane SDK Troubleshooting
If this is very first SDK for a services and you are adding new service folders directly under /SDK, please add
new servicelabel and/or contact assigned reviewer.If the check fails at the
Verify Code Generationstep, please ensure:generate.ps1/cmdto generate this PR instead of callingautorestdirectly.Please pay attention to the @microsoft.csharp version output after running
generate.ps1. If it is lower than current released version (2.3.82), please run it again as it should pull down the latest version.Note: We have recently updated the PSH module called by
generate.ps1to emit additional data. This would help reduce/eliminate the Code Verification check error. Please run following command:Old outstanding PR cleanup
Please note:
If PRs (including draft) has been out for more than 60 days and there are no responses from our query or followups, they will be closed to maintain a concise list for our reviewers.