| /* |
| * 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/compiler.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 gem5 |
| { |
| |
| GEM5_DEPRECATED_NAMESPACE(Compressor, compression); |
| namespace compression |
| { |
| |
| FrequentValues::FrequentValues(const Params &p) |
| : Base(p), useHuffmanEncoding(p.max_code_length != 0), |
| indexEncoder(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 = indexEncoder.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 = indexEncoder.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 |
| [[maybe_unused]] const encoder::Code code = |
| indexEncoder.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 == |
| indexEncoder.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) { |
| indexEncoder.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 |
| indexEncoder.sample(uncompressedValue, ULLONG_MAX); |
| |
| indexEncoder.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 compression |
| } // namespace gem5 |