사소한 키의 경우 unorder_map보다 map을 사용하면 어떤 이점이 있습니까?


371

unordered_mapC ++에서 최근에 한 이야기 는 조회 효율 ( 상각 O (1)O (log n) ) 때문에 이전에 unordered_map사용했던 대부분의 경우에 사용해야한다는 것을 깨달았습니다 . 나는지도를 사용하는 대부분의 시간, 나는 하나를 사용 하거나 키 유형으로; 따라서 해시 함수 정의에 아무런 문제가 없습니다. 내가 그것에 대해 더 많이 생각할수록 간단한 유형의 키의 경우 over 를 사용하는 이유를 찾을 수 없다는 것을 깨닫게 되었습니다. 인터페이스를 보았고 찾지 못했습니다. 내 코드에 영향을 미치는 중요한 차이점.mapintstd::stringstd::mapstd::unordered_map

따라서 질문 : 사용하는 실제 이유가 std::map이상 std::unordered_map같은 간단한 유형의 경우 int와는 std::string?

엄격하게 프로그래밍 관점에서 묻습니다. 표준으로 완전히 간주되지 않았으며 이식에 문제가 발생할 수 있음을 알고 있습니다.

또한 정답 중 하나가 작은 오버 헤드로 인해 "더 작은 데이터 세트에 더 효율적" 일 것으로 기대합니다 (그렇습니까?). 따라서 질문의 양을 키는 사소하지 않습니다 (> 1 024).

편집 : 야 , 나는 명백한 것을 잊었다. (GMan 덕분에!)-예,지도는 물론 주문된다.


22
인터뷰에서이 질문을하는 것을 좋아합니다. 이 질문에 대한 답은 복잡성 이론의 실제 적용에 대한 통찰력을 제공하며 O (1)과 같은 평범한 흑백 진술뿐만 아니라 O (n)보다 우수하거나 O (k)는 O (logn) 등과 동일합니다. ..

42
@Beh, 나는 당신이 의미 생각 "거품 종류의 더 나은 때보 다 빠른 종류의"P
코넬 Kisielewicz

2
스마트 포인터가 사소한 열쇠입니까?
thomthom

다음은지도가 유리한 경우 중 하나입니다. stackoverflow.com/questions/51964419/…
anilbey

답변:


399

map그 요소를 순서대로 유지하는 것을 잊지 마십시오 . 포기할 수 없다면 분명히 사용할 수 없습니다 unordered_map.

명심해야 할 것은 unordered_map일반적으로 더 많은 메모리를 사용 한다는 것입니다 . map하우스 키핑 포인터와 각 객체에 대한 메모리 만 있습니다. 반대로, unordered_map큰 배열 (일부 구현에서는 상당히 커질 수 있음)을 가지며 각 객체에 대한 추가 메모리가 있습니다. 메모리를 인식 map해야하는 경우 큰 어레이가 없기 때문에 더 나은 것으로 입증해야합니다.

따라서 순수한 조회 검색이 필요한 경우 unordered_map갈 길입니다. 그러나 항상 상충 관계가 있으며, 감당할 수 없다면 사용할 수 없습니다.

개인적인 경험을 바탕으로, 주요 엔티티 룩업 테이블 unordered_map대신 사용할 때 성능이 크게 향상되었습니다 (물론 측정 됨) map.

반면에 반복적으로 요소를 삽입하고 제거하는 것이 훨씬 느리다는 것을 알았습니다. 비교적 정적 인 요소 컬렉션에는 유용하지만 많은 삽입 및 삭제를 수행하는 경우 해싱 + 버킷 팅이 추가되는 것으로 보입니다. (이것은 여러 번 반복되었습니다.)


3
unordered_map vs. map (또는 vector vs list)의 큰 (r) 메모리 블록 속성에 대한 한 가지 더, 기본 프로세스 힙 (여기서 말하는 Windows)은 직렬화됩니다. 다중 스레드 응용 프로그램에서 대량으로 (작은) 블록을 할당하는 것은 매우 비쌉니다.
ROAR

4
RA : 특정 프로그램에 문제가 있다고 생각되면 컨테이너와 결합 된 자신의 할당 자 유형을 사용하여 다소 제어 할 수 있습니다.

9
당신이 크기를 알고 unordered_map처음에 그것을 예약한다면-당신은 여전히 ​​많은 삽입에 대한 벌금을 지불합니까? 예를 들어, 룩업 테이블을 작성할 때 한 번만 삽입 한 다음 나중에 읽어야합니다.
thomthom

3
@thomthom 내가 알 수있는 한, 성능면에서 페널티가 없어야합니다. 성능에 영향을 미치는 이유는 배열이 너무 커지면 모든 요소를 ​​다시 해시하기 때문입니다. reserve를 호출하면 기존 요소를 잠재적으로 다시 해시하지만 시작시 호출하면 적어도 cplusplus.com/reference/unordered_map/unordered_map/reserve
Richard Fung

6
메모리 측면에서는 반대라고 확신합니다. 순서가 지정되지 않은 컨테이너에 대한 기본 1.0로드 팩터를 가정하면 버킷에 대한 요소 당 하나의 포인터와 버킷에있는 다음 요소에 대한 요소 당 하나의 포인터가 있으므로 각 요소 당 두 개의 포인터와 데이터가 추가됩니다. 반면, 주문 된 컨테이너의 경우 일반적인 RB- 트리 구현에는 세 개의 포인터 (왼쪽 / 오른쪽 / 부모)와 정렬로 인해 색상 단어가 사용됩니다. 즉, 각 요소 당 4 개의 포인터와 데이터가 있습니다.
Yakov Galka

126

구현 속도 std::mapstd::unordered_map구현 속도를 비교하려면 time_hash_map 프로그램이있는 Google의 sparsehash 프로젝트를 사용 하여 시간을 측정 할 수 있습니다. 예를 들어, x86_64 Linux 시스템에서 gcc 4.4.2 사용

$ ./time_hash_map
TR1 UNORDERED_MAP (4 byte objects, 10000000 iterations):
map_grow              126.1 ns  (27427396 hashes, 40000000 copies)  290.9 MB
map_predict/grow       67.4 ns  (10000000 hashes, 40000000 copies)  232.8 MB
map_replace            22.3 ns  (37427396 hashes, 40000000 copies)
map_fetch              16.3 ns  (37427396 hashes, 40000000 copies)
map_fetch_empty         9.8 ns  (10000000 hashes,        0 copies)
map_remove             49.1 ns  (37427396 hashes, 40000000 copies)
map_toggle             86.1 ns  (20000000 hashes, 40000000 copies)

STANDARD MAP (4 byte objects, 10000000 iterations):
map_grow              225.3 ns  (       0 hashes, 20000000 copies)  462.4 MB
map_predict/grow      225.1 ns  (       0 hashes, 20000000 copies)  462.6 MB
map_replace           151.2 ns  (       0 hashes, 20000000 copies)
map_fetch             156.0 ns  (       0 hashes, 20000000 copies)
map_fetch_empty         1.4 ns  (       0 hashes,        0 copies)
map_remove            141.0 ns  (       0 hashes, 20000000 copies)
map_toggle             67.3 ns  (       0 hashes, 20000000 copies)

2
정렬되지 않은 맵은 대부분의 작업에서 맵을 능가하는 것처럼 보입니다. 삽입시 이벤트 ...
Michael IV

7
sparsehash는 더 이상 존재하지 않습니다. 삭제되었거나 삭제되었습니다.
User9102d82

1
@ User9102d82 waybackmachine 링크 를 참조하도록 질문을 편집했습니다 .
andreee

다른 사람들도 시간 이외의 다른 숫자도 알아 차릴 수 있도록하기 위해 : 이러한 테스트는 int와 같은 4 바이트 객체 / 데이터 구조로 수행되었습니다. 더 무거운 해싱이 필요하거나 더 큰 것을 저장하면 (복사 작업이 더 무거워 짐) 표준 맵이 빠르게 유리할 수 있습니다!
AlexGeorg

82

GMan이 만든 것과 거의 같은 점을 에코합니다. 사용 유형에 따라 (VS 2008 SP1에 포함 된 구현 사용 std::map)보다 빠를 수 있습니다 std::tr1::unordered_map.

명심해야 할 몇 가지 복잡한 요소가 있습니다. 예를 들어에서에서 std::map키를 비교하고 있습니다. 즉, 키의 시작 부분 만보고 트리의 오른쪽과 왼쪽 하위 브랜치를 구분할 수 있습니다. 내 경험상, 전체 키를 볼 때 거의 유일한 시간은 단일 명령으로 비교할 수있는 int와 같은 것을 사용하는 경우입니다. std :: string과 같은 더 일반적인 키 유형을 사용하면 종종 몇 문자 정도만 비교합니다.

대조적으로 적절한 해시 함수는 항상 전체 키를 봅니다. IOW는 테이블 조회가 일정한 복잡성 임에도 불구하고 해시 자체는 대략 선형 복잡성을가집니다 (물건의 수가 아니라 키의 길이에 따라). 키와 긴 문자열와 더불어,이 std::map전에 검색을 끝낼 수있는 unordered_map경우에도 것입니다 시작 의 검색을.

해시 테이블의 크기를 조정의 여러 가지 방법이 있지만 둘째, 그들의 대부분은 매우 느리게 - 조회를하지 않는 한 그 점에 상당히 삽입과 삭제에 비해 더 자주, 표준 : :지도는 종종보다 더 빨리 될 것입니다 std::unordered_map.

물론 이전 질문에 대한 의견에서 언급했듯이 나무 테이블을 사용할 수도 있습니다. 여기에는 장단점이 있습니다. 한편으로는 최악의 경우를 나무의 경우로 제한합니다. 또한 (적어도 그것을 할 때) 고정 크기의 테이블을 사용했기 때문에 빠른 삽입 및 삭제가 가능합니다. 모든 테이블 크기 조정을 제거 하면 해시 테이블을 훨씬 간단하고 일반적으로 더 빠르게 유지할 수 있습니다.

또 다른 요점 : 해싱 및 트리 기반 맵에 대한 요구 사항이 다릅니다. 해싱에는 분명히 해시 함수와 등식 비교가 필요하며, 순서 맵은 비교가 덜 필요합니다. 물론 제가 언급 한 하이브리드에는 두 가지가 모두 필요합니다. 물론 문자열을 키로 사용하는 일반적인 경우에는 이것이 문제가되지 않지만 일부 유형의 키는 해싱보다 순서가 적합합니다 (또는 그 반대).


2
해시 크기 조정은 dynamic hashing항목에 삽입 할 때마다 k다른 항목을 다시 해시하는 전환 기간이 포함 된 기술에 의해 감소 ​​될 수 있습니다 . 물론, 전환하는 동안 2 개의 다른 테이블을 검색해야합니다.
Matthieu M.

2
"길이가 긴 문자열 인 std :: map은 unorder_map이 검색을 시작하기 전에 검색을 완료 할 수 있습니다." -키가 컬렉션에없는 경우 그것이 존재한다면 물론 전체 길이를 비교하여 일치를 확인해야합니다. 그러나 마찬가지로 unordered_map전체 비교와 해시 일치를 확인해야하므로 대조하는 조회 프로세스의 부분에 따라 다릅니다.
Steve Jessop

2
일반적으로 데이터 지식을 기반으로 해시 함수를 대체 할 수 있습니다. 예를 들어 긴 문자열이 처음 100 개보다 마지막 20 바이트에서 더 많이
변하면

56

@Jerry Coffin의 답변에 흥미를 느꼈습니다. 순서가 있는지도는 일부 실험 ( pastbin 에서 다운로드 할 수 있음) 후에 긴 문자열에서 성능이 향상 될 것이라고 제안했습니다. 이 컬렉션에만 적용되는 것으로 나타났습니다 임의의 문자열의 경우, 맵이 정렬 된 사전 (상당히 많은 양의 접두사-오버랩이있는 단어를 포함)으로 초기화 될 때이 규칙은 아마도 값을 검색하는 데 필요한 트리 깊이가 증가했기 때문에 분류됩니다. 결과는 다음과 같습니다. 첫 번째 숫자 열은 삽입 시간이고 두 번째는 가져 오기 시간입니다.

g++ -g -O3 --std=c++0x   -c -o stdtests.o stdtests.cpp
g++ -o stdtests stdtests.o
gmurphy@interloper:HashTests$ ./stdtests
# 1st number column is insert time, 2nd is fetch time
 ** Integer Keys ** 
 unordered:      137      15
   ordered:      168      81
 ** Random String Keys ** 
 unordered:       55      50
   ordered:       33      31
 ** Real Words Keys ** 
 unordered:      278      76
   ordered:      516     298

2
테스트 주셔서 감사합니다. 노이즈를 측정하지 않도록 각 작업을 여러 번 수행하도록 변경했습니다 (1 대신 카운터를 맵에 삽입). 나는 맵에서 다른 수의 키 (2에서 1000까지)와 최대 ~ 100 개의 키를 통해 실행했습니다. std::map일반적으로 std::unordered_map정수 키의 경우 성능이 뛰어나지 만 ~ 100 키는 가장자리를 잃고 std::unordered_map승리하기 시작합니다. 이미 순서가 지정된 시퀀스를 삽입하는 std::map것은 매우 나쁩니다. 최악의 시나리오 (O (N))가 나타납니다.
Andreas Magnusson

30

나는 단지 지적 할 것입니다 ... 많은 종류가 unordered_map있습니다.

해시 맵 에서 Wikipedia Article 을 찾으십시오 . 사용 된 구현에 따라 조회, 삽입 및 삭제 측면의 특성이 상당히 다를 수 있습니다.

그리고 이것이 unordered_mapSTL을 추가하면서 가장 걱정되는 부분입니다 . 그들은 Policy길을 갈 것 같지 않은 특정 구현을 선택 해야 할 것이므로 평균적인 사용을위한 구현에 갇히지 않을 것입니다. 다른 경우들 ...

예를 들어 일부 해시 맵에는 선형 해시가 있습니다. 여기서 전체 해시 맵을 한 번에 다시 해시하는 대신 각 삽입시 부분이 다시 해시되어 비용을 상쇄하는 데 도움이됩니다.

또 다른 예 : 일부 해시 맵은 버킷에 간단한 노드 목록을 사용하고, 다른 맵은 노드를 사용하지 않고 가장 가까운 슬롯을 찾고 마지막으로 노드 목록을 사용하지만 마지막으로 액세스 한 요소를 다시 정렬합니다 캐싱과 같은 전면에 있습니다.

따라서 현재는 std::map또는 loki::AssocVector고정 된 데이터 세트의 경우를 선호하는 경향이 있습니다.

나를 잘못 이해하지 말고 사용하고 싶습니다 std::unordered_map. 앞으로는 그 컨테이너의 이식성을 구현하는 모든 방법과 그로 인한 다양한 성능을 생각할 때 이러한 컨테이너의 이식성을 "신뢰"하기가 어렵습니다. 이의.


17
1 : 유효한 점 - 나는 내 자신의 구현을 사용했을 때 생활은 쉬웠다 - 적어도 내가 아는 이 빨려>
코넬 Kisielewicz

25

여기에 실제로 언급되지 않은 중요한 차이점이 있습니다.

  • map반복자를 모든 요소에 안정적으로 유지합니다. C ++ 17 map에서는 반복자를 무효화하지 않고 요소를 다른 요소로 옮길 수 있습니다 (잠재적 할당없이 올바르게 구현 된 경우).
  • map 단일 작업의 타이밍은 일반적으로 큰 할당이 필요하지 않기 때문에보다 일관됩니다.
  • unordered_mapstd::hashlibstdc ++에서 구현 된대로 사용하는 것은 신뢰할 수없는 입력이 공급되면 DoS에 취약합니다 (MurmurHash2를 일정한 시드로 사용합니다-시드가 실제로 도움이되지는 않습니다. https://emboss.github.io/blog/2012/12/14/ 파괴 중얼 거림 해시 홍수 도스 리로드 / ).
  • 순서를 지정하면 효율적인 범위 검색이 가능합니다 (예 : 키가 ≥ 42 인 모든 요소를 ​​반복).

14

해시 테이블은 공통 맵 구현보다 더 높은 상수를 가지며 이는 작은 컨테이너에 중요합니다. 최대 크기는 10, 100 또는 1,000 이상입니까? 상수는 이전과 동일하지만 O (log n)은 O (k)에 가깝습니다. (로그 복잡도는 여전히 정말 좋습니다.)

좋은 해시 함수를 만드는 것은 데이터의 특성에 달려 있습니다. 따라서 사용자 지정 해시 함수를 보지 않으려는 경우 (그러나 나중에 모든 것을 가까이에서 typedef하기 때문에 마음이 바뀌고 나중에 쉽게 마음을 바꿀 수 있음) 많은 데이터 소스에 대해 기본값이 적절하게 수행되도록 선택되었지만 순서가 있습니다. map의 본질은 처음에는 해시 테이블이 아니라 매핑하는 기본 설정으로 충분합니다.

또한 다른 (보통 UDT) 유형의 해시 함수 작성에 대해 생각할 필요가 없으며 op <(어쨌든 원하는)을 작성하십시오.


@Roger, unorder_map이 가장 적합한 요소의 대략적인 양을 알고 있습니까? 어쨌든 나는 아마도 그것을 위해 테스트를 쓸 것이다 ... (+1)
Kornel Kisielewicz

1
@Kornel : 많이 걸리지 않습니다. 내 테스트는 약 10,000 요소로 이루어졌습니다. 정말 정확한 그래프를 원한다면 특정 플랫폼과 특정 캐시 크기 로 구현 map중 하나를 구현 unordered_map하고 복잡한 분석을 수행 할 수 있습니다. : P
GManNickG

구현 세부 사항, 컴파일 타임 튜닝 매개 변수 (자신의 구현을 작성하는 경우 쉽게 지원 가능) 및 테스트에 사용되는 특정 시스템에 따라 다릅니다. 다른 컨테이너와 마찬가지로위원회는 광범위한 요구 사항 만 설정합니다.

13

다른 답변에서 이유가 제시되었습니다. 여기 또 다른 것이 있습니다.

std :: map (balanced binary tree) 연산은 O (log n)와 최악의 경우 O (log n)로 상각됩니다. std :: unorder_map (해시 테이블) 작업은 O (1)로 분류되고 최악의 경우 O (n)으로 상각됩니다.

이것이 실제로 실행되는 방법은 해시 테이블이 O (n) 연산을 사용하여 가끔씩 "히치"하는 것인데, 이는 응용 프로그램이 허용 할 수있는 것이거나 아닐 수도 있습니다. 허용되지 않으면 std :: unordered_map보다 std :: map을 선호합니다.


12

요약

순서가 중요하지 않다고 가정합니다.

  • 큰 테이블을 한 번 작성하고 많은 쿼리를 수행하려는 경우 std::unordered_map
  • 작은 테이블을 작성하고 (100 요소 미만일 수 있음) 많은 쿼리를 수행하려면을 사용하십시오 std::map. 이것에 대한 읽기 때문 O(log n)입니다.
  • 테이블을 많이 변경하려는 경우 좋은 옵션 일 수 있습니다 std::map .
  • 확실하지 않은 경우을 사용하십시오 std::unordered_map.

역사적 맥락

대부분의 언어에서 정렬되지 않은 맵 (일명 해시 기반 사전)이 기본 맵이지만 C ++에서는 맵을 기본 맵으로 정렬합니다. 어떻게 된거 지? 어떤 사람들은 C ++위원회가 자신의 고유 한 지혜로이 결정을 내렸다고 잘못 생각하지만 진실은 불행히도 그보다 더 추악합니다.

C ++은 구현 방법에 대한 매개 변수가 너무 많지 않기 때문에 기본적으로 정렬 된 맵으로 끝났다고 널리 알려져 있습니다. 반면 해시 기반 구현에는 수많은 이야기가 있습니다. 따라서 표준화에서 그리드 락을 피하기 위해 순서 맵 함께했습니다. 2005 년경, 많은 언어들이 이미 해시 기반 구현을 잘 구현 했으므로위원회가 새로운 것을 쉽게 받아 들일 수 std::unordered_map있었습니다. 완벽한 세상에서는 std::map질서가 없었을 것이고 우리는 std::ordered_map별개의 유형 이 될 것 입니다.

공연

아래 두 그래프는 스스로를 말해야합니다 ( source ).

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

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


흥미로운 데이터; 테스트에 몇 개의 플랫폼을 포함 시켰습니까?
Toby Speight

1
std :: unalign_map이 여기에 게시 한 2 개의 이미지에 따라 항상 std :: map보다 성능이 우수하므로 많은 쿼리를 수행 할 때 작은 테이블에 std :: map을 사용해야하는 이유는 무엇입니까?
ricky December

그래프는 0.13M 이상의 요소에 대한 성능을 보여줍니다. 작은 (<100) 요소가 있으면 O (log n)이 정렬되지 않은 맵보다 작아 질 수 있습니다.
Shital Shah

10

최근에 50000 병합 및 정렬을 만드는 테스트를 수행했습니다. 즉, 문자열 키가 동일하면 바이트 문자열을 병합하십시오. 그리고 최종 출력이 정렬되어야합니다. 따라서 여기에는 모든 삽입에 대한 조회가 포함됩니다.

를 들어 map구현, 작업을 완료하기 위해 200 밀리합니다. 들어 unordered_map+ map, 이는 70 밀리 얻어 unordered_map삽입 및 80 밀리 map삽입. 따라서 하이브리드 구현은 50ms 더 빠릅니다.

를 사용하기 전에 두 번 생각해야합니다 map. 프로그램의 최종 결과에서 데이터를 정렬하기 만하면 하이브리드 솔루션이 더 나을 수 있습니다.


0

위의 모든 것에 작은 추가 :

map범위별로 요소를 정렬해야 할 때 더 잘 사용 하면 요소를 한 경계에서 다른 경계로 반복 할 수 있습니다.


-1

발신 : http://www.cplusplus.com/reference/map/map/

"내부적으로지도의 요소는 내부 비교 객체 (비교 유형)로 표시되는 특정 엄격한 약한 정렬 기준에 따라 항상 키를 기준으로 정렬됩니다.

맵 컨테이너는 일반적으로 키로 개별 요소에 액세스하기 위해 unorder_map 컨테이너보다 느리지 만 순서에 따라 서브 세트에서 직접 반복 할 수 있습니다. "

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.