-
Notifications
You must be signed in to change notification settings - Fork 94
Conversation
You probably should call this optimize.cs - since it isn't the simple case anymore (see the comment on to close pull request #24). |
Yep, this is 15% faster for me too, thanks. However, I don't want to optimize the |
Sure, to be honest I totally expected this, so I'll do almost that :) |
… build instead of mono
I've made the mentioned above changes. |
Thanks! I'll see if I can get .net going on Linux. |
Hope this helps: https://docs.microsoft.com/en-us/dotnet/core/install/linux |
Hi, I guess we need to consolidate. I was thinking we could do something like techempower benchmarks, but simpler. |
@erikbozic, I'm glad to see whatever structure will emerge. |
By going away you're referring to .NET 6? I guess you're right. |
Yes, I did mean that .NET 6 will consume it. And mono is a weak "showcase" of modern .net potential, anyway. |
Thank you -- just downloaded .NET for Linux (it was a "snap" :-). |
|
||
class Program | ||
{ | ||
public sealed class Ref<T> { |
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.
Small note: The BCL supplies StrongBox<T>
(under System.Runtime.CompilerServices
) that fulfils the exact same purpose as this type.
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.
TIL, thanks!
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.
What is the purpose of having wrapped the integer anyway? My initial thought was that this would actually make the program less performant, because a reference type lives on the heap, whereas an int
lives on the stack. Clearly there must be something I'm missing 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.
@calledude it's a reference type, so we don't copy a new int every time we get it out of the dictionary - but instead increment the existing one. If you try it without you will see all counts are just 1.
In a more general sense: we're storing a pointer to integer instead of only the integer itself.
Otherwise we would need to get it out of the dictionary, increment it and then set the new incremented value back.
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.
What about a ref struct
then? This would force it to still live on the stack and we would have a pointer to it, perhaps that would make it even more performant? I assume there's a tradeoff in the current solution where having it on the stack does not outweigh the cost of copying the value all the time?
Edit; Nvm, ref structs can't be used as generic type arguments.
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.
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.
FWIW; According to BenchmarkDotNet
int
is still faster.
@calledude, I'm not sure what exactly you are measuring there, but if you simply removed wrapping object and still use the rest of the optimized code as-is (that is, using TryGetValue
), your faster code does not work correctly. As @erikbozic already pointed out - all your counters will never increment and always stay at 1.
Yes, extra object indirection adds overhead, but this code is faster than code in "simple" without it exactly because code without it will have to do hash lookup twice: first, to read value and then to store it back again. That is because current Dictionary API does not support atomic increment or in-place value modification.
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.
As for you question on ref struct
- the whole Dictionary lives on the heap and anything you store there has to be boxed and put on a heap, which ref struct
cannot be.
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.
Just double checked and I could've sworn I had made sure I was actually incrementing the value correctly, but I guess not then.
Not my proudest moment... :) Sorry about that!
string line; | ||
while ((line = Console.ReadLine()) != null) | ||
{ | ||
line = line.ToLower(); |
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.
Could save this potential string allocation by instantiating the dictionary with one of the *IgnoreCase
StringComparers. Additionally, that would show the explicit choice of if the algorithm is culture-aware or not.
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.
To be honest I intentionally did not include any of those optimizations that were plentiful in the many parallel PRs :)
Please note that there should be a better API to avoid double key lookups in a dictictionary hopefully coming some time later.
Until then, this PR removes second lookup for existing keys by wrapping counter in a reference cell, shaving off 15-20% on my machine (on .net5).