Tôi có một danh sách 500000 Tuple<long,long,string>
đối tượng được tạo ngẫu nhiên mà tôi đang thực hiện tìm kiếm "giữa" đơn giản:
var data = new List<Tuple<long,long,string>>(500000);
...
var cnt = data.Count(t => t.Item1 <= x && t.Item2 >= x);
Khi tôi tạo mảng ngẫu nhiên của mình và chạy tìm kiếm 100 giá trị được tạo ngẫu nhiên x
, các tìm kiếm sẽ hoàn thành trong khoảng bốn giây. Tuy nhiên, biết được những điều kỳ diệu mà việc sắp xếp thực hiện đối với việc tìm kiếm , tôi quyết định sắp xếp dữ liệu của mình - trước tiên Item1
, sau đó Item2
và cuối cùng Item3
- trước khi chạy 100 tìm kiếm của tôi. Tôi dự kiến phiên bản được sắp xếp sẽ thực hiện nhanh hơn một chút vì dự đoán chi nhánh: suy nghĩ của tôi là khi chúng ta đến điểm Item1 == x
, tất cả các kiểm tra tiếp theo t.Item1 <= x
sẽ dự đoán chính xác chi nhánh là "không mất", tăng tốc phần đuôi của Tìm kiếm. Thật ngạc nhiên, các tìm kiếm mất gấp đôi thời gian trên một mảng được sắp xếp !
Tôi đã thử chuyển đổi thứ tự mà tôi đã chạy thử nghiệm và sử dụng các hạt giống khác nhau cho trình tạo số ngẫu nhiên, nhưng hiệu quả vẫn như nhau: các tìm kiếm trong một mảng chưa được sắp xếp chạy nhanh gần gấp đôi so với các tìm kiếm trong cùng một mảng, nhưng sắp xếp
Có ai có một lời giải thích tốt về hiệu ứng kỳ lạ này? Mã nguồn của các bài kiểm tra của tôi sau đây; Tôi đang sử dụng .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
, tất cả các kiểm tra tiếp theo t.Item1 <= x
sẽ dự đoán chính xác chi nhánh là "không mất", tăng tốc phần đuôi của tìm kiếm. Rõ ràng, dòng suy nghĩ đó đã được chứng minh là sai bởi thực tế khắc nghiệt :)