Kiedy używanie std:: multimap ma sens

Obecnie eksperymentuję nad wykorzystaniem stl-datastructures. Jednak nadal nie jestem pewien, kiedy użyć którego i kiedy użyć określonej kombinacji. Obecnie próbuję się dowiedzieć, czy użycie std::multimap ma sens. Z tego, co widzę, można łatwo zbudować własną implementację multimap poprzez połączenie std::map i std::vector. Pozostaje mi więc pytanie, Kiedy należy użyć każdej z tych struktur danych.

  • Simplicity: a STD:: multimap jest zdecydowanie prostszy do użyj, ponieważ nie trzeba obsługiwać dodatkowego zagnieżdżania. Jednak dostęp do szeregu elementów jako luzem może być konieczne skopiowanie danych z iteratorów do innej struktury danych (na przykład std::vector).
  • prędkość: położenie wektora najprawdopodobniej sprawia, że iteracja w zakresie równego elementu jest znacznie szybsza, ponieważ użycie pamięci podręcznej jest zoptymalizowane. Jednak domyślam się, że std::multimaps mają również wiele trików optymalizacyjnych za plecami, aby iterację nad równymi elementami jako jak najszybciej. Również dotarcie do właściwego zakresu elementów może być zoptymalizowane dla std::multimaps.

Aby wypróbować problemy z prędkością zrobiłem kilka prostych porównań za pomocą następującego programu:

#include <stdint.h>
#include <iostream>
#include <map>
#include <vector>
#include <utility>

typedef std::map<uint32_t, std::vector<uint64_t> > my_mumap_t;

const uint32_t num_partitions = 100000;
const size_t num_elements =     500000;

int main() {
  srand( 1337 );
  std::vector<std::pair<uint32_t,uint64_t>> values;
  for( size_t i = 0; i <= num_elements; ++i ) {
    uint32_t key = rand() % num_partitions;
    uint64_t value = rand();
    values.push_back( std::make_pair( key, value ) );
  }
  clock_t start;
  clock_t stop;
  {
    start = clock();
    std::multimap< uint32_t, uint64_t > mumap;
    for( auto iter = values.begin(); iter != values.end(); ++iter ) {
      mumap.insert( *iter );
    }
    stop = clock();
    std::cout << "Filling std::multimap: " << stop - start << " ticks" << std::endl;
    std::vector<uint64_t> sums;
    start = clock();
    for( uint32_t i = 0; i <= num_partitions; ++i ) {
      uint64_t sum = 0;
      auto range = mumap.equal_range( i );
      for( auto iter = range.first; iter != range.second; ++iter ) {
        sum += iter->second;
      }
      sums.push_back( sum );
    }
    stop = clock();
    std::cout << "Reading std::multimap: " << stop - start << " ticks" << std::endl;
  }
  {
    start = clock();
    my_mumap_t mumap;
    for( auto iter = values.begin(); iter != values.end(); ++iter ) {
      mumap[ iter->first ].push_back( iter->second );
    }
    stop = clock();
    std::cout << "Filling my_mumap_t: " << stop - start << " ticks" << std::endl;
    std::vector<uint64_t> sums;
    start = clock();
    for( uint32_t i = 0; i <= num_partitions; ++i ) {
      uint64_t sum = 0;
      auto range = std::make_pair( mumap[i].begin(), mumap[i].end() );
      for( auto iter = range.first; iter != range.second; ++iter ) {
        sum += *iter;
      }
      sums.push_back( sum );
    }
    stop = clock();
    std::cout << "Reading my_mumap_t: " << stop - start << " ticks" << std::endl;
  }
}

Jak podejrzewałem zależy to głównie od stosunku między num_partitions i num_elements , więc nadal jestem w stratach tutaj. Oto kilka przykładowych wyjść:

Dla num_partitions = 100000 i num_elements = 1000000

Filling std::multimap: 1440000 ticks
Reading std::multimap: 230000 ticks
Filling    my_mumap_t: 1500000 ticks
Reading    my_mumap_t: 170000 ticks

Dla num_partitions = 100000 i num_elements = 500000

Filling std::multimap: 580000 ticks
Reading std::multimap: 150000 ticks
Filling    my_mumap_t: 770000 ticks
Reading    my_mumap_t: 140000 ticks

Dla num_partitions = 100000 i num_elements = 200000

Filling std::multimap: 180000 ticks
Reading std::multimap:  90000 ticks
Filling    my_mumap_t: 290000 ticks
Reading    my_mumap_t: 130000 ticks

Dla num_partitions = 1000 i num_elements = 1000000

Filling std::multimap: 970000 ticks
Reading std::multimap: 150000 ticks
Filling    my_mumap_t: 710000 ticks
Reading    my_mumap_t:  10000 ticks
Nie jestem pewien, jak interpretować te wyniki. W jaki sposób zdecydujesz się na poprawną strukturę danych? Czy są jakieś dodatkowe ograniczenia dla decyzji, które mogłem przeoczyć?
Author: Kerrek SB, 2011-12-01

3 answers

Trudno powiedzieć, czy twój benchmark robi dobrze, więc nie mogę skomentować liczb. Jednak kilka ogólnych punktów:

  • Dlaczego multimap zamiast mapy wektorów: Mapy, multimapy, zestawy i multisety są zasadniczo tą samą strukturą danych, a gdy już ją posiadasz, trywialne jest przeliterowanie wszystkich czterech. Więc pierwsza odpowiedź brzmi: "dlaczego nie mieć to"?

  • Jak to jest przydatne : Multimapy są jedną z tych rzeczy, które potrzebujesz rzadko, ale kiedy ich potrzebujesz, naprawdę ich potrzebujesz.

  • Dlaczego nie wrzucić własnego rozwiązania? Jak powiedziałem, nie jestem pewien tych benchmarków, ale nawet Jeśli mógłbyś zrobić coś innego, co nie jest gorsze niż standardowy kontener( co kwestionuję), powinieneś rozważyć ogólny ciężar poprawienia go, testowania i utrzymywania go. Wyobraź sobie świat, w którym będziesz opodatkowany za każdą linijkę kodu, którą napisałeś (to Stepanov sugestia). W miarę możliwości ponownie używać komponentów zgodnych ze standardami branżowymi.

Wreszcie, oto typowy sposób iteracji multimapy:

for (auto it1 = m.cbegin(), it2 = it1, end = m.cend(); it1 != end; it1 = it2)
{
  // unique key values at this level
  for ( ; it2 != end && it2->first == it1->first; ++it2)
  {
    // equal key value (`== it1->first`) at this level
  }
}
 25
Author: Kerrek SB,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2017-09-14 15:08:14

Zapomniałeś o jednej bardzo ważnej alternatywie: nie wszystkie sekwencje są sobie równe.

Zwłaszcza, dlaczego vector a nie deque Czy list?

za pomocą list

A std::map<int, std::list<int> > powinno działać mniej więcej równoważnie z std::multimap<int, int>, ponieważ list jest również oparte na węzłach.

za pomocą deque

A deque jest domyślnym kontenerem używanym, gdy nie wiesz, do którego kontenera się udać i nie masz żadnych SPECJALNYCH WYMAGAŃ.

W odniesieniu do vector, wymieniasz trochę prędkości odczytu (niewiele) na szybsze operacje push i pop.

Używając zamiast deque i pewnych oczywistych optymalizacji , otrzymuję:

const uint32_t num_partitions = 100000;
const size_t num_elements =     500000;

Filling std::multimap: 360000 ticks
Filling MyMumap:       530000 ticks

Reading std::multimap: 70000 ticks (0)
Reading MyMumap:       30000 ticks (0)

Lub w "złym" przypadku:

const uint32_t num_partitions = 100000;
const size_t num_elements =     200000;

Filling std::multimap: 100000 ticks
Filling MyMumap:       240000 ticks

Reading std::multimap: 30000 ticks (0)
Reading MyMumap:       10000 ticks (0)

Zatem czytanie jest bezwarunkowo szybsze, ale napełnianie jest również znacznie wolniejsze.

 8
Author: Matthieu M.,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-01 17:22:48

Mapa wektorów zawiera pamięć o pojemności każdego wektora. std::vector zazwyczaj przydziela miejsce dla większej liczby elementów niż faktycznie masz. Może to nie jest wielka sprawa dla Twojej aplikacji, ale jest to kolejny kompromis, którego nie rozważałeś.

Jeśli robisz dużo czytań, wtedy O(1) lookup time of unordered_multimap może być lepszym wyborem.

Jeśli masz dość nowoczesny kompilator (a biorąc pod uwagę obecność słowa kluczowego auto, masz) to ogólnie jesteś będzie miał trudności z pokonaniem standardowych kontenerów pod względem wydajności i niezawodności. Ludzie, którzy je napisali, są ekspertami. Zawsze zaczynałbym od standardowego kontenera, który najłatwiej wyraża to, co chcesz zrobić. Profiluj swój kod wcześnie i często, a jeśli nie działa wystarczająco szybko, poszukaj sposobów na jego ulepszenie (np. używając kontenerów unordered_ podczas większości odczytów).

Więc, aby odpowiedzieć na twoje pierwotne pytanie, jeśli potrzebujesz tablicy asocjacyjnej wartości, gdzie te wartości nie będą unikalne, wtedy użycie std::multimap zdecydowanie ma sens.

 7
Author: Michael Kristofik,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-12-01 14:57:35