blob: d2e53039074f3ab5549bc6f85722f4fceecb3cc2 [file]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.
#pragma once
#include "codegen/llvm-codegen.h"
#include <unistd.h>
#include <mutex>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include "common/status.h"
#include "kudu/util/slice.h"
#include "thirdparty/datasketches/MurmurHash3.h"
#include "util/cache/cache.h"
#include "util/histogram-metric.h"
#include "util/metrics.h"
namespace impala {
/// The key to a CodeGen Cache entry.
/// It contains a hash code at the first of sizeof(struct HashCode) bytes. If the
/// cache is in NORMAL mode, the content of the key will be after the hash code,
/// which could be a combination of several keys, otherwise, in OPTIMAL mode, the
/// CodeGenCacheKey to be stored to the cache would only contain a hash code and
/// the total length to reduce the memory consumption.
/// For a key in the NORMAL mode, it would be like:
/// Hash Code| Total Length| Length of Key 1| Data of Key 1| Length of Key 2
/// | Data of Key 2| ... | Length of Key N| Data of Key N.
/// The key is constructed by CodeGenCacheKeyConstructor.
/// Functions are NOT thread-safe.
class CodeGenCacheKey {
public:
/// The type of a hashcode for the codegen cache key.
struct HashCode {
HashCode() { memset(&hash_code, 0, sizeof(HashState)); }
// This function is used by unordered_set to compare elements of HashCode.
bool operator==(const HashCode& h) const {
return (this->hash_code.h1 == h.hash_code.h1)
&& (this->hash_code.h2 == h.hash_code.h2);
}
friend std::ostream& operator<<(std::ostream& o, const HashCode& h) {
return o << std::hex << std::setfill('0') << std::setw(16) << h.hash_code.h1 << ":"
<< std::setw(16) << h.hash_code.h2;
}
string str() {
std::stringstream out;
out << *this;
return out.str();
}
HashState hash_code;
};
/// Used as the hash function in unordered_set for struct HashCode.
/// It is required by the unordered_set to generate a hash code for a
/// customized structure.
struct HashCodeHash {
uint64_t operator()(const HashCode& h) const {
// Return the first 64bits for the hash.
return h.hash_code.h1;
}
};
/// The type of the length of a general element in the codegen cache key.
typedef int ElementLengthType;
/// Construct an empty key.
CodeGenCacheKey() {}
/// Construct the key from a string. Uses move to fasten the process.
CodeGenCacheKey(string key) : key_(std::move(key)) {}
/// Set the data of the key. Uses swap to fasten the process.
void set_key(string key) { key_.swap(key); }
/// Return the hash code of the key.
HashCode hash_code() const {
DCHECK(!key_.empty());
DCHECK(key_.length() >= sizeof(HashCode));
return *(const HashCode*)key_.c_str();
}
/// Return the slice of the hash code of the key.
Slice hash_code_slice() const {
DCHECK_LE(sizeof(HashCode), key_.size());
return Slice(key_.c_str(), sizeof(HashCode));
}
/// Return the size of a key in optimal mode.
static constexpr size_t OptimalKeySize = sizeof(HashCode) + sizeof(ElementLengthType);
/// Return the slice of key in optimal mode, which is the hash code plus the total
/// length of a full key.
Slice optimal_key_slice() const {
DCHECK_LE(OptimalKeySize, key_.size());
return Slice(key_.c_str(), OptimalKeySize);
}
/// Return the hash code from a key of type Slice.
static HashCode hash_code(Slice key) {
DCHECK(!key.empty());
DCHECK_LE(sizeof(HashCode), key.size());
return *(const HashCode*)key.data();
;
}
/// Return the data of the key.
string& data() { return key_; }
Slice data_slice() const { return Slice(key_.c_str(), key_.size()); }
/// Return if the key is empty.
bool empty() const { return key_.empty(); }
private:
/// The data of the key.
std::string key_;
};
/// The class helps to construct a CodeGenCacheKey.
class CodeGenCacheKeyConstructor {
public:
/// Construct a CodeGenCacheKey from a string.
/// It could be in future to construct from a list of string keys.
static void construct(std::string&, CodeGenCacheKey*);
/// An arbitrary constant used to seed the hash.
static constexpr uint64_t CODEGEN_CACHE_HASH_SEED_CONST = 0x6b8b4567327b23c6;
};
/// The class helps to analyze the codegen cache mode.
class CodeGenCacheModeAnalyzer {
public:
// The mask should follow the definition of TCodeGenCacheMode.
// The lowest byte of the TCodeGenCacheMode is used to define the type NORMAL
// or OPTIMAL.
// The nineth bit (bit 8) is used to define whether it is a debug mode. If it is 1,
// then it is a debug mode.
static const uint64_t CODEGEN_CACHE_MODE_MASK = 0xFF;
static const uint64_t CODEGEN_CACHE_MODE_BITS = 8;
static bool is_optimal(const TCodeGenCacheMode::type& mode) {
return (mode & CODEGEN_CACHE_MODE_MASK) == TCodeGenCacheMode::OPTIMAL;
}
static bool is_debug(const TCodeGenCacheMode::type& mode) {
return (mode >> CODEGEN_CACHE_MODE_BITS) != 0;
}
};
struct CodeGenCacheEntry {
CodeGenCacheEntry() : cached_engine_pointer(nullptr) {}
// When Empty, no guarantees are made about the content of other fields.
bool Empty() { return cached_engine_pointer == nullptr; }
void Reset() { cached_engine_pointer = nullptr; }
void Reset(CodeGenObjectCache* cached_engine_ptr, int64_t num_funcs,
int64_t num_instrucs, int64_t num_opt_funcs, int64_t num_opt_instrucs,
uint64_t names_hashcode, int64_t charge, TCodeGenOptLevel::type opt) {
cached_engine_pointer = cached_engine_ptr;
num_functions = num_funcs;
num_instructions = num_instrucs;
num_opt_functions = num_opt_funcs;
num_opt_instructions = num_opt_instrucs;
function_names_hashcode = names_hashcode;
total_bytes_charge = charge;
opt_level = opt;
}
CodeGenObjectCache* cached_engine_pointer;
/// Number of functions before optimization.
int64_t num_functions;
/// Number of instructions before optimization.
int64_t num_instructions;
/// Number of functions after optimization.
int64_t num_opt_functions;
/// Number of instructions after optimization.
int64_t num_opt_instructions;
/// The hashcode of function names in the entry.
uint64_t function_names_hashcode;
/// Bytes charge including the key and the entry.
int64_t total_bytes_charge;
/// CodeGen optimization level used to generate this entry.
TCodeGenOptLevel::type opt_level;
};
/// Each CodeGenCache is supposed to be a singleton in the daemon, manages the codegen
/// cache entries, including providing the function of entry storage and look up, and
/// entry eviction if capacity limit is met.
class CodeGenCache {
public:
CodeGenCache(MetricGroup*);
~CodeGenCache() {
is_closed_ = true;
ReleaseResources();
}
/// Initilization for the codegen cache, including cache and metrics allocation.
Status Init(int64_t capacity);
/// Release the resources that occupied by the codegen cache before destruction.
void ReleaseResources();
/// Lookup the specific cache key for the cache entry. If the key doesn't exist, the
/// entry will be reset to empty.
/// Return Status::Okay unless there is any internal error to throw.
Status Lookup(const CodeGenCacheKey& key, const TCodeGenCacheMode::type& mode,
CodeGenCacheEntry* entry, std::shared_ptr<CodeGenObjectCache>* cached_engine);
bool Lookup(const CodeGenCacheKey& key, const TCodeGenCacheMode::type& mode);
/// Store the cache entry with the specific cache key.
Status Store(const CodeGenCacheKey& key, LlvmCodeGen* codegen,
TCodeGenCacheMode::type mode, TCodeGenOptLevel::type opt_level);
/// Store the shared pointer of CodeGenObjectCache to keep the compiled module cache
/// alive.
void StoreEngine(LlvmCodeGen* codegen);
/// Look up the shared pointer of the CodeGenObjectCache by its raw pointer.
/// If found, return true. Ohterwise, return false.
bool LookupEngine(const CodeGenObjectCache* cached_engine_raw_ptr,
std::shared_ptr<CodeGenObjectCache>* cached_engine = nullptr);
/// Remove the shared pointer of CodeGenObjectCache from the cache by its raw pointer
/// address.
void RemoveEngine(const CodeGenObjectCache* cached_engine_raw_ptr);
/// Increment a hit count or miss count.
void IncHitOrMissCount(bool hit) {
DCHECK(codegen_cache_hits_ && codegen_cache_misses_);
if (hit) {
codegen_cache_hits_->Increment(1);
} else {
codegen_cache_misses_->Increment(1);
}
}
/// EvictionCallback for the codegen cache.
class EvictionCallback : public Cache::EvictionCallback {
public:
EvictionCallback(CodeGenCache* cache, IntCounter* entries_evicted,
IntGauge* entries_in_use, IntGauge* entries_in_use_bytes)
: cache_(cache),
codegen_cache_entries_evicted_(entries_evicted),
codegen_cache_entries_in_use_(entries_in_use),
codegen_cache_entries_in_use_bytes_(entries_in_use_bytes) {}
virtual void EvictedEntry(Slice key, Slice value) override;
private:
CodeGenCache* cache_;
/// Metrics for the codegen cache in the process level.
IntCounter* codegen_cache_entries_evicted_;
IntGauge* codegen_cache_entries_in_use_;
IntGauge* codegen_cache_entries_in_use_bytes_;
};
private:
friend class LlvmCodeGenCacheTest;
/// Helper function to store the entry to the cache.
Status StoreInternal(const CodeGenCacheKey& key, LlvmCodeGen* codegen,
TCodeGenCacheMode::type mode, TCodeGenOptLevel::type opt_level);
/// Indicate if the cache is closed. If is closed, no call is allowed for any functions
/// other than ReleaseResources().
bool is_closed_;
/// The instance of the cache.
std::unique_ptr<Cache> cache_;
/// Metrics for the codegen cache in the process level.
IntCounter* codegen_cache_hits_;
IntCounter* codegen_cache_misses_;
IntCounter* codegen_cache_entries_evicted_;
IntGauge* codegen_cache_entries_in_use_;
IntGauge* codegen_cache_entries_in_use_bytes_;
/// Statistics for the buffer sizes allocated from the system allocator.
HistogramMetric* codegen_cache_entry_size_stats_;
/// Eviction Callback function.
std::unique_ptr<CodeGenCache::EvictionCallback> evict_callback_;
/// Protects to the to insert hash set below.
std::mutex to_insert_set_lock_;
/// The keys of codegen entries that are going to insert to the system cache.
/// The purpose of it is to prevent the same key simultaneously to be inserted.
std::unordered_set<CodeGenCacheKey::HashCode, CodeGenCacheKey::HashCodeHash>
keys_to_insert_;
/// Protects to the map of cached engines.
std::mutex cached_engines_lock_;
/// Stores shared pointers to CodeGenObjectCaches to keep the jitted functions
/// alive. The shared pointer entries could be removed when the cache entry is evicted
/// or when the whole cache is destructed.
std::unordered_map<const CodeGenObjectCache*, std::shared_ptr<CodeGenObjectCache>>
cached_engines_;
};
} // namespace impala