우리는 C ++로 고성능 핵심 소프트웨어를 개발하고 있습니다. 거기에 동시 해시 맵이 필요하고 구현되었습니다. 그래서 우리는 동시 해시 맵이 .NET과 비교하여 얼마나 느린 지 알아 내기 위해 벤치 마크를 작성했습니다 std::unordered_map
.
그러나 std::unordered_map
엄청나게 느린 것 같습니다 ... 그래서 이것은 우리의 마이크로 벤치 마크입니다 google::dense_hash_map
. null 값이 필요함) :
boost::random::mt19937 rng;
boost::random::uniform_int_distribution<> dist(std::numeric_limits<uint64_t>::min(), std::numeric_limits<uint64_t>::max());
std::vector<uint64_t> vec(SIZE);
for (int i = 0; i < SIZE; ++i) {
uint64_t val = 0;
while (val == 0) {
val = dist(rng);
}
vec[i] = val;
}
std::unordered_map<int, long double> map;
auto begin = std::chrono::high_resolution_clock::now();
for (int i = 0; i < SIZE; ++i) {
map[vec[i]] = 0.0;
}
auto end = std::chrono::high_resolution_clock::now();
auto elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin);
std::cout << "inserts: " << elapsed.count() << std::endl;
std::random_shuffle(vec.begin(), vec.end());
begin = std::chrono::high_resolution_clock::now();
long double val;
for (int i = 0; i < SIZE; ++i) {
val = map[vec[i]];
}
end = std::chrono::high_resolution_clock::now();
elapsed = std::chrono::duration_cast<std::chrono::milliseconds>(end - begin);
std::cout << "get: " << elapsed.count() << std::endl;
(편집 : 전체 소스 코드는 여기에서 찾을 수 있습니다 : http://pastebin.com/vPqf7eya )
결과 std::unordered_map
는 다음과 같습니다.
inserts: 35126
get : 2959
대상 google::dense_map
:
inserts: 3653
get : 816
수작업으로 지원되는 동시 맵의 경우 (잠금을 수행하지만 벤치 마크는 단일 스레드이지만 별도의 스폰 스레드에 있음) :
inserts: 5213
get : 2594
pthread 지원없이 벤치 마크 프로그램을 컴파일하고 메인 스레드에서 모든 것을 실행하면 수동으로 지원되는 동시 맵에 대해 다음과 같은 결과가 나타납니다.
inserts: 4441
get : 1180
다음 명령으로 컴파일합니다.
g++-4.7 -O3 -DNDEBUG -I/tmp/benchmap/sparsehash-2.0.2/src/ -std=c++11 -pthread main.cc
따라서 특히 삽입물은 std::unordered_map
매우 비쌉니다. 다른지도의 경우 35 초 대 3-5 초입니다. 또한 조회 시간이 상당히 높은 것 같습니다.
내 질문 : 왜 그렇습니까? 누군가가 std::tr1::unordered_map
자신의 구현보다 느린 이유를 묻는 stackoverflow에 대한 또 다른 질문을 읽었습니다 . 가장 높은 등급의 답변 std::tr1::unordered_map
은 더 복잡한 인터페이스를 구현해야한다는 것입니다. 그러나 나는이 주장을 볼 수 없다 : 우리 std::unordered_map
는 Concurrent_map에서 버킷 접근 방식을 사용하고, 버킷 접근 방식도 사용한다 ( google::dense_hash_map
그렇지 않지만 std::unordered_map
적어도 우리의 손으로 지원하는 동시성 안전 버전보다 빠르다?). 그 외에는 인터페이스에서 해시 맵의 성능을 저하시키는 기능을 강제하는 것을 볼 수 없습니다.
그래서 내 질문 : std::unordered_map
매우 느리게 보이는 것이 사실 입니까? 아니오 인 경우 : 무엇이 잘못 되었습니까? 그렇다면 : 그 이유는 무엇입니까?
그리고 내 주요 질문 : 왜 값을 std::unordered_map
끔찍한 비용 으로 삽입하는 것입니까 (처음에 충분한 공간을 예약하더라도 훨씬 더 잘 수행되지 않으므로 다시 해싱이 문제가 아닌 것 같습니다)?
편집하다:
우선 : 예, 제시된 벤치 마크는 완벽하지 않습니다. 이것은 우리가 그것을 많이 가지고 놀았고 단지 해킹이기 때문입니다 (예를 들어 uint64
int를 생성 하는 배포는 실제로 좋은 생각이 아닙니다. 루프에서 0을 제외합니다. 어리석은 등 ...).
현재 대부분의 댓글은 충분한 공간을 미리 할당하여 unorder_map을 더 빠르게 만들 수 있다고 설명합니다. 우리 애플리케이션에서는 이것이 불가능합니다. 데이터베이스 관리 시스템을 개발 중이며 트랜잭션 중에 일부 데이터 (예 : 잠금 정보)를 저장할 해시 맵이 필요합니다. 따라서이 맵은 1 (사용자가 하나의 삽입 및 커밋 만 수행)에서 수십억 개의 항목 (전체 테이블 스캔이 발생하는 경우)에 이르기까지 모든 것이 될 수 있습니다. 여기에 충분한 공간을 미리 할당하는 것은 불가능합니다 (처음에 많이 할당하면 너무 많은 메모리가 소모됩니다).
또한, 나는 내 질문을 충분히 명확하게 말하지 않은 것에 대해 사과드립니다. 나는 unorder_map을 빠르게 만드는 데 정말로 관심이 없습니다 (구글의 고밀도 해시 맵을 사용하면 잘 작동합니다).이 거대한 성능 차이가 어디서 오는지 실제로 이해하지 못합니다. . 사전 할당 일 수는 없습니다 (사전 할당 된 메모리가 충분하더라도 조밀 한 맵은 unordered_map보다 훨씬 빠르며, 손으로 지원하는 동시 맵은 64 크기의 배열로 시작하므로 unorder_map보다 작은 것입니다).
그래서이 나쁜 성능의 이유는 무엇 std::unordered_map
입니까? 또는 다르게 질문 : std::unordered_map
표준 준수 및 (거의) Google의 고밀도 해시 맵만큼 빠른 인터페이스 구현을 작성할 수 있습니까? 아니면 표준에 구현자가이를 구현하기 위해 비효율적 인 방법을 선택하도록 강제하는 것이 있습니까?
편집 2 :
프로파일 링을 통해 정수 분할에 많은 시간이 사용된다는 것을 알 수 있습니다. std::unordered_map
배열 크기에 소수를 사용하는 반면 다른 구현은 2의 거듭 제곱을 사용합니다. std::unordered_map
소수를 사용하는 이유는 무엇 입니까? 해시가 나쁘면 더 잘 수행하려면? 좋은 해시의 경우 imho는 아무런 차이가 없습니다.
편집 3 :
다음에 대한 숫자는 std::map
다음과 같습니다.
inserts: 16462
get : 16978
Sooooooo : 왜 삽입이 a에 삽입하는 std::map
것보다 빠른 이유는 std::unordered_map
... WAT를 의미합니까? std::map
지역성이 더 나쁘고 (트리 대 배열), 더 많은 할당 (삽입 대 리해 시당 + 각 충돌에 대해 ~ 1)을 만들어야하며 가장 중요한 것은 다른 알고리즘 복잡성 (O (logn) 대 O (1))이 있습니다!
SIZE
입니다.