1 /*
2     SPDX-FileCopyrightText: 2012 Milian Wolff <mail@milianw.de>
3 
4     SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "bench_hashes.h"
8 
9 #include <serialization/indexedstring.h>
10 
11 #include <tests/testcore.h>
12 #include <tests/autotestshell.h>
13 #include <QDateTime>
14 #include <QVector>
15 #include <QTest>
16 
17 #include <unordered_map>
18 
19 // similar to e.g. modificationrevision.cpp
20 struct DataT
21 {
22     QDateTime a;
23     QDateTime b;
24 };
25 
26 using DataPair = QPair<KDevelop::IndexedString, DataT>;
27 using InputData = QVector<DataPair>;
28 
29 struct IndexedStringHash
30 {
operator ()IndexedStringHash31     inline uint operator()(const KDevelop::IndexedString& str) const
32     {
33         return str.hash();
34     }
35 };
36 
37 using StlHash = std::unordered_map<KDevelop::IndexedString, DataT, IndexedStringHash>;
38 
insertData(StlHash & hash,const InputData & data)39 inline void insertData(StlHash& hash, const InputData& data)
40 {
41     for (const DataPair& pair : data) {
42         hash.insert(std::make_pair(pair.first, pair.second));
43     }
44 }
45 
46 using QStringHash = QHash<KDevelop::IndexedString, DataT>;
insertData(QStringHash & hash,const InputData & data)47 inline void insertData(QStringHash& hash, const InputData& data)
48 {
49     for (const DataPair& pair : data) {
50         hash.insert(pair.first, pair.second);
51     }
52 }
53 
54 QTEST_GUILESS_MAIN(BenchHashes)
55 
56 using namespace KDevelop;
57 
Q_DECLARE_METATYPE(InputData)58 Q_DECLARE_METATYPE(InputData)
59 
60 void BenchHashes::initTestCase()
61 {
62     AutoTestShell::init();
63     TestCore::initialize(Core::NoUi);
64 
65     qRegisterMetaType<InputData>();
66 }
67 
cleanupTestCase()68 void BenchHashes::cleanupTestCase()
69 {
70     TestCore::shutdown();
71 }
72 
feedData()73 void BenchHashes::feedData()
74 {
75     QTest::addColumn<bool>("useStl");
76     QTest::addColumn<InputData>("data");
77 
78     InputData data;
79     const QVector<int> sizes{100, 1000, 10000, 100000};
80     for (int size : sizes) {
81         for (int i = data.size(); i < size; ++i) {
82             data << qMakePair(IndexedString(QString::number(i)), DataT());
83         }
84 
85         QCOMPARE(data.size(), size);
86         QTest::newRow(qPrintable(QStringLiteral("unordered_map-%1").arg(size)))
87             << true << data;
88         QTest::newRow(qPrintable(QStringLiteral("qhash-%1").arg(size)))
89             << false << data;
90     }
91 }
92 
insert()93 void BenchHashes::insert()
94 {
95     QFETCH(bool, useStl);
96     QFETCH(InputData, data);
97 
98     if (useStl) {
99         QBENCHMARK {
100             StlHash hash;
101             insertData(hash, data);
102         }
103     } else {
104         QBENCHMARK {
105             QStringHash hash;
106             insertData(hash, data);
107         }
108     }
109 }
110 
insert_data()111 void BenchHashes::insert_data()
112 {
113     feedData();
114 }
115 
find()116 void BenchHashes::find()
117 {
118     QFETCH(bool, useStl);
119     QFETCH(const InputData, data);
120 
121     if (useStl) {
122         StlHash hash;
123         insertData(hash, data);
124         QBENCHMARK {
125             for (const DataPair& pair : data) {
126                 ( void ) hash.find(pair.first);
127             }
128         }
129     } else {
130         QStringHash hash;
131         insertData(hash, data);
132         QBENCHMARK {
133             for (const DataPair& pair : data) {
134                 ( void ) hash.find(pair.first);
135             }
136         }
137     }
138 }
139 
find_data()140 void BenchHashes::find_data()
141 {
142     feedData();
143 }
144 
constFind()145 void BenchHashes::constFind()
146 {
147     QFETCH(bool, useStl);
148     QFETCH(const InputData, data);
149 
150     if (useStl) {
151         StlHash hash;
152         insertData(hash, data);
153         const StlHash& constHash = hash;
154         QBENCHMARK {
155             for (const DataPair& pair : data) {
156                 ( void ) constHash.find(pair.first);
157             }
158         }
159     } else {
160         QStringHash hash;
161         insertData(hash, data);
162         QBENCHMARK {
163             for (const DataPair& pair : data) {
164                 ( void ) hash.constFind(pair.first);
165             }
166         }
167     }
168 }
169 
constFind_data()170 void BenchHashes::constFind_data()
171 {
172     feedData();
173 }
174 
remove()175 void BenchHashes::remove()
176 {
177     QFETCH(bool, useStl);
178     QFETCH(const InputData, data);
179 
180     if (useStl) {
181         StlHash hash;
182         insertData(hash, data);
183         QBENCHMARK {
184             for (const DataPair& pair : data) {
185                 hash.erase(pair.first);
186             }
187         }
188     } else {
189         QStringHash hash;
190         insertData(hash, data);
191         QBENCHMARK {
192             for (const DataPair& pair : data) {
193                 hash.remove(pair.first);
194             }
195         }
196     }
197 }
198 
remove_data()199 void BenchHashes::remove_data()
200 {
201     feedData();
202 }
203 
204 struct TypeRepoTestData
205 {
206     size_t size = 0;
207     void* ptr = nullptr;
208 };
209 
210 /**
211  * somewhat artificial benchmark to test speed impact if we'd ever change
212  * the underlying data type of the TypeSystem / TypeRegister.
213  */
typeRepo()214 void BenchHashes::typeRepo()
215 {
216     QFETCH(int, type);
217     if (type == 1 || type == 2) {
218         QVector<TypeRepoTestData*> v;
219         for (int i = 0; i < 100; ++i) {
220             v.append(new TypeRepoTestData);
221         }
222 
223         if (type == 1) {
224             QBENCHMARK {
225                 for (int i = 0; i < 100; ++i) {
226                     v.at(i)->size++;
227                 }
228             }
229         } else if (type == 2) {
230             TypeRepoTestData** a = v.data();
231             QBENCHMARK {
232                 for (int i = 0; i < 100; ++i) {
233                     a[i]->size++;
234                 }
235             }
236         }
237         qDeleteAll(v);
238     } else if (type == 3) {
239         QHash<int, TypeRepoTestData*> v;
240         for (int i = 0; i < 100; ++i) {
241             v[i] = new TypeRepoTestData;
242         }
243 
244         QBENCHMARK {
245             for (int i = 0; i < 100; ++i) {
246                 v.value(i)->size++;
247             }
248         }
249         qDeleteAll(v);
250     } else if (type == 4) {
251         QMap<int, TypeRepoTestData*> v;
252         for (int i = 0; i < 100; ++i) {
253             v[i] = new TypeRepoTestData;
254         }
255 
256         QBENCHMARK {
257             for (int i = 0; i < 100; ++i) {
258                 v.value(i)->size++;
259             }
260         }
261         qDeleteAll(v);
262     } else if (type == 5) {
263         std::unordered_map<int, TypeRepoTestData*> v;
264         for (int i = 0; i < 100; ++i) {
265             v[i] = new TypeRepoTestData;
266         }
267 
268         QBENCHMARK {
269             for (int i = 0; i < 100; ++i) {
270                 v.at(i)->size++;
271             }
272         }
273         for (const auto& [key, value] : v) {
274             delete value;
275         }
276     } else if (type == 6) {
277         // for the idea, look at c++'s lexer.cpp
278         const int vectors = 5;
279         using Pair = QPair<int, TypeRepoTestData*>;
280         using InnerVector = QVarLengthArray<Pair, vectors>;
281         QVarLengthArray <InnerVector, 10> v;
282         v.resize(vectors);
283         for (int i = 0; i < 100; ++i) {
284             v[i % vectors] << qMakePair(i, new TypeRepoTestData);
285         }
286 
287         QBENCHMARK {
288             for (int i = 0; i < 100; ++i) {
289                 for (const Pair& p : qAsConst(v.at(i % vectors))) {
290                     if (p.first == i) {
291                         p.second->size++;
292                         break;
293                     }
294                 }
295             }
296         }
297         for (const auto& inner : v) {
298             for (const auto& [key, value] : inner) {
299                 delete value;
300             }
301         }
302     } else if (type == 0) {
303         QBENCHMARK {}
304     }
305 }
306 
typeRepo_data()307 void BenchHashes::typeRepo_data()
308 {
309     QTest::addColumn<int>("type");
310 
311     QTest::newRow("noop") << 0;
312     QTest::newRow("vector") << 1;
313     QTest::newRow("vector-raw") << 2;
314     QTest::newRow("qhash") << 3;
315     QTest::newRow("qmap") << 4;
316     QTest::newRow("unordered_map") << 5;
317     QTest::newRow("nested-vector") << 6;
318 }
319