C # Sort 및 OrderBy 비교


105

Sort 또는 OrderBy를 사용하여 목록을 정렬 할 수 있습니다. 어느 것이 더 빠릅니까? 둘 다 동일한 알고리즘에서 작동합니까?

List<Person> persons = new List<Person>();
persons.Add(new Person("P005", "Janson"));
persons.Add(new Person("P002", "Aravind"));
persons.Add(new Person("P007", "Kazhal"));

1.

persons.Sort((p1,p2)=>string.Compare(p1.Name,p2.Name,true));

2.

var query = persons.OrderBy(n => n.Name, new NameComparer());

class NameComparer : IComparer<string>
{
    public int Compare(string x,string y)
    {
      return  string.Compare(x, y, true);
    }
}

22
이에 대해 언급 한 답변이 없다고 믿을 수는 없지만 가장 큰 차이점은 다음과 같습니다. OrderBy는 Array 또는 List의 정렬 된 복사본을 만들고 Sort는 실제로 정렬합니다.
PRMan

2
제목이 비교를 말했듯이 OrderBy가 안정적이고 정렬은 최대 16 개 요소까지 안정적이라는 점을 추가하고 싶습니다. 요소가 그 이상이면 다른 불안정한 알고리즘으로 전환하면 최대 16 개의 요소 삽입 정렬이 사용되므로 편집 : 안정은 상대적인 순서를 유지하는 것을 의미합니다. 같은 키를 가진 요소의.
Eklavyaa

@PRMan 아니요, OrderBy는 게으른 열거 형을 만듭니다. 반환 된 열거 형에서 ToList와 같은 메서드를 호출하는 경우에만 정렬 된 복사본을 얻습니다.
Stewart

1
@Stewart, System.Core / System / Linq / Enumerable.cs의 버퍼에있는 TElement []에 Array.Copy 또는 Collection.Copy를 복사본으로 간주하지 않습니까? IEnumerable에서 ToList를 호출하면 일시적으로 메모리에 한 번에 3 개의 복사본이있을 수 있습니다. 이것은 내 요점의 일부였던 매우 큰 배열의 문제입니다. 또한 동일한 정렬 순서가 두 번 이상 필요한 경우 List in-place를 한 번 호출하는 것이 영구성 때문에 List를 반복적으로 정렬하는 것보다 훨씬 효율적입니다.
PRMan

1
@PRMan 오, 당신은 정렬 된 사본이 내부적으로 빌드된다는 것을 의미했습니다. OrderBy가 복사본을 만들지 않기 때문에 여전히 정확하지 않습니다. 제가 볼 수 있듯이 이것은 실제로 컬렉션을 반복하기 시작할 때 GetEnumerator 메서드에 의해 수행됩니다. 방금 코드를 단계별로 실행 해 보았는데 LINQ 식에서 변수를 채우는 코드가 거의 즉시 실행되지만 foreach 루프에 들어가면 정렬하는 데 시간이 걸립니다. 시간이 좀 더 남았을 때 배후에서 어떻게 작동하는지 알아 내기 위해 노력해야 할 것 같습니다.
Stewart

답변:


90

측정하지 않는 이유 :

class Program
{
    class NameComparer : IComparer<string>
    {
        public int Compare(string x, string y)
        {
            return string.Compare(x, y, true);
        }
    }

    class Person
    {
        public Person(string id, string name)
        {
            Id = id;
            Name = name;
        }
        public string Id { get; set; }
        public string Name { get; set; }
    }

    static void Main()
    {
        List<Person> persons = new List<Person>();
        persons.Add(new Person("P005", "Janson"));
        persons.Add(new Person("P002", "Aravind"));
        persons.Add(new Person("P007", "Kazhal"));

        Sort(persons);
        OrderBy(persons);

        const int COUNT = 1000000;
        Stopwatch watch = Stopwatch.StartNew();
        for (int i = 0; i < COUNT; i++)
        {
            Sort(persons);
        }
        watch.Stop();
        Console.WriteLine("Sort: {0}ms", watch.ElapsedMilliseconds);

        watch = Stopwatch.StartNew();
        for (int i = 0; i < COUNT; i++)
        {
            OrderBy(persons);
        }
        watch.Stop();
        Console.WriteLine("OrderBy: {0}ms", watch.ElapsedMilliseconds);
    }

    static void Sort(List<Person> list)
    {
        list.Sort((p1, p2) => string.Compare(p1.Name, p2.Name, true));
    }

    static void OrderBy(List<Person> list)
    {
        var result = list.OrderBy(n => n.Name, new NameComparer()).ToArray();
    }
}

릴리스 모드에서 컴파일 할 때 내 컴퓨터에서이 프로그램은 다음을 인쇄합니다.

Sort: 1162ms
OrderBy: 1269ms

최신 정보:

@Stefan이 제안한대로 큰 목록을 더 적게 정렬 한 결과는 다음과 같습니다.

List<Person> persons = new List<Person>();
for (int i = 0; i < 100000; i++)
{
    persons.Add(new Person("P" + i.ToString(), "Janson" + i.ToString()));
}

Sort(persons);
OrderBy(persons);

const int COUNT = 30;
Stopwatch watch = Stopwatch.StartNew();
for (int i = 0; i < COUNT; i++)
{
    Sort(persons);
}
watch.Stop();
Console.WriteLine("Sort: {0}ms", watch.ElapsedMilliseconds);

watch = Stopwatch.StartNew();
for (int i = 0; i < COUNT; i++)
{
    OrderBy(persons);
}
watch.Stop();
Console.WriteLine("OrderBy: {0}ms", watch.ElapsedMilliseconds);

인쇄물:

Sort: 8965ms
OrderBy: 8460ms

이 시나리오에서는 OrderBy가 더 잘 수행되는 것처럼 보입니다.


업데이트 2 :

임의 이름 사용 :

List<Person> persons = new List<Person>();
for (int i = 0; i < 100000; i++)
{
    persons.Add(new Person("P" + i.ToString(), RandomString(5, true)));
}

어디:

private static Random randomSeed = new Random();
public static string RandomString(int size, bool lowerCase)
{
    var sb = new StringBuilder(size);
    int start = (lowerCase) ? 97 : 65;
    for (int i = 0; i < size; i++)
    {
        sb.Append((char)(26 * randomSeed.NextDouble() + start));
    }
    return sb.ToString();
}

수율 :

Sort: 8968ms
OrderBy: 8728ms

여전히 OrderBy가 더 빠릅니다.


2
아주 작은 목록 (3 개 항목)을 1000000 번 정렬하거나 매우 큰 목록 (1000000 개 항목)을 몇 번 정렬하는 것과는 많이 다릅니다. 둘 다 매우 관련이 있습니다. 실제로는 중간 크기의 목록 (중간은 무엇입니까? ... 현재 1000 개 항목이라고 가정 해 보겠습니다)이 가장 흥미 롭습니다. IMHO, 3 개 항목이있는 정렬 목록은별로 의미가 없습니다.
Stefan Steinegger

25
"더 빠름"과 "눈에 띄게 더 빠름"에는 차이가 있습니다. 마지막 예에서 차이는 약 1/4 초였습니다. 사용자가 알아 차 릴까요? 사용자가 결과를 얻기 위해 거의 9 초를 기다리는 것이 용납되지 않습니까? 두 질문에 대한 대답이 "아니요"이면 성능 측면에서 어떤 질문을 선택하든 상관 없습니다.
Eric Lippert

12
여기에서 테스트는 스톱워치를 시작하기 전에 목록을 정렬하므로 정렬 된 입력에 직면했을 때 두 알고리즘이 비교되는 방식을 비교하고 있습니다. 이것은 정렬되지 않은 입력을 사용한 상대 성능과 상당히 다를 수 있습니다.
phoog

3
이 결과는 LINQ인플레 이스 List<T>.Sort구현 에 비해 추가 메모리를 소비 해야한다는 사실을 고려할 때 IMHO가 상당히 놀랍 습니다 . 새로운 .NET 버전에서이 기능을 개선했는지 확실하지 않지만 내 컴퓨터 (i7 3 세대 64 비트 .NET 4.5 릴리스) 에서는 모든 경우에 Sort성능이 뛰어납니다 OrderBy. 또한 OrderedEnumerable<T>소스 코드 를 살펴보면 마지막으로 Quicksort를 호출하여 인덱스 배열을 제자리에 정렬하기 전에 3 개의 추가 배열 (첫 번째 a Buffer<T>, 다음 프로젝션 된 키 배열, 다음 인덱스 배열)을 생성하는 것으로 보입니다 .
Groo

2
...이 모든 끝에 ToArray결과 배열을 만드는 호출이 있습니다. 메모리 작업과 배열 인덱싱은 매우 빠른 작업이지만 여전히 이러한 결과의 논리를 찾을 수 없습니다.
Groo

121

아니요, 동일한 알고리즘이 아닙니다. 우선 LINQ OrderBy안정된 것으로 문서화됩니다 (즉, 두 항목이 동일한 Name경우 원래 순서대로 표시됨).

또한 쿼리를 버퍼링하는지 아니면 여러 번 반복하는지에 따라 달라집니다 (결과를 버퍼링하지 않는 한 LINQ-to-Objects는마다 다시 정렬됩니다 foreach).

OrderBy쿼리의 경우 다음을 사용하고 싶습니다.

OrderBy(n => n.Name, StringComparer.{yourchoice}IgnoreCase);

(용 {yourchoice}의 하나 CurrentCulture, Ordinal또는 InvariantCulture).

List<T>.Sort

이 메서드는 QuickSort 알고리즘을 사용하는 Array.Sort를 사용합니다. 이 구현은 불안정한 정렬을 수행합니다. 즉, 두 요소가 같으면 순서가 유지되지 않을 수 있습니다. 반대로 안정적인 정렬은 동일한 요소의 순서를 유지합니다.

Enumerable.OrderBy

이 방법은 안정적인 정렬을 수행합니다. 즉, 두 요소의 키가 같으면 요소의 순서가 유지됩니다. 반대로 불안정한 정렬은 동일한 키를 가진 요소의 순서를 유지하지 않습니다. 종류; 즉, 두 요소가 같으면 순서가 유지되지 않을 수 있습니다. 반대로 안정적인 정렬은 동일한 요소의 순서를 유지합니다.


5
.NET Reflector 또는 ILSpy를 사용 Enumerable.OrderBy하여 내부 구현을 열고 드릴 다운하면 OrderBy 정렬 알고리즘이 안정적인 정렬을 수행하는 QuickSort의 변형임을 알 수 있습니다. (참조 System.Linq.EnumerableSorter<TElement>) 따라서 Array.SortEnumerable.OrderBy둘 다 O (N log N) 실행 시간 을 가질 것으로 예상 할 수 있습니다 . 여기서 N 은 컬렉션의 요소 수입니다.
John Beyer

@Marc 두 요소가 같고 순서가 유지되지 않으면 차이점이 무엇인지 잘 모르겠습니다. 이것은 확실히 원시 데이터 유형에 대한 문제처럼 보이지 않습니다. 그러나 참조 유형의 경우에도 이름이 Marc Gravell 인 사람이 Marc Gravell이라는 이름의 다른 사람 앞에 나타났습니다 (예 : :)). 나는 당신의 대답 / 지식에 의문을 제기하는 것이 아니라이 시나리오의 적용을 찾고 있습니다.
Mukus

4
@Mukus는 회사 주소록을 이름 (또는 실제로 생년월일)으로 정렬한다고 상상합니다. 필연적으로 중복 될 수 있습니다. 문제는 궁극적으로 그들에게 어떤 일이 발생합니까? 하위 주문이 정의되어 있습니까?
Marc Gravell

55

Darin Dimitrov의 답변은 이미 정렬 된 입력에 직면했을 OrderBy때보 다 약간 더 빠르다 는 것을 보여줍니다 List.Sort. 정렬되지 않은 데이터를 반복적으로 정렬하도록 그의 코드를 수정했으며 OrderBy대부분의 경우 약간 느립니다.

또한 OrderBy테스트는 ToArrayLinq 열거자를 강제로 열거하는 데 사용 하지만 Person[]입력 유형 ( List<Person>) 과 다른 유형 ( )을 반환합니다 . 따라서 ToList대신 사용하여 테스트를 다시 실행하고 ToArray더 큰 차이를 얻었습니다.

Sort: 25175ms
OrderBy: 30259ms
OrderByWithToList: 31458ms

코드:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;

class Program
{
    class NameComparer : IComparer<string>
    {
        public int Compare(string x, string y)
        {
            return string.Compare(x, y, true);
        }
    }

    class Person
    {
        public Person(string id, string name)
        {
            Id = id;
            Name = name;
        }
        public string Id { get; set; }
        public string Name { get; set; }
        public override string ToString()
        {
            return Id + ": " + Name;
        }
    }

    private static Random randomSeed = new Random();
    public static string RandomString(int size, bool lowerCase)
    {
        var sb = new StringBuilder(size);
        int start = (lowerCase) ? 97 : 65;
        for (int i = 0; i < size; i++)
        {
            sb.Append((char)(26 * randomSeed.NextDouble() + start));
        }
        return sb.ToString();
    }

    private class PersonList : List<Person>
    {
        public PersonList(IEnumerable<Person> persons)
           : base(persons)
        {
        }

        public PersonList()
        {
        }

        public override string ToString()
        {
            var names = Math.Min(Count, 5);
            var builder = new StringBuilder();
            for (var i = 0; i < names; i++)
                builder.Append(this[i]).Append(", ");
            return builder.ToString();
        }
    }

    static void Main()
    {
        var persons = new PersonList();
        for (int i = 0; i < 100000; i++)
        {
            persons.Add(new Person("P" + i.ToString(), RandomString(5, true)));
        } 

        var unsortedPersons = new PersonList(persons);

        const int COUNT = 30;
        Stopwatch watch = new Stopwatch();
        for (int i = 0; i < COUNT; i++)
        {
            watch.Start();
            Sort(persons);
            watch.Stop();
            persons.Clear();
            persons.AddRange(unsortedPersons);
        }
        Console.WriteLine("Sort: {0}ms", watch.ElapsedMilliseconds);

        watch = new Stopwatch();
        for (int i = 0; i < COUNT; i++)
        {
            watch.Start();
            OrderBy(persons);
            watch.Stop();
            persons.Clear();
            persons.AddRange(unsortedPersons);
        }
        Console.WriteLine("OrderBy: {0}ms", watch.ElapsedMilliseconds);

        watch = new Stopwatch();
        for (int i = 0; i < COUNT; i++)
        {
            watch.Start();
            OrderByWithToList(persons);
            watch.Stop();
            persons.Clear();
            persons.AddRange(unsortedPersons);
        }
        Console.WriteLine("OrderByWithToList: {0}ms", watch.ElapsedMilliseconds);
    }

    static void Sort(List<Person> list)
    {
        list.Sort((p1, p2) => string.Compare(p1.Name, p2.Name, true));
    }

    static void OrderBy(List<Person> list)
    {
        var result = list.OrderBy(n => n.Name, new NameComparer()).ToArray();
    }

    static void OrderByWithToList(List<Person> list)
    {
        var result = list.OrderBy(n => n.Name, new NameComparer()).ToList();
    }
}

2
나는 LinqPad 5 이제 테스트 코드를 실행 (.NET 5)과는 OrderByWithToList같은 시간이 걸립니다 OrderBy.
dovid

38

나는 또 다른 차이점주의하는 것이 중요하다고 생각 SortOrderBy:

Person.CalculateSalary()많은 시간이 걸리는 방법 이 있다고 가정합니다 . 큰 목록을 정렬하는 작업보다 더 많을 수 있습니다.

비교

// Option 1
persons.Sort((p1, p2) => Compare(p1.CalculateSalary(), p2.CalculateSalary()));
// Option 2
var query = persons.OrderBy(p => p.CalculateSalary()); 

옵션 2 그것은 단지 호출하기 때문에, 우수한 성능을 가질 수있다 CalculateSalary방법 N 번 반면, Sort옵션을 호출 할 수 있습니다 CalculateSalary최대 2 N (로그 N ) 정렬 알고리즘의 성공 여부에 따라, 시간을.


4
이 문제에 대한 해결책이 있지만, 즉 데이터를 배열에 보관하고 두 개의 배열, 하나의 키와 다른 하나의 값을 사용하는 Array.Sort 오버로드를 사용하는 것이 사실입니다. 키 배열을 채울 때 CalculateSalary n시간 을 호출 합니다. 이것은 분명히 OrderBy를 사용하는 것만 큼 편리하지 않습니다.
phoog

14

간단히 말해서 :

목록 / 배열 정렬 () :

  • 불안정한 정렬.
  • 제자리에서 완료되었습니다.
  • Introsort / Quicksort를 사용합니다.
  • 사용자 지정 비교는 비교자를 제공하여 수행됩니다. 비교 비용이 비싸다면 OrderBy ()보다 느릴 수 있습니다 (키 사용을 허용합니다. 아래 참조).

OrderBy / ThenBy () :

  • 안정적인 정렬.
  • 제자리에 있지 않습니다.
  • Quicksort를 사용하십시오. Quicksort는 안정적인 정렬이 아닙니다. 여기에 트릭이 있습니다. 정렬 할 때 두 요소의 키가 같으면 정렬 전에 저장된 초기 순서를 비교합니다.
  • 키 (람다 사용)를 사용하여 값에 따라 요소를 정렬 할 수 있습니다 (예 :) x => x.Id. 모든 키는 정렬하기 전에 먼저 추출됩니다. 이렇게하면 Sort () 및 사용자 지정 비교자를 사용하는 것보다 성능이 향상 될 수 있습니다.

출처 : MDSN , 참조 출처dotnet / coreclr 저장소 (GitHub).

위에 나열된 일부 명령문은 현재 .NET 프레임 워크 구현 (4.7.2)을 기반으로합니다. 향후 변경 될 수 있습니다.


0

OrderBy 및 Sort 메서드에서 사용하는 알고리즘의 복잡성을 계산해야합니다. QuickSort는 내가 기억하는 것처럼 n (log n)의 복잡성을 가지고 있습니다. 여기서 n은 배열의 길이입니다.

orderby도 검색했지만 msdn 라이브러리에서도 정보를 찾을 수 없습니다. 하나의 속성에만 관련된 동일한 값과 정렬이없는 경우 Sort () 메서드를 사용하는 것이 좋습니다. OrderBy를 사용하지 않는 경우.


1
현재 MSDN 문서에 따르면 Sort는 입력에 따라 세 가지 다른 정렬 알고리즘을 사용합니다. 그중에는 QuickSort가 있습니다. OrderBy () 알고리즘에 대한 질문은 여기에 있습니다 (Quicksort) : stackoverflow.com/questions/2792074/…
Thor

-1

orderby가 훨씬 더 유용하다는 것을 추가하고 싶습니다.

왜? 내가 할 수 있기 때문에 :

Dim thisAccountBalances = account.DictOfBalances.Values.ToList
thisAccountBalances.ForEach(Sub(x) x.computeBalanceOtherFactors())
thisAccountBalances=thisAccountBalances.OrderBy(Function(x) x.TotalBalance).tolist
listOfBalances.AddRange(thisAccountBalances)

왜 복잡한 비교 자일까요? 필드를 기준으로 정렬하십시오. 여기에서는 TotalBalance를 기준으로 정렬합니다.

아주 쉽게.

정렬로는 할 수 없습니다. 이유가 궁금합니다. orderBy로 잘하십시오.

속도는 항상 O (n)입니다.


3
질문 : 귀하의 답변에서 O (n) 시간 (나는 가정)이 OrderBy 또는 Comparer를 참조합니까? 빠른 정렬이 O (N) 시간을 달성 할 수 있다고 생각하지 않습니다.
Kevman
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.