mem-cache: Implement a frequency-sampling compressor
authorDaniel R. Carvalho <odanrc@yahoo.com.br>
Tue, 25 Jun 2019 15:44:19 +0000 (17:44 +0200)
committerDaniel Carvalho <odanrc@yahoo.com.br>
Wed, 16 Dec 2020 12:13:05 +0000 (12:13 +0000)
Implementation of a generic frequency-based sampling
compressor. The compressor goes through a sampling stage,
where no compression is done, and the values are simply
sampled for their frequencies. Then, after enough samples
have been taken, the compressor starts generating
compressed data.

Compression works by comparing chunks to the table of
most frequent values. In theory, a chunk that is present
in the frequency table has its value replaced by the
index of its respective entry in the table. In practice,
the value itself is stored because there is no straight-
forward way to acquire an index from an entry.

Finally, the index can be encoded so that the values
with highest frequency have smaller codeword representation.
Its Huffman coupling can be used similar to the approach
taken in "SC 2 : A Statistical Compression Cache Scheme".

Change-Id: Iae0ebda08e8c08f3b62930fd0fb7e818fd0d141f
Signed-off-by: Daniel R. Carvalho <odanrc@yahoo.com.br>
Reviewed-on: https://gem5-review.googlesource.com/c/public/gem5/+/37335
Reviewed-by: Nikos Nikoleris <nikos.nikoleris@arm.com>
Maintainer: Nikos Nikoleris <nikos.nikoleris@arm.com>
Tested-by: kokoro <noreply+kokoro@google.com>
13 files changed:
src/mem/cache/base.cc
src/mem/cache/compressors/Compressors.py
src/mem/cache/compressors/SConscript
src/mem/cache/compressors/base.cc
src/mem/cache/compressors/base.hh
src/mem/cache/compressors/encoders/SConscript [new file with mode: 0644]
src/mem/cache/compressors/encoders/base.hh [new file with mode: 0644]
src/mem/cache/compressors/encoders/huffman.cc [new file with mode: 0644]
src/mem/cache/compressors/encoders/huffman.hh [new file with mode: 0644]
src/mem/cache/compressors/frequent_values.cc [new file with mode: 0644]
src/mem/cache/compressors/frequent_values.hh [new file with mode: 0644]
src/mem/cache/compressors/multi.cc
src/mem/cache/compressors/multi.hh

index 22c28421d18290d60f5de59a0ac7a09cd81ec1fb..5fc3456642810bd30ff1bc05833994cc8a097487 100644 (file)
@@ -123,6 +123,8 @@ BaseCache::BaseCache(const BaseCacheParams &p, unsigned blk_size)
     tags->tagsInit();
     if (prefetcher)
         prefetcher->setCache(this);
+    if (compressor)
+        compressor->setCache(this);
 }
 
 BaseCache::~BaseCache()
index b93a66a0dcdbca1e03ccf4ffcae41d0246165167..a05f1dea07edcfd86193309d6a8cf9529ae71ac8 100644 (file)
 
 from m5.params import *
 from m5.proxy import *
-from m5.SimObject import SimObject
+from m5.SimObject import *
+
+from m5.objects.IndexingPolicies import *
+from m5.objects.ReplacementPolicies import *
 
 class BaseCacheCompressor(SimObject):
     type = 'BaseCacheCompressor'
@@ -178,6 +181,38 @@ class FPCD(BaseDictionaryCompressor):
 
     dictionary_size = 2
 
+class FrequentValuesCompressor(BaseCacheCompressor):
+    type = 'FrequentValuesCompressor'
+    cxx_class = 'Compressor::FrequentValues'
+    cxx_header = "mem/cache/compressors/frequent_values.hh"
+
+    chunk_size_bits = 32
+    code_generation_ticks = Param.Unsigned(10000, "Number of elapsed " \
+        "ticks until the samples are analyzed and their codes are generated.")
+    # @todo The width of a counter width is determined by the maximum
+    # number of times a given value appears in the cache - i.e.,
+    # log2(cache_size/chunk_size_bits))".
+    counter_bits = Param.Unsigned(18, "Number of bits per frequency counter.")
+    max_code_length = Param.Unsigned(18, "Maximum number of bits in a "
+        "codeword. If 0, table indices are not encoded.")
+    num_samples = Param.Unsigned(100000, "Number of samples that must be " \
+        "taken before compression is effectively used.")
+    check_saturation = Param.Bool(False, "Whether the counters should be " \
+        "manipulated in case of saturation.")
+
+    vft_assoc = Param.Int(16, "Associativity of the VFT.")
+    vft_entries = Param.MemorySize("1024", "Number of entries of the VFT.")
+    vft_indexing_policy = Param.BaseIndexingPolicy(
+        SetAssociative(entry_size = 1, assoc = Parent.vft_assoc,
+        size = Parent.vft_entries), "Indexing policy of the VFT.")
+    vft_replacement_policy = Param.BaseReplacementPolicy(LFURP(),
+        "Replacement policy of the VFT.")
+
+    comp_chunks_per_cycle = 1
+    comp_extra_latency = 1
+    decomp_chunks_per_cycle = 1
+    decomp_extra_latency = 0
+
 class MultiCompressor(BaseCacheCompressor):
     type = 'MultiCompressor'
     cxx_class = 'Compressor::Multi'
index f696c045e00f9a03a0a81ba379d386698d031e9c..ae9570710301528c0ceb9f653c2bf49fa093f2f4 100644 (file)
@@ -36,6 +36,7 @@ Source('base_delta.cc')
 Source('cpack.cc')
 Source('fpc.cc')
 Source('fpcd.cc')
+Source('frequent_values.cc')
 Source('multi.cc')
 Source('perfect.cc')
 Source('repeated_qwords.cc')
index 9778605bf70b6d052126324bae8dcfe2cfd9fa3e..1febe7de7ee7134516fdfdcd63f8707f9552201e 100644 (file)
@@ -40,6 +40,7 @@
 
 #include "base/trace.hh"
 #include "debug/CacheComp.hh"
+#include "mem/cache/base.hh"
 #include "mem/cache/tags/super_blk.hh"
 #include "params/BaseCacheCompressor.hh"
 
@@ -82,7 +83,7 @@ Base::Base(const Params &p)
     compExtraLatency(p.comp_extra_latency),
     decompChunksPerCycle(p.decomp_chunks_per_cycle),
     decompExtraLatency(p.decomp_extra_latency),
-    stats(*this)
+    cache(nullptr), stats(*this)
 {
     fatal_if(64 % chunkSizeBits,
         "64 must be a multiple of the chunk granularity.");
@@ -97,6 +98,13 @@ Base::Base(const Params &p)
     fatal_if(blkSize < sizeThreshold, "Compressed data must fit in a block");
 }
 
+void
+Base::setCache(BaseCache *_cache)
+{
+    assert(!cache);
+    cache = _cache;
+}
+
 std::vector<Base::Chunk>
 Base::toChunks(const uint64_t* data) const
 {
index 767eda5281ecd33e49b43d06691fa8b2a6841d95..e1d03cff166b776d2955ffb06400e300165c9240 100644 (file)
@@ -42,6 +42,7 @@
 #include "base/types.hh"
 #include "sim/sim_object.hh"
 
+class BaseCache;
 class CacheBlk;
 struct BaseCacheCompressorParams;
 
@@ -119,6 +120,9 @@ class Base : public SimObject
      */
     const Cycles decompExtraLatency;
 
+    /** Pointer to the parent cache. */
+    BaseCache* cache;
+
     struct BaseStats : public Stats::Group
     {
         const Base& compressor;
@@ -193,6 +197,9 @@ class Base : public SimObject
     Base(const Params &p);
     virtual ~Base() = default;
 
+    /** The cache can only be set once. */
+    virtual void setCache(BaseCache *_cache);
+
     /**
      * Apply the compression process to the cache line. Ignores compression
      * cycles.
diff --git a/src/mem/cache/compressors/encoders/SConscript b/src/mem/cache/compressors/encoders/SConscript
new file mode 100644 (file)
index 0000000..fbf0426
--- /dev/null
@@ -0,0 +1,31 @@
+# -*- mode:python -*-
+
+# Copyright (c) 2020 Inria
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met: redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer;
+# redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution;
+# neither the name of the copyright holders nor the names of its
+# contributors may be used to endorse or promote products derived from
+# this software without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+Import('*')
+
+Source('huffman.cc')
diff --git a/src/mem/cache/compressors/encoders/base.hh b/src/mem/cache/compressors/encoders/base.hh
new file mode 100644 (file)
index 0000000..a90dac1
--- /dev/null
@@ -0,0 +1,79 @@
+/*
+ * Copyright (c) 2019, 2020 Inria
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met: redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer;
+ * redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution;
+ * neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MEM_CACHE_COMPRESSORS_ENCODERS_BASE_HH__
+#define __MEM_CACHE_COMPRESSORS_ENCODERS_BASE_HH__
+
+#include <cstdint>
+
+namespace Compressor {
+namespace Encoder {
+
+struct Code
+{
+    /** Only the LSB of the code are relevant. */
+    uint64_t code;
+    /** Number of bits in the code. */
+    unsigned length;
+};
+
+/**
+ * Base class for encoders. The goal of encoders is to provide an alternative
+ * representation to values, ideally shorter than the value. The alternative
+ * representation is called a code.
+ */
+class Base
+{
+  public:
+    Base() {}
+    virtual ~Base() = default;
+
+    /**
+     * The function responsible for the generation of the alternative value.
+     * If the size of the returning Code is greater than the maximum undelying
+     * type's size (e.g., 64 bits) the encoding results should be discarded.
+     *
+     * @param The value to be encoded.
+     * @return The encoded value.
+     */
+    virtual Code encode(const uint64_t val) const = 0;
+
+    /**
+     * Decode a value.
+     * @sa encode()
+     *
+     * @param code The encoded value.
+     * @return The original value.
+     */
+    virtual uint64_t decode(const uint64_t code) const = 0;
+};
+
+} // namespace Encoder
+} // namespace Compressor
+
+#endif //__MEM_CACHE_COMPRESSORS_ENCODERS_BASE_HH__
diff --git a/src/mem/cache/compressors/encoders/huffman.cc b/src/mem/cache/compressors/encoders/huffman.cc
new file mode 100644 (file)
index 0000000..8b75456
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+ * Copyright (c) 2019, 2020 Inria
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met: redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer;
+ * redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution;
+ * neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "mem/cache/compressors/encoders/huffman.hh"
+
+#include <cassert>
+
+#include "base/logging.hh"
+
+namespace Compressor {
+namespace Encoder {
+
+Huffman::Huffman(uint64_t max_code_length)
+  : Base(), maxCodeLength(max_code_length)
+{
+    fatal_if(maxCodeLength > 64,
+        "Code length cannot surpass its underlying container");
+}
+
+void
+Huffman::sample(uint64_t value, uint64_t frequency)
+{
+    if (frequency != 0) {
+        trees.push(new Node(value, frequency));
+    }
+}
+
+std::unique_ptr<Huffman::Node>
+Huffman::buildTree()
+{
+    // Construct tree by assigning left and right nodes. The left path leads
+    // to the most frequent values
+    while (trees.size() > 1) {
+        Node* left = trees.top();
+        trees.pop();
+
+        Node* right = trees.top();
+        trees.pop();
+
+        Node* parent = new Node(left, right);
+        trees.push(parent);
+    }
+
+    // All queue entries have been merged into a single entry containing
+    // the tree
+    Node* root = trees.top();
+    trees.pop();
+    return std::unique_ptr<Node>(root);
+}
+
+void
+Huffman::generateCodeMaps()
+{
+    valueToCode.clear();
+    codeToValue.clear();
+    generateCodes(buildTree().get(), Code());
+}
+
+void
+Huffman::generateCodes(const Node* node, const Code& current_code)
+{
+    // Drop all entries with length greater than maxCodeLength
+    if (current_code.length > maxCodeLength) {
+        return;
+    }
+
+    if (node->isLeaf()) {
+        valueToCode[node->getValue()] = current_code;
+        codeToValue[current_code.code] = node->getValue();
+    } else {
+        Code right_code = current_code;
+        right_code.code = (right_code.code << 1) + 1;
+        right_code.length++;
+        generateCodes(node->getRightSubTree(), right_code);
+
+        Code left_code = current_code;
+        left_code.code = left_code.code << 1;
+        left_code.length++;
+        generateCodes(node->getLeftSubTree(), left_code);
+    }
+}
+
+Code
+Huffman::encode(const uint64_t val) const
+{
+    auto it = valueToCode.find(val);
+    if (it == valueToCode.end()) {
+        // If the value is unknown, generate a dummy code with invalid
+        // length to let the caller know the encoding is invalid
+        Code dummy_code;
+        dummy_code.code = 0;
+        dummy_code.length = 65;
+        return dummy_code;
+    } else {
+        return it->second;
+    }
+}
+
+uint64_t
+Huffman::decode(const uint64_t code) const
+{
+    // A code that does not exist cannot be decoded
+    auto it = codeToValue.find(code);
+    assert(it != codeToValue.end());
+    return it->second;
+}
+
+} // namespace Encoder
+} // namespace Compressor
diff --git a/src/mem/cache/compressors/encoders/huffman.hh b/src/mem/cache/compressors/encoders/huffman.hh
new file mode 100644 (file)
index 0000000..946051f
--- /dev/null
@@ -0,0 +1,180 @@
+/*
+ * Copyright (c) 2019, 2020 Inria
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met: redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer;
+ * redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution;
+ * neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MEM_CACHE_COMPRESSORS_ENCODERS_HUFFMAN_HH__
+#define __MEM_CACHE_COMPRESSORS_ENCODERS_HUFFMAN_HH__
+
+#include <cassert>
+#include <cstdint>
+#include <map>
+#include <memory>
+#include <queue>
+#include <vector>
+
+#include "mem/cache/compressors/encoders/base.hh"
+
+namespace Compressor {
+namespace Encoder {
+
+/**
+ * This encoder builds a Huffman tree using the frequency of each value to
+ * be encoded.
+ */
+class Huffman : public Base
+{
+  public:
+    Huffman(uint64_t max_code_length);
+    ~Huffman() = default;
+
+    /**
+     * Inserts the value-frequency pair in the tree.
+     *
+     * @param value The value.
+     * @param frequency The value's frequency.
+     */
+    void sample(uint64_t value, uint64_t frequency);
+
+    /** Generation of the code maps. This automatically builds the tree. */
+    void generateCodeMaps();
+
+    Code encode(const uint64_t val) const override;
+    uint64_t decode(const uint64_t code) const override;
+
+  private:
+    /** Node for the Huffman tree. */
+    class Node
+    {
+      private:
+        /** Frequency of the value represented by this node. */
+        const uint64_t _frequency;
+
+        /** Value represented by this node, if this is a leaf node. */
+        const uint64_t _value;
+
+        /** The left tree. */
+        std::unique_ptr<Node> _left;
+
+        /** The right tree. */
+        std::unique_ptr<Node> _right;
+
+      public:
+        /** Initialize node as a leaf node. */
+        Node(uint64_t value, uint64_t frequency)
+          : _frequency(frequency), _value(value), _left(), _right()
+        {
+        }
+
+        /** Initialize node as an internal node. */
+        Node(Node* left, Node* right)
+          : _frequency(left->getFrequency() + right->getFrequency()),
+            _value(0), _left(left), _right(right)
+        {
+        }
+
+        /** Getter for the frequency counter. */
+        uint64_t getFrequency() const { return _frequency; }
+
+        /**
+         * Determine if the node is a leaf node by checking if it does not
+         * have sub-trees.
+         *
+         * @return Wether the node is a leaf node.
+         */
+        bool
+        isLeaf() const
+        {
+            return (_left == nullptr) && (_right == nullptr);
+        }
+
+        /**
+         * Get the leaf's value.
+         *
+         * @return The leaf's value.
+         */
+        uint64_t
+        getValue() const
+        {
+            assert(isLeaf());
+            return _value;
+        }
+
+        const Node* getLeftSubTree() const { return _left.get(); }
+        const Node* getRightSubTree() const { return _right.get(); }
+    };
+
+    /**
+     * Maximum number of bits in a codeword. If a codeword requires more
+     * than this amount of bits, its respective value is discarded.
+     */
+    const unsigned maxCodeLength;
+
+    /**
+     * Table containing the codewords and their respective lengths. Some
+     * entries are discarded due to their lengths being too big.
+     */
+    std::map<uint64_t, Code> valueToCode;
+    std::map<uint64_t, uint64_t> codeToValue;
+
+    /**
+     * Entries are not inserted directly into the tree. First they are sorted
+     * based on their frequencies.
+     */
+    struct NodeComparator
+    {
+        bool
+        operator()(const Node* lhs, const Node* rhs) const
+        {
+            return lhs->getFrequency() > rhs->getFrequency();
+        }
+    };
+    std::priority_queue<Node*, std::vector<Node*>, NodeComparator> trees;
+
+    /**
+     * Build a Huffman tree using the values and their respective
+     * frequencies, which have been informed through the insertion
+     * function.
+     *
+     * @return A pointer to the root of the tree.
+     */
+    std::unique_ptr<Node> buildTree();
+
+    /**
+     * Recursive function that generates the huffman codes based on
+     * the tree provided. The generated codes are added to the code
+     * map structure.
+     *
+     * @param node The node being analyzed.
+     * @param current_code The code so far.
+     */
+    void generateCodes(const Node* node, const Code& current_code);
+};
+
+} // namespace Encoder
+} // namespace Compressor
+
+#endif //__MEM_CACHE_COMPRESSORS_ENCODERS_HUFFMAN_HH__
diff --git a/src/mem/cache/compressors/frequent_values.cc b/src/mem/cache/compressors/frequent_values.cc
new file mode 100644 (file)
index 0000000..2806e31
--- /dev/null
@@ -0,0 +1,297 @@
+/*
+ * Copyright (c) 2019-2020 Inria
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met: redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer;
+ * redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution;
+ * neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#include "mem/cache/compressors/frequent_values.hh"
+
+#include <algorithm>
+#include <limits>
+
+#include "base/bitfield.hh"
+#include "base/intmath.hh"
+#include "base/logging.hh"
+#include "debug/CacheComp.hh"
+#include "mem/cache/prefetch/associative_set_impl.hh"
+#include "params/FrequentValuesCompressor.hh"
+
+namespace Compressor {
+
+FrequentValues::FrequentValues(const Params &p)
+  : Base(p), useHuffmanEncoding(p.max_code_length != 0),
+    encoder(p.max_code_length), counterBits(p.counter_bits),
+    codeGenerationTicks(p.code_generation_ticks),
+    checkSaturation(p.check_saturation), numVFTEntries(p.vft_entries),
+    numSamples(p.num_samples), takenSamples(0), phase(SAMPLING),
+    VFT(p.vft_assoc, p.vft_entries, p.vft_indexing_policy,
+      p.vft_replacement_policy, VFTEntry(counterBits)),
+    codeGenerationEvent([this]{ phase = COMPRESSING; }, name())
+{
+    fatal_if((numVFTEntries - 1) > mask(chunkSizeBits),
+        "There are more VFT entries than possible values.");
+}
+
+std::unique_ptr<Base::CompressionData>
+FrequentValues::compress(const std::vector<Chunk>& chunks, Cycles& comp_lat,
+    Cycles& decomp_lat)
+{
+    std::unique_ptr<CompData> comp_data =
+        std::unique_ptr<CompData>(new CompData());
+
+    // Compression size
+    std::size_t size = 0;
+
+    // Compress every value sequentially. The compressed values are then
+    // added to the final compressed data.
+    for (const auto& chunk : chunks) {
+        Encoder::Code code;
+        int length = 0;
+        if (phase == COMPRESSING) {
+            VFTEntry* entry = VFT.findEntry(chunk, false);
+
+            // Theoretically, the code would be the index of the entry;
+            // however, there is no practical need to do so, and we simply
+            // use the value instead
+            const unsigned uncompressed_index = uncompressedValue;
+            const unsigned index = entry ? chunk : uncompressed_index;
+
+            // If using an index encoder, apply it
+            if (useHuffmanEncoding) {
+                code = encoder.encode(index);
+
+                if (index == uncompressed_index) {
+                    code.length += chunkSizeBits;
+                } else if (code.length > 64) {
+                    // If, for some reason, we could not generate an encoding
+                    // for the value, generate the uncompressed encoding
+                    code = encoder.encode(uncompressed_index);
+                    assert(code.length <= 64);
+                    code.length += chunkSizeBits;
+                }
+            } else {
+                const unsigned code_size = std::log2(numVFTEntries);
+                if (entry) {
+                    code = {index, code_size};
+                } else {
+                    code = {uncompressed_index, code_size + chunkSizeBits};
+                }
+            }
+        } else {
+            // Not compressing yet; simply copy the value over
+            code = {chunk, chunkSizeBits};
+        }
+        length += code.length;
+
+        DPRINTF(CacheComp, "Compressed %016x to %016x (Size = %d) "
+            "(Phase: %d)\n", chunk, code.code, length, phase);
+
+        comp_data->compressedValues.emplace_back(code, chunk);
+
+        size += length;
+    }
+
+    // Set final compression size
+    comp_data->setSizeBits(size);
+
+    // Set latencies based on the degree of parallelization, and any extra
+    // latencies due to shifting or packaging
+    comp_lat = Cycles(compExtraLatency +
+        (chunks.size() / compChunksPerCycle));
+    decomp_lat = Cycles(decompExtraLatency +
+        (chunks.size() / decompChunksPerCycle));
+
+    // Return compressed line
+    return comp_data;
+}
+
+void
+FrequentValues::decompress(const CompressionData* comp_data, uint64_t* data)
+{
+    const CompData* casted_comp_data = static_cast<const CompData*>(comp_data);
+
+    // Decompress every entry sequentially
+    std::vector<Chunk> decomp_chunks;
+    for (const auto& comp_chunk : casted_comp_data->compressedValues) {
+        if (phase == COMPRESSING) {
+            if (useHuffmanEncoding) {
+                // Although in theory we have the codeword and have to find
+                // its corresponding value, in order to make life easier we
+                // search for the value and verify that the stored code
+                // matches the table's
+                M5_VAR_USED const Encoder::Code code =
+                    encoder.encode(comp_chunk.value);
+
+                // Either the value will be found and the codes match, or the
+                // value will not be found because it is an uncompressed entry
+                assert(((code.length <= 64) &&
+                        (code.code == comp_chunk.code.code)) ||
+                    (comp_chunk.code.code ==
+                        encoder.encode(uncompressedValue).code));
+            } else {
+                // The value at the given VFT entry must match the one stored,
+                // if it is not the uncompressed value
+                assert((comp_chunk.code.code == uncompressedValue) ||
+                    VFT.findEntry(comp_chunk.value, false));
+            }
+        }
+
+        decomp_chunks.push_back(comp_chunk.value);
+        DPRINTF(CacheComp, "Decompressed %016x to %016x\n",
+            comp_chunk.code.code, comp_chunk.value);
+    }
+
+    // Concatenate the decompressed words to generate the cache lines
+    fromChunks(decomp_chunks, data);
+}
+
+void
+FrequentValues::sampleValues(const std::vector<uint64_t> &data,
+    bool is_invalidation)
+{
+    const std::vector<Chunk> chunks = toChunks(data.data());
+    for (const Chunk& chunk : chunks) {
+        VFTEntry* entry = VFT.findEntry(chunk, false);
+        bool saturated = false;
+        if (!is_invalidation) {
+            // If a VFT hit, increase new value's counter; otherwise, insert
+            // new value
+            if (!entry) {
+                entry = VFT.findVictim(chunk);
+                assert(entry != nullptr);
+                entry->value = chunk;
+                VFT.insertEntry(chunk, false, entry);
+            } else {
+                VFT.accessEntry(entry);
+            }
+            entry->counter++;
+            saturated = entry->counter.isSaturated();
+        } else {
+            // If a VFT hit, decrease value's counter
+            if (entry) {
+                VFT.accessEntry(entry);
+                entry->counter--;
+            }
+        }
+
+        // If any counter saturates, all counters are shifted right,
+        // resulting in precision loss
+        if (checkSaturation && saturated) {
+            for (auto& entry : VFT) {
+                entry.counter >>= 1;
+            }
+        }
+    }
+
+    takenSamples += chunks.size();
+}
+
+void
+FrequentValues::generateCodes()
+{
+    // We need to find a pseudo value to store uncompressed values as
+    // For that we generate all possible values from 0 to 1 size larger
+    // than the number of real values.
+    std::set<uint64_t> uncompressed_values;
+    for (int i = 0; i < numVFTEntries+1; ++i) {
+        uncompressed_values.insert(uncompressed_values.end(), i);
+    }
+
+    for (const auto& entry : VFT) {
+        // Remove the respective real value from the list of possible
+        // pseudo values for the uncompressed value
+        uncompressed_values.erase(entry.value);
+    }
+
+    // Select the first remaining possible value as the value
+    // representing uncompressed values
+    assert(uncompressed_values.size() >= 1);
+    uncompressedValue = *uncompressed_values.begin();
+    assert(VFT.findEntry(uncompressedValue, false) == nullptr);
+
+    if (useHuffmanEncoding) {
+        // Populate the queue, adding each entry as a tree with one node.
+        // They are sorted such that the value with highest frequency is
+        // the queue's top
+        for (const auto& entry : VFT) {
+            encoder.sample(entry.value, entry.counter);
+        }
+
+        // Insert the uncompressed value in the tree assuming it has the
+        // highest frequency, since it is in fact a group of all the values
+        // not present in the VFT
+        encoder.sample(uncompressedValue, ULLONG_MAX);
+
+        encoder.generateCodeMaps();
+    }
+
+    // Generate the code map and mark the current phase as code generation
+    phase = CODE_GENERATION;
+
+    // Let us know when to change from the code generation phase to the
+    // effective compression phase
+    schedule(codeGenerationEvent, curTick() + codeGenerationTicks);
+}
+
+void
+FrequentValues::probeNotify(const DataUpdate &data_update)
+{
+    // Do not update VFT if not sampling
+    if (phase == SAMPLING) {
+        // If the new data is not present, the notification is due to a
+        // fill; otherwise, sample the old block's contents
+        if (data_update.oldData.size() > 0) {
+            sampleValues(data_update.oldData, true);
+        }
+        // If the new data is not present, the notification is due to an
+        // invalidation; otherwise, sample the new block's contents
+        if (data_update.newData.size() > 0) {
+            sampleValues(data_update.newData, false);
+        }
+
+        // Check if it is done with the sampling phase. If so, generate the
+        // codes that will be used for the compression phase
+        if (takenSamples >= numSamples) {
+            generateCodes();
+        }
+    }
+}
+
+void
+FrequentValues::regProbeListeners()
+{
+    assert(listeners.empty());
+    assert(cache != nullptr);
+    listeners.push_back(new FrequentValuesListener(
+        *this, cache->getProbeManager(), "Data Update"));
+}
+
+void
+FrequentValues::FrequentValuesListener::notify(const DataUpdate &data_update)
+{
+    parent.probeNotify(data_update);
+}
+
+} // namespace Compressor
diff --git a/src/mem/cache/compressors/frequent_values.hh b/src/mem/cache/compressors/frequent_values.hh
new file mode 100644 (file)
index 0000000..0624759
--- /dev/null
@@ -0,0 +1,223 @@
+/*
+ * Copyright (c) 2019-2020 Inria
+ * All rights reserved.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are
+ * met: redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer;
+ * redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution;
+ * neither the name of the copyright holders nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+ * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+ * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+ * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+ * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+ * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+ * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+ * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+ */
+
+#ifndef __MEM_CACHE_COMPRESSORS_FREQUENT_VALUES_HH__
+#define __MEM_CACHE_COMPRESSORS_FREQUENT_VALUES_HH__
+
+#include <climits>
+#include <cstdint>
+#include <memory>
+#include <vector>
+
+#include "base/sat_counter.hh"
+#include "base/types.hh"
+#include "mem/cache/base.hh"
+#include "mem/cache/compressors/base.hh"
+#include "mem/cache/compressors/encoders/huffman.hh"
+#include "mem/cache/prefetch/associative_set.hh"
+#include "sim/eventq.hh"
+#include "sim/probe/probe.hh"
+
+struct FrequentValuesCompressorParams;
+
+namespace Compressor {
+
+/**
+ * This compressor samples the cache for a while, trying to define the
+ * most frequently used values. When these values are determined, they are
+ * associated to shorter representations (codes). Then the compressor can
+ * start its effective compression phase, in which occurrences of these
+ * values are substituted by their codes.
+ */
+class FrequentValues : public Base
+{
+  private:
+    class CompData;
+
+    using DataUpdate = BaseCache::DataUpdate;
+
+    class FrequentValuesListener : public ProbeListenerArgBase<DataUpdate>
+    {
+      protected:
+        FrequentValues &parent;
+
+      public:
+        FrequentValuesListener(FrequentValues &_parent, ProbeManager *pm,
+            const std::string &name)
+          : ProbeListenerArgBase(pm, name), parent(_parent)
+        {
+        }
+        void notify(const DataUpdate &data_update) override;
+    };
+    std::vector<FrequentValuesListener*> listeners;
+
+    /** Whether Huffman encoding is applied to the VFT indices. */
+    const bool useHuffmanEncoding;
+
+    /** The encoder applied to the VFT indices. */
+    Encoder::Huffman encoder;
+
+    /** Number of bits in the saturating counters. */
+    const int counterBits;
+
+    /** Ticks needed to perform the CODE_GENERATION phase. */
+    const Tick codeGenerationTicks;
+
+    /** Whether an action must be performed when counters saturate. */
+    const bool checkSaturation;
+
+    /** Maximum number of VFT entries, and thus of codewords too. */
+    const unsigned numVFTEntries;
+
+    /** Number of samples in the sampling phase. */
+    const unsigned numSamples;
+
+    /** Number of samples taken so far. */
+    unsigned takenSamples;
+
+    /**
+     * The phase that the compressor is at. It assumes that sampling and
+     * code generation are done only once.
+     */
+    enum Phase {SAMPLING, CODE_GENERATION, COMPRESSING};
+    Phase phase;
+
+    class VFTEntry : public TaggedEntry
+    {
+      public:
+        /**
+         * The value is stored as a 64 bit entry to accomodate for the
+         * uncompressed value. All real values must be 32 bits.
+         */
+        uint64_t value;
+
+        /**
+         * The ideal counter width (in bits) is determined by the maximum
+         * number of times a given value appears in the cache
+         * (log2(cache_size / chunkSizeBits)). If smaller counters are used
+         * their values should be rescaled when saturated.
+         */
+        SatCounter32 counter;
+
+        VFTEntry(std::size_t num_bits)
+          : TaggedEntry(), value(0), counter(num_bits)
+        {
+        }
+
+        void
+        invalidate() override
+        {
+            TaggedEntry::invalidate();
+            value = 0;
+            counter.reset();
+        }
+    };
+
+    /**
+     * The Value Frequency Table, a small cache that keeps track and estimates
+     * the frequency distribution of values in the cache.
+     */
+    AssociativeSet<VFTEntry> VFT;
+
+    /**
+     * A pseudo value is used as the representation of uncompressed values.
+     * This value is a random value that is not present in the VFT. It is
+     * selected at the end of the sampling phase.
+     */
+    uint64_t uncompressedValue;
+
+    /** Event to handle finishing code generation and starting compression. */
+    EventFunctionWrapper codeGenerationEvent;
+
+    /**
+     * Sample values from a packet, adding them to the VFT.
+     *
+     * @param data The line being sampled.
+     * @param is_invalidation whether this event comes from an invalidation.
+     */
+    void sampleValues(const std::vector<uint64_t> &data,
+        bool is_invalidation);
+
+    /** End sampling phase and start the code generation. */
+    void generateCodes();
+
+    std::unique_ptr<Base::CompressionData> compress(
+        const std::vector<Chunk>& chunks, Cycles& comp_lat,
+        Cycles& decomp_lat) override;
+
+    void decompress(const CompressionData* comp_data, uint64_t* data) override;
+
+  public:
+    typedef FrequentValuesCompressorParams Params;
+    FrequentValues(const Params &p);
+    ~FrequentValues() = default;
+
+    /**
+     * Process a notification event from the ProbeListener.
+     *
+     * @param data_update The data regarding the entry's contents update.
+     */
+    void probeNotify(const DataUpdate &data_update);
+
+    void regProbeListeners() override;
+};
+
+class FrequentValues::CompData : public CompressionData
+{
+  public:
+    /**
+     * A compressed value contains its encoding, and the compressed data
+     * itself.
+     */
+    struct CompressedValue
+    {
+        /** The codeword.*/
+        Encoder::Code code;
+
+        /**
+         * Original value, stored both for when the codeword marks an
+         * uncompressed entry, and to verify correctness.
+         */
+        uint64_t value;
+
+        CompressedValue(Encoder::Code _code, uint64_t _value)
+          : code(_code), value(_value)
+        {
+        }
+    };
+
+    /**
+     * The values contained in the original data, after being compressed
+     * sequentially.
+     */
+    std::vector<CompressedValue> compressedValues;
+};
+
+} // namespace Compressor
+
+#endif //__MEM_CACHE_COMPRESSORS_FREQUENT_VALUES_HH__
index 52a4112c102770681d86a2e519e68e6ae4a749e7..8fc8f3536a5afd8f72064ccf29490b27998fabee 100644 (file)
@@ -73,6 +73,15 @@ Multi::~Multi()
     }
 }
 
+void
+Multi::setCache(BaseCache *_cache)
+{
+    Base::setCache(_cache);
+    for (auto& compressor : compressors) {
+        compressor->setCache(_cache);
+    }
+}
+
 std::unique_ptr<Base::CompressionData>
 Multi::compress(const std::vector<Chunk>& chunks, Cycles& comp_lat,
     Cycles& decomp_lat)
index 559501a65fb546129f8e409f21d031de49879831..ec49401ef4b6ba2a9bddddd59967f0eb102ecbc7 100644 (file)
@@ -101,6 +101,8 @@ class Multi : public Base
     Multi(const Params &p);
     ~Multi();
 
+    void setCache(BaseCache *_cache) override;
+
     std::unique_ptr<Base::CompressionData> compress(
         const std::vector<Base::Chunk>& chunks,
         Cycles& comp_lat, Cycles& decomp_lat) override;