Optimize tracking of active allocations.
authorMilian Wolff <mail@milianw.de>
Mon, 14 Dec 2015 17:24:34 +0000 (18:24 +0100)
committerMilian Wolff <mail@milianw.de>
Mon, 14 Dec 2015 17:47:44 +0000 (18:47 +0100)
Instead of mapping the pointer to a pair of allocated size and
trace index, we intern the pair and map the pointer to a 32bit index.
This assumes that not more than 4.294.967.295 different pairs occur,
which could theoretically be broken by allocating different sizes
in a loop. Practically, I've never seen this happen. If it really
breaks we can always bump it to a 64bit index later.

Furthermore, this patch introduces a new PointerMap, which drastically
reduces the memory overhead of tracking the allocations. The benchmark
which also gets added here, shows that its overhead is only ~20%
compared to the 100% overhead of a simple hash map. Also note that
even google's sparse_hash_map only gets down to 50% overhead.
Even better, the PointerMap implementation is faster than google's
sparse_hash_map. This is possible by leveraging some information about
memory allocations, which return pointers to pages. Thus the pointers
are clustered and we can shrink a 64bit pointer to an common shared
base pointer and a small 16bit offset.

On my machine, the runtime performance is close to that of the simple
hash map. As such, I decided to move this allocation tracking into
heaptrack_interpret itself. This drastically reduces the file size
of heaptrack data files, sometimes cutting the size into half. Even
better, this speeds some heaptrack benchmarks, as less data is written
to disk. And of course all of this leads to an much improved
performance of heaptrack_gui and heaptrack_print.

13 files changed:
CMakeLists.txt
accumulatedtracedata.cpp
accumulatedtracedata.h
cmake/FindSparseHash.cmake [new file with mode: 0644]
heaptrack_interpret.cpp
indices.h [new file with mode: 0644]
pointermap.h [new file with mode: 0644]
tests/CMakeLists.txt
tests/benchmarks/CMakeLists.txt [new file with mode: 0644]
tests/benchmarks/bench_pointerhash.cpp [new file with mode: 0644]
tests/benchmarks/bench_pointermap.cpp [new file with mode: 0644]
tests/benchmarks/bench_pointers.h [new file with mode: 0644]
tests/benchmarks/bench_pointersparsehash.cpp [new file with mode: 0644]

index cb4d343..ce0c3d4 100644 (file)
@@ -7,8 +7,8 @@ if(NOT CMAKE_BUILD_TYPE)
   set(CMAKE_BUILD_TYPE RelWithDebInfo CACHE STRING "Choose the type of build." FORCE)
 endif()
 
-set(HEAPTRACK_VERSION_MAJOR 0)
-set(HEAPTRACK_VERSION_MINOR 1)
+set(HEAPTRACK_VERSION_MAJOR 1)
+set(HEAPTRACK_VERSION_MINOR 0)
 set(HEAPTRACK_VERSION_PATCH 0)
 set(HEAPTRACK_LIB_VERSION 1.0.0)
 set(HEAPTRACK_LIB_SOVERSION 1)
@@ -20,6 +20,8 @@ find_package(ECM 1.0.0 NO_MODULE)
 find_package(ZLIB REQUIRED)
 include(FeatureSummary)
 
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${CMAKE_CURRENT_SOURCE_DIR}/cmake)
+
 if(Qt5_FOUND AND ECM_FOUND)
     set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} ${ECM_MODULE_PATH} ${ECM_KDE_MODULE_DIR})
     find_package(KF5 OPTIONAL_COMPONENTS CoreAddons I18n ItemModels ThreadWeaver ConfigWidgets)
index ba72944..034646e 100644 (file)
@@ -158,6 +158,7 @@ bool AccumulatedTraceData::read(istream& in)
     allocations.clear();
     sizeHistogram.clear();
     uint64_t lastAllocationPtr = 0;
+    uint fileVersion = 0;
 
     while (reader.getLine(in)) {
         if (reader.mode() == 's') {
@@ -208,50 +209,77 @@ bool AccumulatedTraceData::read(istream& in)
                 opNewIpIndices.push_back(index);
             }
         } else if (reader.mode() == '+') {
-            uint64_t size = 0;
-            TraceIndex traceId;
-            uint64_t ptr = 0;
-            if (!(reader >> size) || !(reader >> traceId) || !(reader >> ptr)) {
-                cerr << "failed to parse line: " << reader.line() << endl;
-                continue;
-            }
-
-            if (size <= std::numeric_limits<uint32_t>::max()) {
-                // save memory by storing this allocation in the list of small allocations
-                activeSmallAllocations[ptr] = {traceId, static_cast<uint32_t>(size)};
+            BigAllocationInfo info;
+            if (fileVersion >= 0x010000) {
+                uint32_t allocationInfoIndex = 0;
+                if (!(reader >> allocationInfoIndex)) {
+                    cerr << "failed to parse line: " << reader.line() << endl;
+                    continue;
+                }
+                info = allocationInfos[allocationInfoIndex];
             } else {
-                // these rare allocations consume more memory to track, but that's fine
-                activeBigAllocations[ptr] = {traceId, size};
+                uint64_t ptr = 0;
+                if (!(reader >> info.size) || !(reader >> info.traceIndex) || !(reader >> ptr)) {
+                    cerr << "failed to parse line: " << reader.line() << endl;
+                    continue;
+                }
+
+                if (info.size <= std::numeric_limits<uint32_t>::max()) {
+                    // save memory by storing this allocation in the list of small allocations
+                    activeSmallAllocations[ptr] = {static_cast<uint32_t>(info.size), info.traceIndex};
+                } else {
+                    // these rare allocations consume more memory to track, but that's fine
+                    activeBigAllocations[ptr] = info;
+                }
+                lastAllocationPtr = ptr;
             }
 
-            auto& allocation = findAllocation(traceId);
-            allocation.leaked += size;
-            allocation.allocated += size;
+            auto& allocation = findAllocation(info.traceIndex);
+            allocation.leaked += info.size;
+            allocation.allocated += info.size;
             ++allocation.allocations;
             if (allocation.leaked > allocation.peak) {
                 allocation.peak = allocation.leaked;
             }
-            totalAllocated += size;
+
+            totalAllocated += info.size;
             ++totalAllocations;
-            leaked += size;
+            leaked += info.size;
             if (leaked > peak) {
                 peak = leaked;
             }
-            lastAllocationPtr = ptr;
-            handleAllocation();
+
             if (printHistogram) {
-                ++sizeHistogram[size];
+                ++sizeHistogram[info.size];
             }
+
+            handleAllocation();
         } else if (reader.mode() == '-') {
-            uint64_t ptr = 0;
-            if (!(reader >> ptr)) {
-                cerr << "failed to parse line: " << reader.line() << endl;
-                continue;
-            }
-            const auto info = takeActiveAllocation(ptr);
-            if (!info.traceIndex) {
-                // happens when we attached to a running application
-                continue;
+            BigAllocationInfo info;
+            bool temporary = false;
+            if (fileVersion >= 0x010000) {
+                uint32_t allocationInfoIndex = 0;
+                if (!(reader >> allocationInfoIndex)) {
+                    cerr << "failed to parse line: " << reader.line() << endl;
+                    continue;
+                }
+                info = allocationInfos[allocationInfoIndex];
+                // optional, and thus may fail.
+                // but that's OK since we default to false anyways
+                reader >> temporary;
+            } else {
+                uint64_t ptr = 0;
+                if (!(reader >> ptr)) {
+                    cerr << "failed to parse line: " << reader.line() << endl;
+                    continue;
+                }
+                info = takeActiveAllocation(ptr);
+                if (!info.traceIndex) {
+                    // happens when we attached to a running application
+                    continue;
+                }
+                temporary = lastAllocationPtr == ptr;
+                lastAllocationPtr = 0;
             }
 
             auto& allocation = findAllocation(info.traceIndex);
@@ -265,11 +293,17 @@ bool AccumulatedTraceData::read(istream& in)
                 allocation.leaked -= info.size;
             }
             leaked -= info.size;
-            if (lastAllocationPtr == ptr) {
+            if (temporary) {
                 ++allocation.temporary;
                 ++totalTemporary;
             }
-            lastAllocationPtr = 0;
+        } else if (reader.mode() == 'a') {
+            BigAllocationInfo info;
+            if (!(reader >> info.size) || !(reader >> info.traceIndex)) {
+                cerr << "failed to parse line: " << reader.line() << endl;
+                continue;
+            }
+            allocationInfos.push_back(info);
         } else if (reader.mode() == '#') {
             // comment or empty line
             continue;
@@ -288,7 +322,7 @@ bool AccumulatedTraceData::read(istream& in)
             peak = 0;
             fromAttached = true;
         } else if (reader.mode() == 'v') {
-            // the version, we ignore it for now
+            reader >> fileVersion;
         } else {
             cerr << "failed to parse line: " << reader.line() << endl;
         }
@@ -354,13 +388,15 @@ bool AccumulatedTraceData::isStopIndex(const StringIndex index) const
     return find(stopIndices.begin(), stopIndices.end(), index) != stopIndices.end();
 }
 
+// only used for backwards compatibility with older data files
+// newer files do this in heaptrack_interpret already with some more magic
 BigAllocationInfo AccumulatedTraceData::takeActiveAllocation(uint64_t ptr)
 {
     auto small = activeSmallAllocations.find(ptr);
     if (small != activeSmallAllocations.end()) {
         auto ret = small->second;
         activeSmallAllocations.erase(small);
-        return {ret.traceIndex, ret.size};
+        return {ret.size, ret.traceIndex};
     }
     // rare
     auto big = activeBigAllocations.find(ptr);
index 2be87a1..6192f4c 100644 (file)
 
 #include <fstream>
 #include <unordered_map>
+#include <unordered_set>
 #include <map>
+#include <boost/functional/hash.hpp>
 
-// sadly, C++ doesn't yet have opaque typedefs
-template<typename Base>
-struct Index
-{
-    uint32_t index = 0;
-
-    explicit operator bool() const
-    {
-        return index;
-    }
-
-    bool operator<(Index o) const
-    {
-        return index < o.index;
-    }
-
-    bool operator!=(Index o) const
-    {
-        return index != o.index;
-    }
-
-    bool operator==(Index o) const
-    {
-        return index == o.index;
-    }
-};
-
-template<typename Base>
-uint qHash(const Index<Base> index, uint seed = 0) noexcept
-{
-    return qHash(index.index, seed);
-}
-
-struct StringIndex : public Index<StringIndex> {};
-struct ModuleIndex : public StringIndex {};
-struct FunctionIndex : public StringIndex {};
-struct FileIndex : public StringIndex {};
-struct IpIndex : public Index<IpIndex> {};
-struct TraceIndex : public Index<TraceIndex> {};
+#include "indices.h"
 
 struct InstructionPointer
 {
@@ -138,8 +102,8 @@ struct MergedAllocation : public AllocationData
  */
 struct SmallAllocationInfo
 {
-    TraceIndex traceIndex;
     uint32_t size;
+    TraceIndex traceIndex;
 };
 
 /**
@@ -147,8 +111,12 @@ struct SmallAllocationInfo
  */
 struct BigAllocationInfo
 {
-    TraceIndex traceIndex;
     uint64_t size;
+    TraceIndex traceIndex;
+    bool operator==(const BigAllocationInfo& rhs) const
+    {
+        return rhs.traceIndex == traceIndex && rhs.size == size;
+    }
 };
 
 struct AccumulatedTraceData
@@ -197,12 +165,16 @@ struct AccumulatedTraceData
 
     // indices of functions that should stop the backtrace, e.g. main or static initialization
     std::vector<StringIndex> stopIndices;
-    std::unordered_map<uint64_t, SmallAllocationInfo> activeSmallAllocations;
-    std::unordered_map<uint64_t, BigAllocationInfo> activeBigAllocations;
     std::vector<InstructionPointer> instructionPointers;
     std::vector<TraceNode> traces;
     std::vector<std::string> strings;
     std::vector<IpIndex> opNewIpIndices;
+
+    std::vector<BigAllocationInfo> allocationInfos;
+
+    // backwards compatibility
+    std::unordered_map<uint64_t, SmallAllocationInfo> activeSmallAllocations;
+    std::unordered_map<uint64_t, BigAllocationInfo> activeBigAllocations;
 };
 
 #endif // ACCUMULATEDTRACEDATA_H
diff --git a/cmake/FindSparseHash.cmake b/cmake/FindSparseHash.cmake
new file mode 100644 (file)
index 0000000..41dfb26
--- /dev/null
@@ -0,0 +1,18 @@
+# - Try to find SparseHash
+# Once done this will define
+#  SPARSEHASH_FOUND - System has SparseHash
+#  SPARSEHASH_INCLUDE_DIRS - The SparseHash include directories
+
+find_path(SPARSEHASH_INCLUDE_DIR NAMES sparse_hash_map
+    PATHS ${SPARSEHASH_ROOT} $ENV{SPARSEHASH_ROOT} /usr/local/ /usr/ /sw/ /opt/local /opt/csw/ /opt/ ENV CPLUS_INCLUDE_PATH
+    PATH_SUFFIXES include/sparsehash include/google include
+)
+
+set(SPARSEHASH_INCLUDE_DIRS ${SPARSEHASH_INCLUDE_DIR})
+
+# handle the QUIETLY and REQUIRED arguments and set SPARSEHASH_FOUND to TRUE if
+# all listed variables are TRUE
+include(FindPackageHandleStandardArgs)
+find_package_handle_standard_args(SparseHash DEFAULT_MSG SPARSEHASH_INCLUDE_DIR)
+
+mark_as_advanced(SPARSEHASH_INCLUDE_DIR)
\ No newline at end of file
index 2715312..96002d1 100644 (file)
@@ -26,6 +26,7 @@
 #include <iostream>
 #include <sstream>
 #include <unordered_map>
+#include <unordered_set>
 #include <vector>
 #include <tuple>
 #include <algorithm>
 #include <cxxabi.h>
 
 #include <boost/algorithm/string/predicate.hpp>
+#include <boost/functional/hash.hpp>
 
 #include "libbacktrace/backtrace.h"
 #include "libbacktrace/internal.h"
 #include "linereader.h"
+#include "pointermap.h"
 
 using namespace std;
 
@@ -126,25 +129,6 @@ struct Module
     backtrace_state* backtraceState;
 };
 
-struct Allocation
-{
-    // backtrace entry point
-    size_t ipIndex;
-    // number of allocations
-    size_t allocations;
-    // amount of bytes leaked
-    size_t leaked;
-};
-
-/**
- * Information for a single call to an allocation function
- */
-struct AllocationInfo
-{
-    size_t ipIndex;
-    size_t size;
-};
-
 struct ResolvedIP
 {
     size_t moduleIndex = 0;
@@ -329,6 +313,36 @@ private:
     unordered_map<uintptr_t, size_t> m_encounteredIps;
 };
 
+/**
+ * Information for a single call to an allocation function for big allocations.
+ */
+struct AllocationInfo
+{
+    uint64_t size;
+    TraceIndex traceIndex;
+    AllocationIndex allocationInfoIndex;
+    bool operator==(const AllocationInfo& rhs) const
+    {
+        return rhs.traceIndex == traceIndex
+            && rhs.size == size;
+            // allocationInfoIndex not compared to allow to look it up
+    }
+};
+
+}
+
+namespace std {
+template<>
+struct hash<AllocationInfo> {
+    size_t operator()(const AllocationInfo &info) const
+    {
+        size_t seed = 0;
+        boost::hash_combine(seed, info.size);
+        boost::hash_combine(seed, info.traceIndex.index);
+        // allocationInfoIndex not hashed to allow to look it up
+        return seed;
+    }
+};
 }
 
 int main(int /*argc*/, char** /*argv*/)
@@ -344,6 +358,11 @@ int main(int /*argc*/, char** /*argv*/)
 
     string exe;
 
+    unordered_set<AllocationInfo> allocationInfos;
+    PointerMap ptrToIndex;
+    uint64_t lastPtr = 0;
+    allocationInfos.reserve(625000);
+
     while (reader.getLine(cin)) {
         if (reader.mode() == 'x') {
             reader >> exe;
@@ -382,6 +401,45 @@ int main(int /*argc*/, char** /*argv*/)
             const auto ipId = data.addIp(instructionPointer);
             // trace point, map current output index to parent index
             fprintf(stdout, "t %zx %zx\n", ipId, parentIndex);
+        } else if (reader.mode() == '+') {
+            uint64_t size = 0;
+            TraceIndex traceId;
+            uint64_t ptr = 0;
+            if (!(reader >> size) || !(reader >> traceId.index) || !(reader >> ptr)) {
+                cerr << "failed to parse line: " << reader.line() << endl;
+                continue;
+            }
+            AllocationIndex index;
+            index.index = allocationInfos.size();
+            AllocationInfo info = {size, traceId, index};
+            auto it = allocationInfos.find(info);
+            if (it != allocationInfos.end()) {
+                info = *it;
+            } else {
+                allocationInfos.insert(it, info);
+                fprintf(stdout, "a %zx %x\n", info.size, info.traceIndex.index);
+            }
+            ptrToIndex.addPointer(ptr, info.allocationInfoIndex);
+            lastPtr = ptr;
+            fprintf(stdout, "+ %x\n", info.allocationInfoIndex.index);
+        } else if (reader.mode() == '-') {
+            uint64_t ptr = 0;
+            if (!(reader >> ptr)) {
+                cerr << "failed to parse line: " << reader.line() << endl;
+                continue;
+            }
+            bool temporary = lastPtr == ptr;
+            lastPtr = 0;
+            auto allocation = ptrToIndex.takePointer(ptr);
+            if (!allocation.second) {
+                continue;
+            }
+            fprintf(stdout, "- %x", allocation.first.index);
+            if (temporary) {
+                fputs(" 1\n", stdout);
+            } else {
+                fputc('\n', stdout);
+            }
         } else {
             fputs(reader.line().c_str(), stdout);
             fputc('\n', stdout);
diff --git a/indices.h b/indices.h
new file mode 100644 (file)
index 0000000..7242bcd
--- /dev/null
+++ b/indices.h
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2015 Milian Wolff <mail@milianw.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef INDICES_H
+#define INDICES_H
+
+#include <unordered_map>
+
+// sadly, C++ doesn't yet have opaque typedefs
+template<typename Base>
+struct Index
+{
+    uint32_t index = 0;
+
+    explicit operator bool() const
+    {
+        return index;
+    }
+
+    bool operator<(Index o) const
+    {
+        return index < o.index;
+    }
+
+    bool operator!=(Index o) const
+    {
+        return index != o.index;
+    }
+
+    bool operator==(Index o) const
+    {
+        return index == o.index;
+    }
+};
+
+template<typename Base>
+uint qHash(const Index<Base> index, uint seed = 0) noexcept
+{
+    return qHash(index.index, seed);
+}
+
+namespace std {
+template<typename Base>
+struct hash<Index<Base>> {
+    std::size_t operator()(const Index<Base> index) const
+    {
+        return std::hash<uint32_t>()(index.index);
+    }
+};
+}
+
+struct StringIndex : public Index<StringIndex> {};
+struct ModuleIndex : public StringIndex {};
+struct FunctionIndex : public StringIndex {};
+struct FileIndex : public StringIndex {};
+struct IpIndex : public Index<IpIndex> {};
+struct TraceIndex : public Index<TraceIndex> {};
+struct AllocationIndex : public Index<AllocationIndex> {};
+
+#endif // INDICES_H
diff --git a/pointermap.h b/pointermap.h
new file mode 100644 (file)
index 0000000..ab6b62b
--- /dev/null
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2015 Milian Wolff <mail@milianw.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef POINTERMAP_H
+#define POINTERMAP_H
+
+#include <vector>
+#include <unordered_map>
+#include <map>
+#include <limits>
+#include <iostream>
+#include <algorithm>
+
+#include "indices.h"
+
+/**
+ * A low-memory-overhead map of 64bit pointer addresses to 32bit allocation indices.
+ *
+ * We leverage the fact that pointers are allocated in pages, i.e. close to each
+ * other. We split the 64bit address into a common large part and an individual
+ * 16bit small part by dividing the address by some number (PageSize below) and
+ * keeping the result as the big part and the residue as the small part.
+ *
+ * The big part of the address is used for a hash map to lookup the Indices
+ * structure where we aggregate common pointers in two memory-efficient vectors,
+ * one for the 16bit small pointer pairs, and one for the 32bit allocation indices.
+ */
+class PointerMap
+{
+    struct SplitPointer
+    {
+        enum {
+            PageSize = std::numeric_limits<uint16_t>::max() / 4
+        };
+        SplitPointer(uint64_t ptr)
+            : big(ptr / PageSize)
+            , small(ptr % PageSize)
+        {}
+        uint64_t big;
+        uint16_t small;
+    };
+
+public:
+    PointerMap()
+    {
+        map.reserve(1024);
+    }
+
+    void addPointer(const uint64_t ptr, const AllocationIndex allocationIndex)
+    {
+        const SplitPointer pointer(ptr);
+
+        auto mapIt = map.find(pointer.big);
+        if (mapIt == map.end()) {
+            mapIt = map.insert(mapIt, std::make_pair(pointer.big, Indices()));
+        }
+        auto& indices = mapIt->second;
+        auto pageIt = std::lower_bound(indices.smallPtrParts.begin(), indices.smallPtrParts.end(), pointer.small);
+        auto allocationIt = indices.allocationIndices.begin() + distance(indices.smallPtrParts.begin(), pageIt);
+        if (pageIt == indices.smallPtrParts.end() || *pageIt != pointer.small) {
+            indices.smallPtrParts.insert(pageIt, pointer.small);
+            indices.allocationIndices.insert(allocationIt, allocationIndex);
+        } else {
+            *allocationIt = allocationIndex;
+        }
+    }
+
+    std::pair<AllocationIndex, bool> takePointer(const uint64_t ptr)
+    {
+        const SplitPointer pointer(ptr);
+
+        auto mapIt = map.find(pointer.big);
+        if (mapIt == map.end()) {
+            return {{}, false};
+        }
+        auto& indices = mapIt->second;
+        auto pageIt = std::lower_bound(indices.smallPtrParts.begin(), indices.smallPtrParts.end(), pointer.small);
+        if (pageIt == indices.smallPtrParts.end() || *pageIt != pointer.small) {
+            return  {{}, false};
+        }
+        auto allocationIt = indices.allocationIndices.begin() + distance(indices.smallPtrParts.begin(), pageIt);
+        auto index = *allocationIt;
+        indices.allocationIndices.erase(allocationIt);
+        indices.smallPtrParts.erase(pageIt);
+        if (indices.allocationIndices.empty()) {
+            map.erase(mapIt);
+        }
+        return {index, true};
+    }
+
+private:
+    struct Indices
+    {
+        std::vector<uint16_t> smallPtrParts;
+        std::vector<AllocationIndex> allocationIndices;
+    };
+    std::unordered_map<uint64_t, Indices> map;
+};
+
+#endif // POINTERMAP_H
index f761ec9..c909cc7 100644 (file)
@@ -1,2 +1,3 @@
 add_subdirectory(manual)
 add_subdirectory(auto)
+add_subdirectory(benchmarks)
\ No newline at end of file
diff --git a/tests/benchmarks/CMakeLists.txt b/tests/benchmarks/CMakeLists.txt
new file mode 100644 (file)
index 0000000..39ea65a
--- /dev/null
@@ -0,0 +1,18 @@
+include_directories(../..)
+
+include (CheckCXXSourceCompiles)
+check_cxx_source_compiles(
+    "#include <malloc.h>
+    int main() { return mallinfo().uordblks > 0; }"
+    HAVE_MALLOC_H)
+
+if (HAVE_MALLOC_H)
+    add_executable(bench_pointermap bench_pointermap.cpp)
+    add_executable(bench_pointerhash bench_pointerhash.cpp)
+
+    find_package(SparseHash)
+    if(SPARSEHASH_FOUND)
+        include_directories(${SPARSEHASH_INCLUDE_DIRS})
+        add_executable(bench_pointersparsehash bench_pointersparsehash.cpp)
+    endif()
+endif()
diff --git a/tests/benchmarks/bench_pointerhash.cpp b/tests/benchmarks/bench_pointerhash.cpp
new file mode 100644 (file)
index 0000000..40ae5a0
--- /dev/null
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2015 Milian Wolff <mail@milianw.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <unordered_map>
+
+#include "bench_pointers.h"
+#include "indices.h"
+
+struct PointerHashMap
+{
+    PointerHashMap()
+    {
+        map.reserve(65536);
+    }
+
+    void addPointer(const uint64_t ptr, const AllocationIndex index)
+    {
+        map[ptr] = index;
+    }
+
+    std::pair<AllocationIndex, bool> takePointer(const uint64_t ptr)
+    {
+        auto it = map.find(ptr);
+        if (it == map.end()) {
+            return {{}, false};
+        }
+        auto ret = std::make_pair(it->second, true);
+        map.erase(it);
+        return ret;
+    }
+
+    std::unordered_map<uint64_t, AllocationIndex> map;
+};
+
+int main()
+{
+    benchPointers<PointerHashMap>();
+    return 0;
+}
\ No newline at end of file
diff --git a/tests/benchmarks/bench_pointermap.cpp b/tests/benchmarks/bench_pointermap.cpp
new file mode 100644 (file)
index 0000000..543df64
--- /dev/null
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2015 Milian Wolff <mail@milianw.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include "pointermap.h"
+#include "bench_pointers.h"
+
+int main()
+{
+    benchPointers<PointerMap>();
+    return 0;
+}
\ No newline at end of file
diff --git a/tests/benchmarks/bench_pointers.h b/tests/benchmarks/bench_pointers.h
new file mode 100644 (file)
index 0000000..d7f2f53
--- /dev/null
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2015 Milian Wolff <mail@milianw.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#ifndef BENCH_POINTERS
+#define BENCH_POINTERS
+
+#include <cstdint>
+#include <vector>
+#include <algorithm>
+#include <iostream>
+
+#include <malloc.h>
+
+#include "indices.h"
+
+template<typename Map>
+void benchPointers()
+{
+    uint32_t matches = 0;
+    constexpr uint32_t NUM_POINTERS = 10000000;
+    {
+        std::vector<uint64_t> pointers(NUM_POINTERS);
+        const auto baseline = mallinfo().uordblks;
+        std::cerr << "allocated vector:        \t" << baseline << std::endl;
+        for (uint32_t i = 0; i < NUM_POINTERS; ++i) {
+            pointers[i] = reinterpret_cast<uint64_t>(malloc(1));
+        }
+        const auto allocated = (mallinfo().uordblks - baseline);
+        std::cerr << "allocated input pointers:\t" << allocated << std::endl;
+        for (auto ptr : pointers) {
+            free(reinterpret_cast<void*>(ptr));
+        }
+        std::cerr << "freed input pointers:    \t" << (mallinfo().uordblks - baseline) << std::endl;
+        srand(0);
+        std::random_shuffle(pointers.begin(), pointers.end());
+        malloc_trim(0);
+        std::cerr << "begin actual benchmark:  \t" << (mallinfo().uordblks - baseline) << std::endl;
+
+        {
+            Map map;
+            for (auto ptr : pointers) {
+                AllocationIndex index;
+                index.index = static_cast<uint32_t>(ptr);
+                map.addPointer(ptr, index);
+            }
+
+            const auto added = mallinfo().uordblks - baseline;
+            std::cerr << "pointers added:          \t" << added << " (" << (float(added) * 100.f / allocated) << "% overhead)" << std::endl;
+
+            std::random_shuffle(pointers.begin(), pointers.end());
+            for (auto ptr : pointers) {
+                AllocationIndex index;
+                index.index = static_cast<uint32_t>(ptr);
+                auto allocation = map.takePointer(ptr);
+                if (allocation.second && allocation.first == index) {
+                    ++matches;
+                }
+            }
+
+            std::cerr << "pointers removed:        \t" << mallinfo().uordblks << std::endl;
+            malloc_trim(0);
+            std::cerr << "trimmed:                 \t" << mallinfo().uordblks << std::endl;
+        }
+    }
+    if (matches != NUM_POINTERS) {
+        std::cerr << "FAILED!";
+        abort();
+    }
+}
+
+#endif
diff --git a/tests/benchmarks/bench_pointersparsehash.cpp b/tests/benchmarks/bench_pointersparsehash.cpp
new file mode 100644 (file)
index 0000000..4437e1d
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2015 Milian Wolff <mail@milianw.de>
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU Library General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public
+ * License along with this program; if not, write to the
+ * Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+#include <sparse_hash_map>
+#include "bench_pointers.h"
+#include "indices.h"
+
+struct PointerHashMap
+{
+    void addPointer(const uint64_t ptr, const AllocationIndex index)
+    {
+        map[ptr] = index;
+    }
+
+    std::pair<AllocationIndex, bool> takePointer(const uint64_t ptr)
+    {
+        auto it = map.find(ptr);
+        if (it == map.end()) {
+            return {{}, false};
+        }
+        auto ret = std::make_pair(it->second, true);
+        map.erase(it);
+        return ret;
+    }
+
+    google::sparse_hash_map<uint64_t, AllocationIndex> map;
+};
+
+int main()
+{
+    benchPointers<PointerHashMap>();
+    return 0;
+}
\ No newline at end of file