development

해시 셋과리스트 성능

big-blog 2020. 2. 27. 22:23
반응형

해시 셋과리스트 성능


일반 HashSet<T>클래스 의 검색 성능이 일반 클래스 의 검색 성능 보다 높다는 것은 분명합니다 List<T>. 해시 기반 키를 List<T>클래스 의 선형 접근 방식과 비교하십시오 .

그러나 해시 키를 계산하는 데 자체 CPU 사이클이 소요될 수 있으므로 소량의 항목에 대해서는 선형 검색이에 대한 실제 대안이 될 수 있습니다 HashSet<T>.

내 질문 : 손익분기 점은 어디에 있습니까?

시나리오를 단순화하고 공정하게하기 위해 List<T>클래스가 요소의 Equals()메소드를 사용하여 항목을 식별 한다고 가정합니다 .


많은 사람들은 일단 속도가 실제로 HashSet<T>이길 수 있는 문제가되는 크기에 도달하면 List<T>그것은 당신이하는 일에 달려 있다고 말합니다.

List<T>평균 5 개 항목 만 포함 한다고 가정 해 보겠습니다 . 많은 수의주기에 걸쳐 하나의 항목이 각주기에 추가되거나 제거되면을 사용하는 것이 좋습니다 List<T>.

나는 내 컴퓨터에서 이것을 테스트했으며, 이점을 얻으려면 매우 작아야합니다 List<T>. 짧은 문자열 목록의 경우 크기 20 이후의 개체의 경우 크기 5 이후에 이점이 사라졌습니다.

1 item LIST strs time: 617ms
1 item HASHSET strs time: 1332ms

2 item LIST strs time: 781ms
2 item HASHSET strs time: 1354ms

3 item LIST strs time: 950ms
3 item HASHSET strs time: 1405ms

4 item LIST strs time: 1126ms
4 item HASHSET strs time: 1441ms

5 item LIST strs time: 1370ms
5 item HASHSET strs time: 1452ms

6 item LIST strs time: 1481ms
6 item HASHSET strs time: 1418ms

7 item LIST strs time: 1581ms
7 item HASHSET strs time: 1464ms

8 item LIST strs time: 1726ms
8 item HASHSET strs time: 1398ms

9 item LIST strs time: 1901ms
9 item HASHSET strs time: 1433ms

1 item LIST objs time: 614ms
1 item HASHSET objs time: 1993ms

4 item LIST objs time: 837ms
4 item HASHSET objs time: 1914ms

7 item LIST objs time: 1070ms
7 item HASHSET objs time: 1900ms

10 item LIST objs time: 1267ms
10 item HASHSET objs time: 1904ms

13 item LIST objs time: 1494ms
13 item HASHSET objs time: 1893ms

16 item LIST objs time: 1695ms
16 item HASHSET objs time: 1879ms

19 item LIST objs time: 1902ms
19 item HASHSET objs time: 1950ms

22 item LIST objs time: 2136ms
22 item HASHSET objs time: 1893ms

25 item LIST objs time: 2357ms
25 item HASHSET objs time: 1826ms

28 item LIST objs time: 2555ms
28 item HASHSET objs time: 1865ms

31 item LIST objs time: 2755ms
31 item HASHSET objs time: 1963ms

34 item LIST objs time: 3025ms
34 item HASHSET objs time: 1874ms

37 item LIST objs time: 3195ms
37 item HASHSET objs time: 1958ms

40 item LIST objs time: 3401ms
40 item HASHSET objs time: 1855ms

43 item LIST objs time: 3618ms
43 item HASHSET objs time: 1869ms

46 item LIST objs time: 3883ms
46 item HASHSET objs time: 2046ms

49 item LIST objs time: 4218ms
49 item HASHSET objs time: 1873ms

데이터는 그래프로 표시됩니다.

여기에 이미지 설명을 입력하십시오

코드는 다음과 같습니다.

static void Main(string[] args)
{
    int times = 10000000;


    for (int listSize = 1; listSize < 10; listSize++)
    {
        List<string> list = new List<string>();
        HashSet<string> hashset = new HashSet<string>();

        for (int i = 0; i < listSize; i++)
        {
            list.Add("string" + i.ToString());
            hashset.Add("string" + i.ToString());
        }

        Stopwatch timer = new Stopwatch();
        timer.Start();
        for (int i = 0; i < times; i++)
        {
            list.Remove("string0");
            list.Add("string0");
        }
        timer.Stop();
        Console.WriteLine(listSize.ToString() + " item LIST strs time: " + timer.ElapsedMilliseconds.ToString() + "ms");


        timer = new Stopwatch();
        timer.Start();
        for (int i = 0; i < times; i++)
        {
            hashset.Remove("string0");
            hashset.Add("string0");
        }
        timer.Stop();
        Console.WriteLine(listSize.ToString() + " item HASHSET strs time: " + timer.ElapsedMilliseconds.ToString() + "ms");
        Console.WriteLine();
    }


    for (int listSize = 1; listSize < 50; listSize+=3)
    {
        List<object> list = new List<object>();
        HashSet<object> hashset = new HashSet<object>();

        for (int i = 0; i < listSize; i++)
        {
            list.Add(new object());
            hashset.Add(new object());
        }

        object objToAddRem = list[0];

        Stopwatch timer = new Stopwatch();
        timer.Start();
        for (int i = 0; i < times; i++)
        {
            list.Remove(objToAddRem);
            list.Add(objToAddRem);
        }
        timer.Stop();
        Console.WriteLine(listSize.ToString() + " item LIST objs time: " + timer.ElapsedMilliseconds.ToString() + "ms");



        timer = new Stopwatch();
        timer.Start();
        for (int i = 0; i < times; i++)
        {
            hashset.Remove(objToAddRem);
            hashset.Add(objToAddRem);
        }
        timer.Stop();
        Console.WriteLine(listSize.ToString() + " item HASHSET objs time: " + timer.ElapsedMilliseconds.ToString() + "ms");
        Console.WriteLine();
    }

    Console.ReadLine();
}

당신은 이것을 잘못보고 있습니다. 예, List의 선형 검색은 소수의 항목에 대해 HashSet을 이길 것입니다. 그러나 성능 차이는 일반적으로 작은 컬렉션에는 중요하지 않습니다. 일반적으로 걱정해야 할 큰 모음 이며 Big-O 측면에서 생각하는입니다. 그러나 HashSet 성능에 대한 실제 병목 현상을 측정 한 경우 하이브리드 List / HashSet을 만들려고 시도 할 수 있지만 SO에 대한 질문을하지 않고 많은 경험적 성능 테스트를 수행하여이를 수행 할 수 있습니다.


이 두 구조를 비교하는 것은 본질적으로 무의미 성능 다르게 동작합니다. 의도를 전달하는 구조를 사용하십시오. List<T>복제본이 없다고 말하고 반복 순서가 a와 비교해도 문제가되지 않지만 상대적으로 내결함성 이 없으므로 HashSet<T>사용 List<T>하기에 여전히 좋지 않습니다.

즉, 성능의 다른 측면조사하겠습니다 .

+------------+--------+-------------+-----------+----------+----------+-----------+
| Collection | Random | Containment | Insertion | Addition |  Removal | Memory    |
|            | access |             |           |          |          |           |
+------------+--------+-------------+-----------+----------+----------+-----------+
| List<T>    | O(1)   | O(n)        | O(n)      | O(1)*    | O(n)     | Lesser    |
| HashSet<T> | O(n)   | O(1)        | n/a       | O(1)     | O(1)     | Greater** |
+------------+--------+-------------+-----------+----------+----------+-----------+

* Even though addition is O(1) in both cases, it will be relatively slower in HashSet<T> since it involves cost of precomputing hash code before storing it.

** The superior scalability of HashSet<T> has a memory cost. Every entry is stored as a new object along with its hash code. This article might give you an idea.


HashSet <> 또는 List <> 사용 여부는 컬렉션에 액세스하는 방법에 달려 있습니다 . 품목 순서를 보증해야하는 경우 목록을 사용하십시오. 그렇지 않으면 HashSet을 사용하십시오. Microsoft가 해시 알고리즘 및 객체의 구현에 대해 걱정하게하십시오.

HashSet은 컬렉션 ( O (1)의 복잡성 또는 그 근처 ) 을 열거하지 않아도 항목에 액세스 할 수 있으며, List는 주문을 보장하므로 HashSet과 달리 일부 항목은 열거되어야합니다 (O (n)의 복잡성).


이전 답변을 설명하기 위해 다른 시나리오에 대한 몇 가지 벤치 마크를 살펴 보았습니다.

  1. 작은 문자열 (12 ~ 20) (길이 5 ~ 10 자)
  2. 많은 (~ 10K) 작은 문자열
  3. 긴 문자열 (길이 200 ~ 1000 자)
  4. 많은 (~ 5K) 긴 문자열
  5. 몇 가지 정수
  6. 많은 (~ 10K) 정수

그리고 각 시나리오마다 나타나는 값을 찾으십시오.

  1. 목록의 시작 부분 ( "start", index 0)
  2. 목록의 시작 부분 근처 ( "초기", 색인 1)
  3. 목록 중간에 ( "중간", 색인 수 / 2)
  4. 목록 끝 근처 ( "late", index count-2)
  5. 목록 끝에서 ( "end", index count-1)

각 시나리오 전에 임의 크기의 임의 문자열 목록을 생성 한 다음 각 목록을 해시 세트에 제공했습니다. 각 시나리오는 기본적으로 10,000 번 실행되었습니다.

(의사 코드 테스트)

stopwatch.start
for X times
    exists = list.Contains(lookup);
stopwatch.stop

stopwatch.start
for X times
    exists = hashset.Contains(lookup);
stopwatch.stop

샘플 출력

Windows 7, 12GB Ram, 64 비트, Xeon 2.8GHz에서 테스트

---------- Testing few small strings ------------
Sample items: (16 total)
vgnwaloqf diwfpxbv tdcdc grfch icsjwk
...

Benchmarks:
1: hashset: late -- 100.00 % -- [Elapsed: 0.0018398 sec]
2: hashset: middle -- 104.19 % -- [Elapsed: 0.0019169 sec]
3: hashset: end -- 108.21 % -- [Elapsed: 0.0019908 sec]
4: list: early -- 144.62 % -- [Elapsed: 0.0026607 sec]
5: hashset: start -- 174.32 % -- [Elapsed: 0.0032071 sec]
6: list: middle -- 187.72 % -- [Elapsed: 0.0034536 sec]
7: list: late -- 192.66 % -- [Elapsed: 0.0035446 sec]
8: list: end -- 215.42 % -- [Elapsed: 0.0039633 sec]
9: hashset: early -- 217.95 % -- [Elapsed: 0.0040098 sec]
10: list: start -- 576.55 % -- [Elapsed: 0.0106073 sec]


---------- Testing many small strings ------------
Sample items: (10346 total)
dmnowa yshtrxorj vthjk okrxegip vwpoltck
...

Benchmarks:
1: hashset: end -- 100.00 % -- [Elapsed: 0.0017443 sec]
2: hashset: late -- 102.91 % -- [Elapsed: 0.0017951 sec]
3: hashset: middle -- 106.23 % -- [Elapsed: 0.0018529 sec]
4: list: early -- 107.49 % -- [Elapsed: 0.0018749 sec]
5: list: start -- 126.23 % -- [Elapsed: 0.0022018 sec]
6: hashset: early -- 134.11 % -- [Elapsed: 0.0023393 sec]
7: hashset: start -- 372.09 % -- [Elapsed: 0.0064903 sec]
8: list: middle -- 48,593.79 % -- [Elapsed: 0.8476214 sec]
9: list: end -- 99,020.73 % -- [Elapsed: 1.7272186 sec]
10: list: late -- 99,089.36 % -- [Elapsed: 1.7284155 sec]


---------- Testing few long strings ------------
Sample items: (19 total)
hidfymjyjtffcjmlcaoivbylakmqgoiowbgxpyhnrreodxyleehkhsofjqenyrrtlphbcnvdrbqdvji...
...

Benchmarks:
1: list: early -- 100.00 % -- [Elapsed: 0.0018266 sec]
2: list: start -- 115.76 % -- [Elapsed: 0.0021144 sec]
3: list: middle -- 143.44 % -- [Elapsed: 0.0026201 sec]
4: list: late -- 190.05 % -- [Elapsed: 0.0034715 sec]
5: list: end -- 193.78 % -- [Elapsed: 0.0035395 sec]
6: hashset: early -- 215.00 % -- [Elapsed: 0.0039271 sec]
7: hashset: end -- 248.47 % -- [Elapsed: 0.0045386 sec]
8: hashset: start -- 298.04 % -- [Elapsed: 0.005444 sec]
9: hashset: middle -- 325.63 % -- [Elapsed: 0.005948 sec]
10: hashset: late -- 431.62 % -- [Elapsed: 0.0078839 sec]


---------- Testing many long strings ------------
Sample items: (5000 total)
yrpjccgxjbketcpmnvyqvghhlnjblhgimybdygumtijtrwaromwrajlsjhxoselbucqualmhbmwnvnpnm
...

Benchmarks:
1: list: early -- 100.00 % -- [Elapsed: 0.0016211 sec]
2: list: start -- 132.73 % -- [Elapsed: 0.0021517 sec]
3: hashset: start -- 231.26 % -- [Elapsed: 0.003749 sec]
4: hashset: end -- 368.74 % -- [Elapsed: 0.0059776 sec]
5: hashset: middle -- 385.50 % -- [Elapsed: 0.0062493 sec]
6: hashset: late -- 406.23 % -- [Elapsed: 0.0065854 sec]
7: hashset: early -- 421.34 % -- [Elapsed: 0.0068304 sec]
8: list: middle -- 18,619.12 % -- [Elapsed: 0.3018345 sec]
9: list: end -- 40,942.82 % -- [Elapsed: 0.663724 sec]
10: list: late -- 41,188.19 % -- [Elapsed: 0.6677017 sec]


---------- Testing few ints ------------
Sample items: (16 total)
7266092 60668895 159021363 216428460 28007724
...

Benchmarks:
1: hashset: early -- 100.00 % -- [Elapsed: 0.0016211 sec]
2: hashset: end -- 100.45 % -- [Elapsed: 0.0016284 sec]
3: list: early -- 101.83 % -- [Elapsed: 0.0016507 sec]
4: hashset: late -- 108.95 % -- [Elapsed: 0.0017662 sec]
5: hashset: middle -- 112.29 % -- [Elapsed: 0.0018204 sec]
6: hashset: start -- 120.33 % -- [Elapsed: 0.0019506 sec]
7: list: late -- 134.45 % -- [Elapsed: 0.0021795 sec]
8: list: start -- 136.43 % -- [Elapsed: 0.0022117 sec]
9: list: end -- 169.77 % -- [Elapsed: 0.0027522 sec]
10: list: middle -- 237.94 % -- [Elapsed: 0.0038573 sec]


---------- Testing many ints ------------
Sample items: (10357 total)
370826556 569127161 101235820 792075135 270823009
...

Benchmarks:
1: list: early -- 100.00 % -- [Elapsed: 0.0015132 sec]
2: hashset: end -- 101.79 % -- [Elapsed: 0.0015403 sec]
3: hashset: early -- 102.08 % -- [Elapsed: 0.0015446 sec]
4: hashset: middle -- 103.21 % -- [Elapsed: 0.0015618 sec]
5: hashset: late -- 104.26 % -- [Elapsed: 0.0015776 sec]
6: list: start -- 126.78 % -- [Elapsed: 0.0019184 sec]
7: hashset: start -- 130.91 % -- [Elapsed: 0.0019809 sec]
8: list: middle -- 16,497.89 % -- [Elapsed: 0.2496461 sec]
9: list: end -- 32,715.52 % -- [Elapsed: 0.4950512 sec]
10: list: late -- 33,698.87 % -- [Elapsed: 0.5099313 sec]

손익분기 점은 해시 계산 비용에 따라 다릅니다. 해시 계산은 사소하거나 아닐 수 있습니다 ... :-) 항상 System.Collections.Specialized.HybridDictionary 클래스가 있으므로 손익 분기점에 대해 걱정할 필요가 없습니다.


항상 그렇듯이 대답은 " 그것은 달려있다 "입니다. C #에 대해 이야기하는 태그에서 가정합니다.

최선의 방법은 결정하는 것입니다

  1. 데이터 세트
  2. 사용 요구 사항

테스트 사례를 작성하십시오.

또한 목록을 정렬하는 방법 (정렬 된 경우), 비교해야 할 종류, 목록의 특정 개체에 대해 "비교"작업에 걸리는 시간 또는 사용하려는 방법에 따라 다릅니다. 수집.

일반적으로 선택할 수있는 가장 좋은 방법은 작업중인 데이터의 크기를 기준으로하는 것이 아니라 액세스하려는 방식에 따라 다릅니다. 특정 문자열 또는 다른 데이터와 관련된 각 데이터가 있습니까? 해시 기반 컬렉션이 가장 좋습니다. 저장하는 데이터의 순서가 중요합니까, 아니면 모든 데이터에 동시에 액세스해야합니까? 그러면 일반 목록이 더 나을 수 있습니다.

추가 :

물론, 위의 의견은 '성능'이 데이터 액세스를 의미한다고 가정합니다. 고려해야 할 사항 : "성능"이라고 말할 때 무엇을 찾고 있습니까? 성과 개별 가치가 조회됩니까? 대규모 (10000, 100000 이상) 값 세트를 관리합니까? 데이터 구조를 데이터로 채우는 성능입니까? 데이터를 삭제 하시겠습니까? 개별 데이터 비트에 액세스하십니까? 값을 바꾸시겠습니까? 값을 반복합니까? 메모리 사용량? 데이터 복사 속도? 예를 들어 문자열 값으로 데이터에 액세스하지만 주요 성능 요구 사항이 최소 메모리 사용량 인 경우 충돌하는 디자인 문제가있을 수 있습니다.


중단 점을 자동으로 감지하고 널값을 허용하는 HybridDictionary를 사용하여 HashSet과 동일하게 만들 수 있습니다.


때에 따라 다르지. 정답이 실제로 중요한 경우 프로파일 링을 수행하고 알아보십시오. 세트에 특정 개수 이상의 요소가 없을 것이라고 확신한다면 목록으로 이동하십시오. 숫자가 제한이 없으면 HashSet을 사용하십시오.


해싱하는 내용에 따라 다릅니다. 키가 정수이면 HashSet이 더 빠르기 전에 많은 항목이 필요하지 않을 수 있습니다. 문자열에 키를 입력하면 속도가 느려지고 입력 문자열에 따라 다릅니다.

확실히 벤치마킹을 쉽게 할 수 있습니까?


고려하지 않은 한 가지 요소는 GetHashcode () 함수의 견고성입니다. 완벽한 해시 기능으로 HashSet은 더 나은 검색 성능을 제공합니다. 그러나 해시 함수가 감소함에 따라 HashSet 검색 시간도 줄어 듭니다.


목록 구현, CPU 아키텍처, JVM, 루프 시맨틱, 등가 방법의 복잡성 등 많은 요인에 따라 달라집니다 ... 목록을 효과적으로 벤치마킹 할만큼 충분히 커질 때까지 (1000 개 이상의 요소), 해시 기반 바이너리 룩업은 선형 검색을 수동으로 이길 수 있으며 차이는 거기에서 확장됩니다.

도움이 되었기를 바랍니다!

참고 URL : https://stackoverflow.com/questions/150750/hashset-vs-list-performance



반응형