-
-
Notifications
You must be signed in to change notification settings - Fork 36
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
Support non-string keys (eg. tuples) #173
Comments
That's an interesting benchmark and I know these are more C# than CacheTower, but I'm curious:
|
I actually have a mistake in that benchmark. I was trying a few different things out and wrote the optimized benchmark with the non-optimized results. It actually allocates far less if I have the dictionary use the key I'll put together a few more benchmarks in more comments below. |
Strings vs Tuple Benchmark: Writing[SimpleJob(RuntimeMoniker.NetCoreApp50), MemoryDiagnoser]
public class TestBenchmark
{
private Dictionary<string, int> DataA {get;set;} = new();
private Dictionary<(string, int), int> DataB {get;set;} = new();
private string BaseString;
[GlobalSetup]
public void Setup()
{
BaseString = "hello";
}
[Benchmark]
public void NoTuple()
{
DataA.Clear();
for (var i = 0; i < 10000; i++)
{
DataA.Add($"hello:{i}", i);
}
}
[Benchmark]
public void Tuple()
{
DataB.Clear();
for (var i = 0; i < 10000; i++)
{
DataB.Add((BaseString, i), i);
}
}
}
|
Strings vs Tuple Benchmark: Reading[SimpleJob(RuntimeMoniker.NetCoreApp50), MemoryDiagnoser]
public class TestBenchmark
{
private Dictionary<string, int> DataA {get;set;} = new();
private Dictionary<(string, int), int> DataB {get;set;} = new();
private string BaseString;
private int BaseInt;
[GlobalSetup]
public void Setup()
{
BaseString = "hello";
BaseInt = 6485;
for (var i = 0; i < 10000; i++)
{
DataA.Add($"hello:{i}", i);
}
for (var i = 0; i < 10000; i++)
{
DataB.Add((BaseString, i), i);
}
}
[Benchmark]
public int NoTuple()
{
return DataA[$"hello:{BaseInt}"];
}
[Benchmark]
public int Tuple()
{
return DataB[(BaseString, BaseInt)];
}
}
|
String vs Tuple Benchmark: Lookup Only[SimpleJob(RuntimeMoniker.NetCoreApp50), MemoryDiagnoser]
public class TestBenchmark
{
private Dictionary<string, int> DataA {get;set;} = new();
private Dictionary<(string, int), int> DataB {get;set;} = new();
private string BaseString;
private int BaseInt;
private string NoTupleKey;
private (string, int) TupleKey;
[GlobalSetup]
public void Setup()
{
BaseString = "hello";
BaseInt = 6485;
NoTupleKey = $"hello:{BaseInt}";
TupleKey = (BaseString, BaseInt);
for (var i = 0; i < 10000; i++)
{
DataA.Add($"hello:{i}", i);
}
for (var i = 0; i < 10000; i++)
{
DataB.Add((BaseString, i), i);
}
}
[Benchmark]
public int NoTuple()
{
return DataA[NoTupleKey];
}
[Benchmark]
public int Tuple()
{
return DataB[TupleKey];
}
}
|
So the lookup itself is quite a bit slower in the test vs strings however that time is counteracted by the saving of creating the string vs creating the tuple. |
I did discover one interesting thing - if I make the second dictionary use key of
It performs slightly worse when it is a object-type vs string when the key is actually a string but the allocations are the same. But still with the same object-type as the key but using a tuple instead, we get the results I have in the issue description:
So if a |
What problem does the feature solve?
Currently all keys must be of type
string
. This works fine but can cause unnecessary allocations, especially on data retrieval, when one would combine values together to form a string - eg.$"namespace:{someInteger}:{someOtherValue}"
.They could instead use a Tuple like
("namespace", someInteger, someOtherValue)
which would avoid allocations and overall increase performance.Here is a basic benchmark of two dictionaries - one with a tuple and one without:
The thing is, we can't easily use a tuple internally. If we said "all keys are tuples", we could use the
ITuple
interface however that creates issues with boxing. It would still save on allocations but not as many.The thing is we would ideally need a generic argument for keys but defined for a
CacheStack
. This starts working against us again because while you might have a key like(string, int, int)
in one place, you might have another being(string, int)
,Guid
or anything else. We would hit boxing issues again if we wanted to support all variations at all times.Many things would need to be worked out but in the mean time, this would be a good placeholder issue.
How would you use/interact with the feature? (if applicable)
Something like:
A
CacheStack
might need to be defined asCacheStack<TKey>
andCacheStack<TKey, TContext>
.Additional constraints include:
The text was updated successfully, but these errors were encountered: