Почему List <>. OrderBy LINQ быстрее, чем IComparable + List <>. Сортировка в режиме отладки?

Меня интересовало, будет ли быстрее сортировать мои классы с помощью LINQ или реализовать интерфейс IComparable и List.Sort. Я был очень удивлен, когда код LINQ был быстрее.

Чтобы выполнить тест, я сделал очень простой класс с не очень подходящим именем TestSort, реализующим IComparable.

class TestSort: IComparable<TestSort>  {
    private int age;
    private string givenName;

    public int Age {
        get {
            return age;
        }
        set {
            age = value;
        }
    }

    public string GivenName {
        get {
            return givenName;
        }
        set {
            givenName = value;
        }
    }

    public TestSort(int age, string name) {
        this.age = age;
        this.givenName = name;
    }

    public int CompareTo(TestSort other) {
        return this.age.CompareTo(other.age);
    }
}

Тогда простая программа для ее сортировки много раз - сортировка была намного дороже, чем копирование списка, поэтому эффект этого можно игнорировать.

class Program {
    static void Main(string[] args) {
        // Create the test data
        string name = "Mr. Bob";

        Random r = new Random();
        var ts2 = new List<TestSort>();

        for (int i = 0; i < 100; i++) {
            ts2.Add(new TestSort(r.Next(), name));
        }

        DateTime start, end;

        // Test List<>.Sort
        start = DateTime.Now;
        for (int i = 0; i < 100000; i++) {
            var l = ts2.ToList();
            l.Sort();
        }
        end = DateTime.Now;

        Console.WriteLine("IComparable<T>: ");
        Console.WriteLine((end - start).TotalMilliseconds);


        // Test Linq OrderBy
        start = DateTime.Now;
        for (int i = 0; i < 100000; i++) {
            var l = ts2.ToList();
            l = l.OrderBy(item => item.Age).ToList();
        }
        end = DateTime.Now;

        Console.WriteLine("\nLINQ: ");
        Console.WriteLine((end - start).TotalMilliseconds);

        Console.WriteLine("Finished.");
        Console.ReadKey();
    }
}

Я был очень удивлен, получив следующий вывод:

IComparable<T>:
2965.1696

LINQ:
2181.1248

Иногда LINQ будет ниже 2000, а иногда IComparable будет около 3000.

Когда я тестировал его с обычным List<Int>, List.Sort составлял 1/4 скорость LINQ, которая оставалась примерно в 2000 году.

Итак, почему LINQ только около 66% скорости обычной сортировки для моего класса? Я что-то не так с моей реализацией IComparable?

Update: Я просто подумал попробовать сделать это в режиме выпуска, и да, результаты были разными:

IComparable<T>:
1593.0911

Linq:
1958.1119

Но мне все еще очень интересно узнать, почему IComparable работает медленнее в режиме отладки.

Ответы

Ответ 1

Если вы убедитесь, что перед началом измерения все JITed, вы можете получить разные результаты (я также рекомендую использовать класс Stopwatch для измерения времени):

var ll = ts2.ToList();
ll.Sort();
ll.OrderBy(item => item.Age).ToList();

Согласно моим измерениям (после добавления вышеуказанного кода), IComparable всегда быстрее (даже при отладке).

Ответ 2

Сортировка использует unoptimized quicksort, которая имеет сложность n * n в худшем случае. Я не знаю, что использует orderby, но я знаю, что он не использует тот же метод, поскольку он устойчив, в отличие от array.sort.

Ответ 3

Это может быть накладные расходы на вызов метода CompareTo, который будет заменен встроенным при компиляции и запуском в режиме выпуска.

Ответ 4

Для меня я буду использовать Linq с IComparable (когда это самый или единственный способ сортировки). В этом случае элемент TestSort: IComparable<TestSort>

var sorted = ll.OrderBy(item => item); // This automatically used age to compare, as it defined in CompareTo

ll может быть любым IEnumerable: List, Array и т.д.

Также в CompareTo у вас может быть несколько способов сравнения... например, вы можете сделать что-то вроде этого:

  public int CompareTo(TestSort other) {
        return this.age != other.age ? this.age.CompareTo(other.age) : this.dateOfBirth.CompareTo(other.dateOfBirth);
    }