Ho un elenco di 500000 Tuple<long,long,string>
oggetti generati casualmente su cui sto eseguendo una semplice ricerca "tra":
var data = new List<Tuple<long,long,string>>(500000);
...
var cnt = data.Count(t => t.Item1 <= x && t.Item2 >= x);
Quando generi il mio array casuale ed eseguo la mia ricerca di 100 valori generati casualmente di x
, le ricerche si completano in circa quattro secondi. Conoscendo le grandi meraviglie che l'ordinamento fa alla ricerca , tuttavia, ho deciso di ordinare i miei dati - prima per Item1
, poi per Item2
, e infine per Item3
- prima di eseguire le mie 100 ricerche. Mi aspettavo che la versione ordinata avrebbe funzionato un po 'più velocemente a causa della previsione del ramo: il mio pensiero è stato che una volta arrivati al punto in cui Item1 == x
, tutti gli ulteriori controlli t.Item1 <= x
prevedessero il ramo correttamente come "no take", accelerando la porzione di coda del ricerca. Con mia grande sorpresa, le ricerche hanno impiegato il doppio del tempo su un array ordinato !
Ho provato a cambiare l'ordine in cui ho eseguito i miei esperimenti e ho usato seme differenti per il generatore di numeri casuali, ma l'effetto è stato lo stesso: le ricerche in un array non ordinato sono state quasi due volte più veloci delle ricerche nello stesso array, ma smistato!
Qualcuno ha una buona spiegazione di questo strano effetto? Segue il codice sorgente dei miei test; Sto usando .NET 4.0.
private const int TotalCount = 500000;
private const int TotalQueries = 100;
private static long NextLong(Random r) {
var data = new byte[8];
r.NextBytes(data);
return BitConverter.ToInt64(data, 0);
}
private class TupleComparer : IComparer<Tuple<long,long,string>> {
public int Compare(Tuple<long,long,string> x, Tuple<long,long,string> y) {
var res = x.Item1.CompareTo(y.Item1);
if (res != 0) return res;
res = x.Item2.CompareTo(y.Item2);
return (res != 0) ? res : String.CompareOrdinal(x.Item3, y.Item3);
}
}
static void Test(bool doSort) {
var data = new List<Tuple<long,long,string>>(TotalCount);
var random = new Random(1000000007);
var sw = new Stopwatch();
sw.Start();
for (var i = 0 ; i != TotalCount ; i++) {
var a = NextLong(random);
var b = NextLong(random);
if (a > b) {
var tmp = a;
a = b;
b = tmp;
}
var s = string.Format("{0}-{1}", a, b);
data.Add(Tuple.Create(a, b, s));
}
sw.Stop();
if (doSort) {
data.Sort(new TupleComparer());
}
Console.WriteLine("Populated in {0}", sw.Elapsed);
sw.Reset();
var total = 0L;
sw.Start();
for (var i = 0 ; i != TotalQueries ; i++) {
var x = NextLong(random);
var cnt = data.Count(t => t.Item1 <= x && t.Item2 >= x);
total += cnt;
}
sw.Stop();
Console.WriteLine("Found {0} matches in {1} ({2})", total, sw.Elapsed, doSort ? "Sorted" : "Unsorted");
}
static void Main() {
Test(false);
Test(true);
Test(false);
Test(true);
}
Populated in 00:00:01.3176257
Found 15614281 matches in 00:00:04.2463478 (Unsorted)
Populated in 00:00:01.3345087
Found 15614281 matches in 00:00:08.5393730 (Sorted)
Populated in 00:00:01.3665681
Found 15614281 matches in 00:00:04.1796578 (Unsorted)
Populated in 00:00:01.3326378
Found 15614281 matches in 00:00:08.6027886 (Sorted)
Item1 == x
, tutti gli ulteriori controlli t.Item1 <= x
prevedessero correttamente il ramo come "no take", accelerando la parte di coda della ricerca. Ovviamente, questa linea di pensiero è stata smentita dalla dura realtà :)