Optimize aggregation of caller/callee data
authorMilian Wolff <mail@milianw.de>
Mon, 30 Jan 2017 20:55:19 +0000 (21:55 +0100)
committerMilian Wolff <mail@milianw.de>
Mon, 30 Jan 2017 20:58:09 +0000 (21:58 +0100)
Previously, we did that on the unmerged data which means we had
to traverse way more stacks. Now, we instead use the merged data
as input and thereby can get rid of ~50% of the hash allocations
alone used for the recursion guard. This is the #1 hotspot when
it comes to allocation numbers so far, so that boils down to easily
millions of allocations.

src/analyze/gui/locationdata.h
src/analyze/gui/parser.cpp

index 43f50c3..d9d3ac5 100644 (file)
@@ -24,6 +24,8 @@
 
 #include <memory>
 
+#include <boost/functional/hash.hpp>
+
 struct LocationData
 {
     using Ptr = std::shared_ptr<LocationData>;
@@ -61,4 +63,19 @@ inline bool operator<(const LocationData::Ptr& lhs, const LocationData& rhs)
     return *lhs < rhs;
 }
 
+inline uint qHash(const LocationData& location, uint seed_ = 0)
+{
+    size_t seed = seed_;
+    boost::hash_combine(seed, qHash(location.function));
+    boost::hash_combine(seed, qHash(location.file));
+    boost::hash_combine(seed, qHash(location.module));
+    boost::hash_combine(seed, location.line);
+    return seed;
+}
+
+inline uint qHash(const LocationData::Ptr& location, uint seed = 0)
+{
+    return location ? qHash(*location, seed) : seed;
+}
+
 #endif // LOCATIONDATA_H
index 73fad85..9b0a3cf 100644 (file)
@@ -306,37 +306,18 @@ void setParents(QVector<RowData>& children, const RowData* parent)
     }
 }
 
-QPair<TreeData, CallerCalleeRows> mergeAllocations(const ParserData& data)
+TreeData mergeAllocations(const ParserData& data)
 {
     TreeData topRows;
-    CallerCalleeRows callerCalleeRows;
-    callerCalleeRows.reserve(data.instructionPointers.size());
-    // merge allocations, leave parent pointers invalid (their location may
-    // change)
+    // merge allocations, leave parent pointers invalid (their location may change)
     for (const auto& allocation : data.allocations) {
         auto traceIndex = allocation.traceIndex;
         auto rows = &topRows;
-        // we must not count locations more than once in the caller-callee data
-        QSet<IpIndex> recursionGuard;
         while (traceIndex) {
             const auto& trace = data.findTrace(traceIndex);
             const auto& ip = data.findIp(trace.ipIndex);
             auto location = data.stringCache.location(trace.ipIndex, ip);
 
-            if (!recursionGuard.contains(trace.ipIndex)) { // aggregate caller-callee data
-                auto it = lower_bound(
-                    callerCalleeRows.begin(), callerCalleeRows.end(), location,
-                    [](const CallerCalleeData& lhs, const LocationData::Ptr& rhs) { return lhs.location < rhs; });
-                if (it == callerCalleeRows.end() || it->location != location) {
-                    it = callerCalleeRows.insert(it, {{}, {}, location});
-                }
-                it->inclusiveCost += allocation;
-                if (traceIndex == allocation.traceIndex) {
-                    it->selfCost += allocation;
-                }
-                recursionGuard.insert(trace.ipIndex);
-            }
-
             auto it = lower_bound(rows->begin(), rows->end(), location);
             if (it != rows->end() && it->location == location) {
                 it->cost += allocation;
@@ -353,17 +334,7 @@ QPair<TreeData, CallerCalleeRows> mergeAllocations(const ParserData& data)
     // now set the parents, the data is constant from here on
     setParents(topRows, nullptr);
 
-    if (data.stringCache.diffMode) {
-        // remove rows without cost
-        callerCalleeRows.erase(remove_if(callerCalleeRows.begin(), callerCalleeRows.end(),
-                                         [](const CallerCalleeData& data) -> bool {
-                                             return data.inclusiveCost == AllocationData()
-                                                 && data.selfCost == AllocationData();
-                                         }),
-                               callerCalleeRows.end());
-    }
-
-    return qMakePair(topRows, callerCalleeRows);
+    return topRows;
 }
 
 RowData* findByLocation(const RowData& row, QVector<RowData>* data)
@@ -412,6 +383,59 @@ QVector<RowData> toTopDownData(const QVector<RowData>& bottomUpData)
     return topRows;
 }
 
+
+void buildCallerCallee(const TreeData& bottomUpData, CallerCalleeRows* callerCalleeData)
+{
+    foreach (const auto& row, bottomUpData) {
+        if (row.children.isEmpty()) {
+            // leaf node found, bubble up the parent chain to add cost for all frames
+            // to the caller/callee data. this is done top-down since we must not count
+            // locations more than once in the caller-callee data
+            QSet<LocationData::Ptr> recursionGuard;
+
+            auto node = &row;
+            while (node) {
+                const auto& location = node->location;
+                if (!recursionGuard.contains(location)) { // aggregate caller-callee data
+                    auto it = lower_bound(callerCalleeData->begin(), callerCalleeData->end(), location,
+                        [](const CallerCalleeData& lhs, const LocationData::Ptr& rhs) { return lhs.location < rhs; });
+                    if (it == callerCalleeData->end() || it->location != location) {
+                        it = callerCalleeData->insert(it, {{}, {}, location});
+                    }
+                    it->inclusiveCost += row.cost;
+                    if (!node->parent) {
+                        it->selfCost += row.cost;
+                    }
+                    recursionGuard.insert(location);
+                }
+                node = node->parent;
+            }
+        } else {
+            // recurse to find a leaf
+            buildCallerCallee(row.children, callerCalleeData);
+        }
+    }
+}
+
+CallerCalleeRows toCallerCalleeData(const QVector<RowData>& bottomUpData, bool diffMode)
+{
+    CallerCalleeRows callerCalleeRows;
+
+    buildCallerCallee(bottomUpData, &callerCalleeRows);
+
+    if (diffMode) {
+        // remove rows without cost
+        callerCalleeRows.erase(remove_if(callerCalleeRows.begin(), callerCalleeRows.end(),
+                                         [](const CallerCalleeData& data) -> bool {
+                                             return data.inclusiveCost == AllocationData()
+                                                 && data.selfCost == AllocationData();
+                                         }),
+                               callerCalleeRows.end());
+    }
+
+    return callerCalleeRows;
+}
+
 struct MergedHistogramColumnData
 {
     LocationData::Ptr location;
@@ -528,8 +552,7 @@ void Parser::parse(const QString& path, const QString& diffBase)
         emit progressMessageAvailable(i18n("merging allocations..."));
         // merge allocations before modifying the data again
         const auto mergedAllocations = mergeAllocations(*data);
-        emit bottomUpDataAvailable(mergedAllocations.first);
-        emit callerCalleeDataAvailable(mergedAllocations.second);
+        emit bottomUpDataAvailable(mergedAllocations);
 
         // also calculate the size histogram
         emit progressMessageAvailable(i18n("building size histogram..."));
@@ -537,11 +560,15 @@ void Parser::parse(const QString& path, const QString& diffBase)
         emit sizeHistogramDataAvailable(sizeHistogram);
         // now data can be modified again for the chart data evaluation
 
+        const auto diffMode = data->stringCache.diffMode;
         emit progressMessageAvailable(i18n("building charts..."));
         auto parallel = new Collection;
-        *parallel << make_job([this, mergedAllocations, sizeHistogram]() {
-            const auto topDownData = toTopDownData(mergedAllocations.first);
+        *parallel << make_job([this, mergedAllocations]() {
+            const auto topDownData = toTopDownData(mergedAllocations);
             emit topDownDataAvailable(topDownData);
+        }) << make_job([this, mergedAllocations, diffMode]() {
+            const auto callerCalleeData = toCallerCalleeData(mergedAllocations, diffMode);
+            emit callerCalleeDataAvailable(callerCalleeData);
         });
         if (!data->stringCache.diffMode) {
             // only build charts when we are not diffing