사소한 키의 경우 unorder_map보다 map을 사용하면 어떤 이점이 있습니까?
unordered_map
C ++에서 최근에 한 이야기 는 조회 효율 ( 상각 O (1) 대 O (log n) ) 때문에 이전에 unordered_map
사용했던 대부분의 경우에 사용해야한다는 것을 깨달았습니다 . 나는지도를 사용하는 대부분의 시간, 나는 하나를 사용 하거나 키 유형으로; 따라서 해시 함수 정의에 아무런 문제가 없습니다. 내가 그것에 대해 더 많이 생각할수록 간단한 유형의 키의 경우 over 를 사용하는 이유를 찾을 수 없다는 것을 깨닫게 되었습니다. 인터페이스를 보았고 찾지 못했습니다. 내 코드에 영향을 미치는 중요한 차이점.map
int
std::string
std::map
std::unordered_map
따라서 질문 : 사용하는 실제 이유가 std::map
이상 std::unordered map
같은 간단한 유형의 경우 int
와는 std::string
?
엄격하게 프로그래밍 관점에서 묻습니다. 표준으로 완전히 간주되지 않았으며 이식에 문제가 발생할 수 있음을 알고 있습니다.
또한 정답 중 하나가 더 작은 오버 헤드로 인해 "더 작은 데이터 세트에 더 효율적" 일 것으로 기대합니다 (그렇습니까?). 따라서 질문의 양을 키는 중요하지 않습니다 (> 1 024).
편집 : 야 , 나는 명백한 것을 잊었다 (GMan 덕분에!)-그렇다.지도는 물론 주문된다. 나는 그것을 알고 있으며 다른 이유를 찾고있다.
map
그 요소를 순서대로 유지하는 것을 잊지 마십시오 . 당신이 그것을 포기할 수 없다면, 분명히 당신은 사용할 수 없습니다 unordered_map
.
명심해야 할 것은 unordered_map
일반적으로 더 많은 메모리를 사용 한다는 것입니다 . map
하우스 키핑 포인터와 각 객체에 대한 메모리 만 있습니다. 반대로, unordered_map
큰 배열 (일부 구현에서는 상당히 커질 수 있음)이 있고 각 객체에 대한 추가 메모리가 있습니다. 메모리를 인식 map
해야하는 경우 큰 어레이가 없기 때문에 더 나은 것으로 입증해야합니다.
따라서 순수 조회 검색이 필요한 경우 unordered_map
갈 길입니다. 그러나 항상 상충 관계가 있으며, 감당할 수 없다면 사용할 수 없습니다.
개인적인 경험을 바탕 으로 주요 엔티티 룩업 테이블 unordered_map
대신 사용할 때 성능이 크게 향상되었습니다 (물론 측정 됨) map
.
반면에 반복적으로 요소를 삽입하고 제거하는 것이 훨씬 느리다는 것을 알았습니다. 상대적으로 정적 인 요소 컬렉션에는 유용하지만 많은 삽입 및 삭제를 수행하는 경우 해싱 + 버킷 팅이 추가되는 것으로 보입니다. (이것은 여러 번 반복되었습니다.)
구현 속도 std::map
와 std::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)
GMan이 만든 것과 거의 같은 점을 에코합니다. 사용 유형에 따라 (VS 2008 SP1에 포함 된 구현 사용 std::map
)보다 빠를 수 있습니다 std::tr1::unordered_map
.
명심해야 할 몇 가지 복잡한 요소가 있습니다. 예를 들어에서에서 std::map
키를 비교하고 있습니다. 즉, 키의 시작 부분 만보고 트리의 오른쪽과 왼쪽 하위 브랜치를 구분할 수 있습니다. 내 경험상, 전체 키를 볼 때 거의 유일한 시간은 단일 명령으로 비교할 수있는 int와 같은 것을 사용하는 경우입니다. std :: string과 같은 더 일반적인 키 유형을 사용하면 종종 몇 문자 정도만 비교합니다.
대조적으로 적절한 해시 함수는 항상 전체 키를 봅니다. IOW는 테이블 조회가 일정한 복잡성 임에도 불구하고 해시 자체는 대략 선형 복잡성을가집니다 (물건의 수가 아니라 키의 길이에 따라). 키와 긴 문자열와 더불어,이 std::map
전에 검색을 끝낼 수있는 unordered_map
경우에도 것입니다 시작 의 검색을.
해시 테이블의 크기를 조정의 여러 가지 방법이 있지만 둘째, 그들의 대부분은 매우 느리게 - 조회를하지 않는 한 그 점에 상당히 삽입과 삭제에 비해 더 자주, 표준 : :지도는 종종보다 더 빨리 될 것입니다 std::unordered_map
.
물론 이전 질문에 대한 의견에서 언급했듯이 나무 테이블을 사용할 수도 있습니다. 여기에는 장단점이 있습니다. 한편으로는 최악의 경우를 나무의 경우로 제한합니다. 또한 (적어도 그것을했을 때) 고정 크기의 테이블을 사용했기 때문에 빠른 삽입 및 삭제가 가능합니다. 모든 테이블 크기 조정을 제거 하면 해시 테이블을 훨씬 간단하고 일반적으로 더 빠르게 유지할 수 있습니다.
또 다른 요점 : 해싱 및 트리 기반 맵에 대한 요구 사항이 다릅니다. 해싱에는 분명히 해시 함수와 등식 비교가 필요하며, 순서 맵은 비교가 덜 필요합니다. 물론 제가 언급 한 하이브리드에는 두 가지가 모두 필요합니다. 물론 문자열을 키로 사용하는 일반적인 경우에는 이것이 문제가되지 않지만 일부 유형의 키는 해싱보다 순서가 적합합니다 (또는 그 반대).
@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
나는 단지 지적 할 것입니다 ... 많은 종류가 unordered_map
있습니다.
해시 맵 에서 Wikipedia Article 을 찾으십시오 . 사용 된 구현에 따라 조회, 삽입 및 삭제 측면의 특성이 상당히 다를 수 있습니다.
그리고 이것이 unordered_map
STL을 추가하면서 가장 걱정되는 부분입니다 . 그들은 Policy
길을 갈 것 같지 않은 특정 구현을 선택 해야 할 것이므로 평균적인 사용을위한 구현에 갇히지 않을 것입니다. 다른 경우들 ...
예를 들어 일부 해시 맵에는 선형 리 해싱이 있습니다. 여기서 전체 해시 맵을 한 번에 다시 해시하는 대신 각 삽입시 부분이 다시 해시되어 비용을 상쇄하는 데 도움이됩니다.
또 다른 예 : 일부 해시 맵은 버킷에 간단한 노드 목록을 사용하고, 다른 맵은 노드를 사용하지 않고 가장 가까운 슬롯을 찾고 마지막으로 노드 목록을 사용하지만 마지막으로 액세스 한 요소를 다시 정렬합니다 캐싱과 같은 전면에 있습니다.
따라서 현재 나는 ( std::map
또는 loki::AssocVector
고정 된 데이터 세트의 경우) 선호하는 경향이 있습니다.
나를 잘못 이해하지 말고 사용하고 싶습니다 std::unordered_map
. 앞으로는 그 컨테이너의 이식성을 구현하는 모든 방법과 그로 인한 다양한 성능을 생각할 때 이러한 컨테이너의 이식성을 "신뢰하기"가 어렵습니다. 이의.
여기에 실제로 언급되지 않은 중요한 차이점이 있습니다.
map
반복자를 모든 요소에 안정적으로 유지합니다. C ++ 17map
에서는 반복자를 무효화하지 않고 요소를 다른 요소로 옮길 수 있습니다 (잠재적 할당없이 올바르게 구현 된 경우).map
단일 작업의 타이밍은 일반적으로 큰 할당이 필요하지 않기 때문에보다 일관됩니다.unordered_map
std::hash
libstdc ++에서 구현 된대로 사용하는 것은 신뢰할 수없는 입력을 먹인 경우 DoS에 취약합니다 (MurmurHash2를 일정한 시드로 사용합니다-시드가 실제로 도움이되지는 않습니다. https://emboss.github.io/blog/2012/12/14/ 파괴 중얼 거림 해시 홍수 도스 다시로드 / ).- 순서를 지정하면 효율적인 범위 검색이 가능합니다 (예 : 키가 ≥ 42 인 모든 요소를 반복).
해시 테이블은 공통 맵 구현보다 더 높은 상수를 가지며 이는 작은 컨테이너에 중요합니다. 최대 크기는 10, 100 또는 1,000 이상입니까? 상수는 이전과 동일하지만 O (log n)은 O (k)에 가깝습니다. (로그 복잡도는 여전히 정말 좋습니다.)
좋은 해시 함수를 만드는 것은 데이터의 특성에 달려 있습니다. 따라서 사용자 지정 해시 함수를 보지 않으려는 경우 (그러나 나중에 모든 것을 가까이서 망칠 수 있기 때문에 마음이 바뀌고 나중에 쉽게 마음을 바꿀 수 있습니다) 많은 데이터 소스에 대해 기본값이 적절하게 수행되도록 선택되었지만 순서가 있습니다. map의 특성은 처음에는 여전히 해시 테이블이 아니라 매핑하는 기본 설정으로 충분합니다.
또한 다른 (보통 UDT) 유형에 대한 해시 함수 작성에 대해 생각할 필요가 없으며 op <(어쨌든 원하는)을 작성하십시오.
최근에 50000 병합 및 정렬을 만드는 테스트를 수행했습니다. 즉, 문자열 키가 동일하면 바이트 문자열을 병합하십시오. 그리고 최종 결과물이 정렬되어야합니다. 따라서 여기에는 모든 삽입에 대한 조회가 포함됩니다.
를 들어 map
구현, 작업을 완료하기 위해 200 밀리합니다. 들어 unordered_map
+ map
, 이는 70 밀리 얻어 unordered_map
삽입 및 80 밀리 map
삽입. 따라서 하이브리드 구현은 50ms 더 빠릅니다.
를 사용하기 전에 두 번 생각해야합니다 map
. 프로그램의 최종 결과에서 데이터를 정렬하기 만하면 하이브리드 솔루션이 더 나을 수 있습니다.
다른 답변에서 이유가 제시되었습니다. 여기 또 다른 것이 있습니다.
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을 선호합니다.
요약
순서가 중요하지 않다고 가정합니다.
- 큰 테이블을 한 번 작성하고 많은 쿼리를 수행하려는 경우
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 ).
위의 모든 것에 작은 추가 :
map
범위별로 요소를 정렬해야 할 때 더 잘 사용 하고 요소를 한 경계에서 다른 경계로 반복 할 수 있습니다.
에서 : http://www.cplusplus.com/reference/map/map/
"내부적으로지도의 요소는 내부 비교 객체 (비교 유형)로 표시되는 특정 엄격한 약한 정렬 기준에 따라 항상 키를 기준으로 정렬됩니다.
맵 컨테이너는 일반적으로 키로 개별 요소에 액세스하기 위해 unorder_map 컨테이너보다 느리지 만 순서에 따라 서브 세트에서 직접 반복 할 수 있습니다. "
'Programing' 카테고리의 다른 글
인쇄용 인라인 if 문을 작성하는 방법은 무엇입니까? (0) | 2020.03.04 |
---|---|
Python에서 현재 스크립트 이름 가져 오기 (0) | 2020.03.04 |
SQL Server에서 데이터베이스 목록 가져 오기 (0) | 2020.03.03 |
쉘에서 한 줄에 여러 명령 실행 (0) | 2020.03.03 |
자식 추적에서 폴더 제거 (0) | 2020.03.03 |