niedziela, 26 stycznia 2014

Jak (nie) przechodzić po QMapie.

Jest sobie jakaś QMap'a. Na przykład taka:
QMap<QString, QVariant> someMap;
Potrzebujemy wykonać jakąś operację na każdej wartości w tej QMapie.  Zauważyłem u mojego kolegi z zespołu taki kod:
 foreach(QString, someMap.keys()){...}
Nigdy by mi to do głowy to nie przyszło, tak więc postanowiłem sprawdzić, czy rzeczywiście jest to takie złe rozwiązanie. Napisałem programik testowy, uwzględniający cztery przypadki:
 
46   QStringList tmp;
47   for(auto i(0); i < LOOP_LENGTH; i++){
48     foreach(QString key, testMap.keys()){
49       tmp << testMap.value(key).toString();
50     }
51   }
 
To przypadek pierwszy, który zainspirował ten artykuł
59   QStringList tmp;
60   for(auto i(0); i < LOOP_LENGTH; i++){
61     foreach(const QString &key, testMap.keys()){
62      tmp << testMap.value(key).toString();
63     }
64   }
 
Przypadek drugi różni się od pierwszego użyciem stałej referencji, zamiast tworzenia kopi QStringa
72   QStringList tmp;
73   for(auto i(0); i < LOOP_LENGTH; i++){
74     QMap::iterator it(testMap.begin());
75     while(it != testMap.end()){
76       tmp << it.value().toString();
77       ++it;
78     }
79   }
A tutaj kod który wydawał mi się najbardziej intuicyjny w takim przypadku - użycie iteratora. Jest jeszcze czwarty przypadek, z użyciem stałego iteratora. A teraz wyniki testu, przy kluczu o średniej długości 6-ciu znaków, 10-cio elementowej mapie oraz pętli wykonywanej 10000 razy:
"Preparing QMap with 10 items. Key prefix length: 4 , value prefix length 6. loop length 10000"
Foreach test
Time elapsed: 48
Const foreach test
Time elapsed: 43
Iterator test
Time elapsed: 14
Const iterator test
Time elapsed: 13
Jak wydać wersje używające stałego iteratora oraz stałej referencji są nieco szybsze od swoich modyfikowalnych odpowiedników. Wersja z iteratorem jest natomiast prawie 4 razy szybsza niż wersja z użyciem pętli foreach i metody QMap::keys(). A teraz teścik przy nieco zmienionych ustawieniach, wydłużamy rozmiar klucza oraz zwiększamy wielkość mapy.
"Preparing QMap with 1000 items. Key prefix length: 8 , value prefix length 6. loop length 10000"
Foreach test
Time elapsed: 7127
Const foreach test
Time elapsed: 6692
Iterator test
Time elapsed: 1378
Const iterator test
Time elapsed: 1241

Teraz różnica szybkości jest już prawie 6-cio krotna. Dlaczego? Zobaczmy co robi funkcja QMap::keys():
  QList res;
  res.reserve(size());
  const_iterator i = begin();
  while (i != end()) {
    res.append(i.key());
    ++i;
  }

I to wystarczy za cały komentarz. Dobrą praktyką jest zaglądnięcie do kodu Qt, zanim zrobimy jakąś głupotę. Dziękuję za uwagę :)

Brak komentarzy:

Prześlij komentarz