Comments 21
Таким образом, получается что в Microsoft сделали всё правильно, потом неправильно, потом опять правильно.На самом деле это у вашей коллеги ошибка в тестах (или в коде), т.к. если элементы сортируются и при сортировке считаются одинаковыми, то и при проверке в тестах должен использоваться тот же самый метод сравнения (тот же самый компарер или Equals).
Читаем внимательно статью. В одной версии .NET Framework количество сравнений для 100 элементов — 390, в более поздней (т.е. та, что должна быть лучше) — 813, а ещё более поздней — опять 390. В Microsoft что-то сделали правильно, потом неправильно, потом опять правильно.
Я не видел исходников для разных версий, но пока вы выглядите голословно, возможно у версии в 4.0 была асимптотика лучше, возможно она на наиболее распространенных наборах данных (по версии MS) давала в среднем меньшее время. Много еще критериев. Говорить что кто-то что-то сделал неправильно только потому что на вашем наборе в 100 элементов было больше сравнений — это нонсенс.
Кроме того, использовать разные компареры при сортировке и в тестах — это тоже очень странно как минимум.
Кроме того, использовать разные компареры при сортировке и в тестах — это тоже очень странно как минимум.
В коде, что представлен в статье воссозданы один к одному условия что были в тесте. Пускай у нас есть тест на очень много одинаковых решений, который в 4.0.30319.17929 будет работать одно время, а в 4.0.30319.18047 в 2.5 раза дольше на одинаковых данных. И, имхо, этот факт надо держать в голове. В пределах одной версии такой разброс, имхо, странно.
В таком случае вам будет интересно знать, что в List.Sort (а точнее в Array.Sort, который используется внутри) в .net используется не совсем QuickSort, там используется как минимум 5 сортировок, включая HeapSort, QuickSort и InsertionSort, котрые выбираются в зависимости от текущей глубины рекурсии, размера сортируемого куска, установленной версии фреймворка и т.д.
Но вот только ИМХО держать в голове такие мелочи (тем более для какой-то версии, различающейся в шестом знаке — не нужно)
Но вот только ИМХО держать в голове такие мелочи (тем более для какой-то версии, различающейся в шестом знаке — не нужно)
В статье даже ссылка есть на msdn, в которой английским по белому написано: This method uses Array.Sort, which uses the QuickSort algorithm. This implementation performs an unstable sort; that is, if two elements are equal, their order might not be preserved. In contrast, a stable sort preserves the order of elements that are equal.
On average, this method is an O(n log n) operation, where n is Count; in the worst case it is an O(n ^ 2) operation.
Можно ссылку на то, что там используется «HeapSort, QuickSort и InsertionSort»?
On average, this method is an O(n log n) operation, where n is Count; in the worst case it is an O(n ^ 2) operation.
Можно ссылку на то, что там используется «HeapSort, QuickSort и InsertionSort»?
В MSDN тонкости не описаны, ссылки нету, используйте рефлектор или исходники.
Ниже вам уже ответили с кодом.
Ниже вам уже ответили с кодом.
В коде я вижу использование для версии 4 — QuickSort. Где HeapSort и InsertionSort?
Перестаньте быть таким упрямым, также перестаньте верить тому, что написано на обертке. Все остальные сортировки внутри.
Не нашёл исходников CLR 4 (если они у вас есть — буду очень благодарен за возможность на них глянуть), но в CLR 2 функция TrySZSort просто вызывает QuickSort на C++ для стандартных типов. И выигрыш в скорости появляется из-за того что таким образом имеется возможность сравнивать элементы за одну инструкцию сравнения "<", вместо 10, необходимых для того же Int32.CompareTo().
Я по прежнему хочу видеть доказательства того, что Microsoft использует HeapSort и InsertionSort в .NET 4.0
Я по прежнему хочу видеть доказательства того, что Microsoft использует HeapSort и InsertionSort в .NET 4.0
Используйте рефлектор.

Вот из рефлектора для .NET 4.0 Вызывается либо QuickSort, либо TrySZSort, который тоже QuickSort, но для нативных типов. Ваши доказательства, какие?
Надо было всего лишь на 1 шаг глубже зайти.
Для DeepLimitedQuickSort внутри код примерно такой же.
private static void IntroSort(T[] keys, int lo, int hi, int depthLimit, IComparer<T> comparer)
{
for (; hi > lo; {
int num;
hi = num - 1;
}
)
{
int num = hi - lo + 1;
if (num <= 16)
{
if (num == 1)
break;
if (num == 2)
{
ArraySortHelper<T>.SwapIfGreater(keys, comparer, lo, hi);
break;
}
else if (num == 3)
{
ArraySortHelper<T>.SwapIfGreater(keys, comparer, lo, hi - 1);
ArraySortHelper<T>.SwapIfGreater(keys, comparer, lo, hi);
ArraySortHelper<T>.SwapIfGreater(keys, comparer, hi - 1, hi);
break;
}
else
{
ArraySortHelper<T>.InsertionSort(keys, lo, hi, comparer);
break;
}
}
else if (depthLimit == 0)
{
ArraySortHelper<T>.Heapsort(keys, lo, hi, comparer);
break;
}
else
{
--depthLimit;
num = ArraySortHelper<T>.PickPivotAndPartition(keys, lo, hi, comparer);
ArraySortHelper<T>.IntroSort(keys, num + 1, hi, depthLimit, comparer);
}
}
}
Для DeepLimitedQuickSort внутри код примерно такой же.
Так, можно пожалуйста скриншот переходов от List.Sort к IntroSort для .NET 4.0?
Для 4.5 microsoft не отрицает использование IntroSoft msdn.microsoft.com/en-us/library/kwx6zbd4.aspx, но 4.0 msdn.microsoft.com/en-us/library/kwx6zbd4(v=vs.100).aspx использует только QuickSort.
Для 4.5 microsoft не отрицает использование IntroSoft msdn.microsoft.com/en-us/library/kwx6zbd4.aspx, но 4.0 msdn.microsoft.com/en-us/library/kwx6zbd4(v=vs.100).aspx использует только QuickSort.
Внизу уже ответили:
Вот DepthLimitedQuickSort тоже внутри использует как минимум HeapSort, и он, как мы видим — работает когда версия ниже 4.5.
Мне, если честно, наскучило вам уже объяснять. Дальше как-нибудь сами.
if (BinaryCompatibility.TargetsAtLeast_Desktop_V4_5)
ArraySortHelper<T>.IntrospectiveSort(keys, index, length, comparer);
else
ArraySortHelper<T>.DepthLimitedQuickSort(keys, index, length + index - 1, comparer, 32);
Вот DepthLimitedQuickSort тоже внутри использует как минимум HeapSort, и он, как мы видим — работает когда версия ниже 4.5.
Мне, если честно, наскучило вам уже объяснять. Дальше как-нибудь сами.
Тем более исходный посыл был в том, что у вас в первую очередь бажный код, который упал из за изменения тонкостей .net, которые никто и не гарантировал.
А работает он дольше только на вашем примере из 100 одинаковых элементов, очень синтетически. На основании этого нельзя делать выводы о качестве алгоритма.
И еще — quicksort может быть реализован как стабильная сортировка, так что утверждение «которая, как мы все прекрасно знаем, неустойчива» — неверно.
А работает он дольше только на вашем примере из 100 одинаковых элементов, очень синтетически. На основании этого нельзя делать выводы о качестве алгоритма.
И еще — quicksort может быть реализован как стабильная сортировка, так что утверждение «которая, как мы все прекрасно знаем, неустойчива» — неверно.
Самое забавное, что .NET 4.0 и .NET 4.5 используют разные алгоритмы сортировки.
Вот код из глибин Рефектора:
<code
Вот код из глибин Рефектора:
<code
public void Sort(T[] keys, int index, int length, IComparer<T> comparer)
{
try
{
if (comparer == null)
comparer = (IComparer<T>) Comparer<T>.Default;
if (BinaryCompatibility.TargetsAtLeast_Desktop_V4_5)
ArraySortHelper<T>.IntrospectiveSort(keys, index, length, comparer);
else
ArraySortHelper<T>.DepthLimitedQuickSort(keys, index, length + index - 1, comparer, 32);
}
catch (IndexOutOfRangeException ex)
{
IntrospectiveSortUtilities.ThrowOrIgnoreBadComparer((object) comparer);
}
catch (Exception ex)
{
throw new InvalidOperationException(Environment.GetResourceString("InvalidOperation_IComparerFailed"), ex);
}
}
1. Тест у вас какой то странный. Сравнение результата неустойчивой сортировки с компаратором, который в данных условиях возвращает всегда 0. Конечно, тест когда то, да должен упасть.
2. Я думаю, что в МСДНе имелось ввиду, что метод внутри, кроме всего прочего, использует быструю сортировку как основной метод сортировки. Это предостережение скорее всего связано с неустойчивостью метода. К тому же, судя по коду, в асмиптотике количество сравнений должно сходиться. Я не спец по алгоритмам, но вот что натестил.
кол-во элементов / 4 фремворк / 4.5 фреймворк / отношение кол-ва сравнений 4 и 4.5 фреймворков
1000000 / 20715517 /16898758 / 0,815753621
10000000 / 248621693 / 208306444 / 0,837845007
100000000 / 2737859069 / 2388941074 / 0,872558088
3. Я тоже писал реализации быстрой сортировки, сортировки слиянием, и много других сортировок, но на рандомных данных мои реализации по времени уступали Array.Sort. Возможно, это связано с данными оптимизациями, которые в Вашем конкретном случае, выдают большое количество сравнений.
2. Я думаю, что в МСДНе имелось ввиду, что метод внутри, кроме всего прочего, использует быструю сортировку как основной метод сортировки. Это предостережение скорее всего связано с неустойчивостью метода. К тому же, судя по коду, в асмиптотике количество сравнений должно сходиться. Я не спец по алгоритмам, но вот что натестил.
кол-во элементов / 4 фремворк / 4.5 фреймворк / отношение кол-ва сравнений 4 и 4.5 фреймворков
1000000 / 20715517 /16898758 / 0,815753621
10000000 / 248621693 / 208306444 / 0,837845007
100000000 / 2737859069 / 2388941074 / 0,872558088
3. Я тоже писал реализации быстрой сортировки, сортировки слиянием, и много других сортировок, но на рандомных данных мои реализации по времени уступали Array.Sort. Возможно, это связано с данными оптимизациями, которые в Вашем конкретном случае, выдают большое количество сравнений.
Вы могли с тем же успехом использовать компаратор
internal class SmthComparer: IComparer {
#region Implementation of IComparer
public int Compare(Smth x, Smth y) {
return 0;
}
#endregion
}
а потом удивляться, что у вас сортировка массивы любых элементов будет разная на различных версиях фреймворка.
извиняюсь, но почему-то теги выделения кода и пр не работают.
internal class SmthComparer: IComparer {
#region Implementation of IComparer
public int Compare(Smth x, Smth y) {
return 0;
}
#endregion
}
а потом удивляться, что у вас сортировка массивы любых элементов будет разная на различных версиях фреймворка.
извиняюсь, но почему-то теги выделения кода и пр не работают.
с нашим компаратором, который именно на этом тесте всегда возвращает 0
тестировалось поведение метода при разных входных данных. Метод вызывает в себе ещё несколько десятков методов (каждый из которых был оттестирован отдельно), вот их совокупное влияние на результат и тестируется. Тестов было очень много. И был среди них тест, в котором все данные одинаковые, т.е. компаратор именно на этом тесте всегда возвращает 0. К чему Ваши утрирования?
Sign up to leave a comment.
Интересное поведение сортировки в .NET