È più veloce?
Venendo da un punto di vista gamedev, se la tua chiave è un tipo di valore (struct, primitiva, enum, ecc) fornire il proprio EqualityComparer<T>
è significativamente più veloce - a causa del fatto i EqualityComparer<T>.Default
caselle il valore.
Come esempio del mondo reale, l'esempio del tabellone per le affissioni Direct DirectX veniva eseguito a circa il 30% della velocità della versione C++; dove tutti gli altri campioni erano in esecuzione a ~ 90%. La ragione di ciò era che i tabelloni pubblicitari venivano ordinati usando il comparatore di default (e quindi in box), in quanto risulta che 4MB di dati venivano copiati su ogni frame grazie a questo.
Come funziona?
Dictionary<K,V>
fornirà EqualityComparer<T>.Default
a se stesso tramite il costruttore di default. Quello che il confronto uguaglianze predefinito fa è (in pratica, notare quanto la boxe si verifica):
public void GetHashCode(T value)
{
return ((object)value).GetHashCode();
}
public void Equals(T first, T second)
{
return ((object)first).Equals((object)second);
}
Perché mai dovrei usarlo?
E 'abbastanza comune vedere questo tipo di codice (quando si cerca di avere le chiavi case-insensitive):
var dict = new Dictionary<string, int>();
dict.Add(myParam.ToUpperInvariant(), fooParam);
// ...
var val = dict[myParam.ToUpperInvariant()];
Questo è davvero uno spreco, è meglio utilizzare solo uno StringComparer sul costruttore:
var dict = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
È più veloce (redux)?
In questo scenario specifico è molto più veloce, perché i confronti di stringhe ordinali sono il tipo di confronto di stringhe più veloce che si possa fare. Un rapido riferimento:
static void Main(string[] args)
{
var d1 = new Dictionary<string, int>();
var d2 = new Dictionary<string, int>(StringComparer.OrdinalIgnoreCase);
d1.Add("FOO", 1);
d2.Add("FOO", 1);
Stopwatch s = new Stopwatch();
s.Start();
RunTest1(d1, "foo");
s.Stop();
Console.WriteLine("ToUpperInvariant: {0}", s.Elapsed);
s.Reset();
s.Start();
RunTest2(d2, "foo");
s.Stop();
Console.WriteLine("OrdinalIgnoreCase: {0}", s.Elapsed);
Console.ReadLine();
}
static void RunTest1(Dictionary<string, int> values, string val)
{
for (var i = 0; i < 10000000; i++)
{
values[val.ToUpperInvariant()] = values[val.ToUpperInvariant()];
}
}
static void RunTest2(Dictionary<string, int> values, string val)
{
for (var i = 0; i < 10000000; i++)
{
values[val] = values[val];
}
}
// ToUpperInvariant: 00:00:04.5084119
// OrdinalIgnoreCase: 00:00:02.1211549
// 2x faster.
prenotazioni
È possibile eliminare l'overhead boxe implementando un'interfaccia su una struttura (ad esempio IEquatable<T>
). Tuttavia, ci sono molte regole sorprendenti per quando il pugilato si verifica in queste circostanze, quindi consiglio di utilizzare l'interfaccia accoppiata (ad esempio IEqualityComparer<T>
in questo caso) se possibile.
Ottima risposta, grazie :) –
Ottima risposta, ma penso che dovresti aver menzionato 'EqualityComparer .Default' controlla prima se il tipo implementa' IEquatable 'e, in caso affermativo, utilizza il comando implementazione; il che significa che non devi fornire un comparatore personalizzato solo per evitare la boxe se il tuo tipo di valore implementa l'interfaccia 'IEquatable '. –
@ ŞafakGür utilizzando le interfacce per accedere ai tipi di valore li inserirà: http://stackoverflow.com/questions/7995606/boxing-occurrence-in-c-sharp –