include/core/CMemoryDef.h (534 lines of code) (raw):

/* * Copyright Elasticsearch B.V. and/or licensed to Elasticsearch B.V. under one * or more contributor license agreements. Licensed under the Elastic License * 2.0 and the following additional limitation. Functionality enabled by the * files subject to the Elastic License 2.0 may only be used in production when * invoked by an Elasticsearch process with a license key installed that permits * use of machine learning features. You may not use this file except in * compliance with the Elastic License 2.0 and the foregoing additional * limitation. */ #ifndef INCLUDED_ml_core_CMemoryDef_h #define INCLUDED_ml_core_CMemoryDef_h #include <core/CMemoryDec.h> #include <core/CLogger.h> #include <core/UnwrapRef.h> #include <algorithm> #include <functional> #include <typeinfo> namespace ml { namespace core { using TTypeInfoCRef = std::reference_wrapper<const std::type_info>; namespace memory_detail { //! \name Helpers for expectMemoryOverload. //!{ // clang-format off template<typename T, typename = void> struct SContainerLike : std::false_type {}; template<typename T> struct SContainerLike<T, std::void_t<typename T::const_iterator>> : std::true_type {}; template<typename T, typename = void> struct SUserSuppliedMemoryUsageFunction : std::false_type {}; template<typename T> struct SUserSuppliedMemoryUsageFunction<T, std::enable_if_t< std::is_same_v<decltype(&T::memoryUsage), std::size_t (T::*)() const>>> : std::true_type { }; // clang-format on //@} //! Check if we expect an overloaded dynamicSize function. template<typename T> constexpr bool expectMemoryOverload() { return (SContainerLike<T>::value || std::is_same<T, std::any>::value) && !SUserSuppliedMemoryUsageFunction<T>::value && !SDynamicSizeAlwaysZero<T>::value(); } //! \brief Default template declaration for CMemoryDynamicSize::dispatch. template<typename T, typename = void> struct SMemoryDynamicSize { static std::size_t dispatch(const T&) { return 0; } }; //! \brief Template specialisation where T has member function "memoryUsage()". // clang-format off template<typename T> struct SMemoryDynamicSize<T, std::enable_if_t< std::is_same_v<decltype(&T::memoryUsage), std::size_t (T::*)() const>>> { static std::size_t dispatch(const T& t) { return t.memoryUsage(); } }; // clang-format on //! \name Helpers for expectDebugMemoryOverload. //@{ // clang-format off template<typename T, typename = void> struct SUserSuppliedDebugMemoryUsageFunction : std::false_type {}; template<typename T> struct SUserSuppliedDebugMemoryUsageFunction<T, std::enable_if_t< std::is_same_v<decltype(&T::debugMemoryUsage), void (T::*)(const CMemoryUsage::TMemoryUsagePtr&) const>>> : std::true_type { }; // clang-format on //@} //! Check if we expect an overloaded dynamicSize function. template<typename T> constexpr bool expectDebugMemoryOverload() { return (SContainerLike<T>::value || std::is_same<T, std::any>::value) && !SUserSuppliedDebugMemoryUsageFunction<T>::value && !SDynamicSizeAlwaysZero<T>::value(); } //! Check if a small vector is using in-place storage. //! //! A small vector is only using in-place storage if the end of its //! current capacity is closer to its address than its size. Note //! that we can't simply check if capacity > N because N is treated //! as a guideline. template<typename T, std::size_t N> bool inplace(const CSmallVector<T, N>& t) { const char* address = reinterpret_cast<const char*>(&t); const char* storage = reinterpret_cast<const char*>(t.data()); return storage >= address && storage < address + sizeof t; } //! Default template declaration for SDebugMemoryDynamicSize::dispatch. template<typename T, typename = void> struct SDebugMemoryDynamicSize { static void dispatch(const char* name, const T& t, const CMemoryUsage::TMemoryUsagePtr& mem) { std::size_t used = memory::dynamicSize(t); if (used > 0) { std::string description(name); description += "::"; description += typeid(T).name(); mem->addItem(description, used); } } }; //! Template specialisation for when T has a debugMemoryUsage member function. // clang-format off template<typename T> struct SDebugMemoryDynamicSize<T, std::enable_if_t< std::is_same_v<decltype(&T::debugMemoryUsage), void (T::*)(const CMemoryUsage::TMemoryUsagePtr&) const>>> { static void dispatch(const char*, const T& t, const CMemoryUsage::TMemoryUsagePtr& mem) { t.debugMemoryUsage(mem->addChild()); } }; // clang-format on //! \brief Total ordering of type_info objects. struct CORE_EXPORT STypeInfoLess { template<typename T> bool operator()(const std::pair<TTypeInfoCRef, T>& lhs, const std::pair<TTypeInfoCRef, T>& rhs) const { return unwrap_ref(lhs.first).before(unwrap_ref(rhs.first)); } template<typename T> bool operator()(const std::pair<TTypeInfoCRef, T>& lhs, TTypeInfoCRef rhs) const { return unwrap_ref(lhs.first).before(unwrap_ref(rhs)); } template<typename T> bool operator()(TTypeInfoCRef lhs, const std::pair<TTypeInfoCRef, T>& rhs) const { return unwrap_ref(lhs).before(unwrap_ref(rhs.first)); } }; } namespace memory { //! Default implementation for non-pointer types. template<typename T> std::size_t dynamicSize(const T& t, std::enable_if_t<!std::is_pointer_v<T>>*) { // If you hit this assert there are one of three reasons: // 1. You need to add a memoryUsage function to your type, // 2. You need to add a dynamicSizeAlwaysZero function to your type, // 3. You need to include CMemoryDefStd.h. static_assert(!memory_detail::expectMemoryOverload<T>(), "Maybe miss accounting for memory usage"); if constexpr (!memory_detail::SDynamicSizeAlwaysZero<T>::value()) { return memory_detail::SMemoryDynamicSize<T>::dispatch(t); } return 0; } //! Default implementation for pointer types. template<typename T> std::size_t dynamicSize(const T& t, std::enable_if_t<std::is_pointer_v<T>>*) { return t == nullptr ? 0 : memory::staticSize(*t) + memory::dynamicSize(*t); } template<typename T, typename DELETER> std::size_t dynamicSize(const std::unique_ptr<T, DELETER>& t) { return t == nullptr ? 0 : memory::staticSize(*t) + memory::dynamicSize(*t); } template<typename T> std::size_t dynamicSize(const std::shared_ptr<T>& t) { // The check for nullptr here may seem unnecessary but there are situations // where an unset shared_ptr can have a use_count greater than 0, see // https://stackoverflow.com/questions/48885252/c-sharedptr-use-count-for-nullptr/48885643 long uc{t == nullptr ? 0 : t.use_count()}; if (uc == 0) { return 0; } // Note we add on sizeof(long) here to account for the memory // used by the shared reference count. Also, round up. return (sizeof(long) + memory::staticSize(*t) + memory::dynamicSize(*t) + std::size_t(uc - 1)) / uc; } template<typename T, std::size_t N> std::size_t dynamicSize(const std::array<T, N>& t) { return memory::elementDynamicSize(t); } template<typename T, typename A> std::size_t dynamicSize(const std::vector<T, A>& t) { return memory::elementDynamicSize(t) + sizeof(T) * t.capacity(); } template<typename T, std::size_t N> std::size_t dynamicSize(const CSmallVector<T, N>& t) { return memory::elementDynamicSize(t) + (memory_detail::inplace(t) ? 0 : t.capacity()) * sizeof(T); } template<typename K, typename V, typename H, typename P, typename A> std::size_t dynamicSize(const boost::unordered_map<K, V, H, P, A>& t) { return memory::elementDynamicSize(t) + (t.bucket_count() * memory::storageNodeOverhead(t)) + (t.size() * (sizeof(K) + sizeof(V) + memory::storageNodeOverhead(t))) + memory::bucketGroupOverhead(t); } template<typename K, typename V, typename C, typename A> std::size_t dynamicSize(const boost::container::flat_map<K, V, C, A>& t) { return memory::elementDynamicSize(t) + t.capacity() * sizeof(std::pair<K, V>); } template<typename T, typename H, typename P, typename A> std::size_t dynamicSize(const boost::unordered_set<T, H, P, A>& t) { return memory::elementDynamicSize(t) + (t.bucket_count() * sizeof(std::size_t) * 2) + (t.size() * (sizeof(T) + memory::storageNodeOverhead(t))) + memory::bucketGroupOverhead(t); } template<typename T, typename C, typename A> std::size_t dynamicSize(const boost::container::flat_set<T, C, A>& t) { return memory::elementDynamicSize(t) + t.capacity() * sizeof(T); } template<typename T, typename A> std::size_t dynamicSize(const boost::circular_buffer<T, A>& t) { return memory::elementDynamicSize(t) + t.capacity() * sizeof(T); } template<typename T> std::size_t dynamicSize(const std::optional<T>& t) { return !t ? 0 : memory::dynamicSize(*t); } template<typename T> std::size_t dynamicSize(const std::reference_wrapper<T>& /*t*/) { return 0; } template<typename T, typename V> std::size_t dynamicSize(const std::pair<T, V>& t) { std::size_t mem = 0; if constexpr (!memory_detail::SDynamicSizeAlwaysZero<T>::value()) { mem += memory::dynamicSize(t.first); } if constexpr (!memory_detail::SDynamicSizeAlwaysZero<V>::value()) { mem += memory::dynamicSize(t.second); } return mem; } template<typename T, typename I, typename A> std::size_t dynamicSize(const boost::multi_index::multi_index_container<T, I, A>& t); template<typename CONTAINER> std::size_t elementDynamicSize(const CONTAINER& t) { std::size_t mem = 0; if constexpr (!memory_detail::SDynamicSizeAlwaysZero<typename CONTAINER::value_type>::value()) { for (const auto& v : t) { mem += memory::dynamicSize(v); } } return mem; } //! Implements a visitor pattern for computing the size of types stored in //! std::any. //! //! DESCRIPTION:\n //! The idea of this class is a place for users of dynamicSize to register //! callbacks to compute the size of objects stored in std::any. The user //! must ensure that all types have registered callbacks before trying to //! compute their memory usage. It will warn if a type is visited which is //! not registered. Example usage: //! \code{.cpp} //! memory::anyVisitor().insertCallback<std::vector<double>>(); //! std::vector<std::any> variables; //! variables.push_back(TDoubleVec(10)); //! std::size_t size{memory::dynamicSize(variables, visitor)}; //! \endcode //! //! IMPLEMENTATION DECISIONS:\n //! There is no locking to modify the vistor. This is because we expect that //! callbacks are registered once as part of static initialisation. class CORE_EXPORT CAnyVisitor { public: using TDynamicSizeFunc = std::size_t (*)(const std::any& any); using TTypeInfoDynamicSizeFuncPr = std::pair<TTypeInfoCRef, TDynamicSizeFunc>; using TTypeInfoDynamicSizeFuncPrVec = std::vector<TTypeInfoDynamicSizeFuncPr>; public: static CAnyVisitor& instance() { static CAnyVisitor instance; return instance; } public: CAnyVisitor(const CAnyVisitor&) = delete; CAnyVisitor& operator=(const CAnyVisitor&) = delete; //! Insert a callback to compute the size of the type T //! if it is stored in std::any. template<typename T> bool registerCallback() { auto i = std::lower_bound(m_Callbacks.begin(), m_Callbacks.end(), std::cref(typeid(T)), memory_detail::STypeInfoLess()); if (i == m_Callbacks.end()) { m_Callbacks.emplace_back(std::cref(typeid(T)), &CAnyVisitor::dynamicSizeCallback<T>); return true; } if (i->first.get() != typeid(T)) { m_Callbacks.insert( i, {std::cref(typeid(T)), &CAnyVisitor::dynamicSizeCallback<T>}); return true; } return false; } //! Calculate the dynamic size of x if a callback has been //! registered for its type. std::size_t dynamicSize(const std::any& x) const { if (x.has_value()) { auto i = std::lower_bound(m_Callbacks.begin(), m_Callbacks.end(), std::cref(x.type()), memory_detail::STypeInfoLess()); if (i != m_Callbacks.end() && i->first.get() == x.type()) { return (*i->second)(x); } LOG_ERROR(<< "No callback registered for " << x.type().name()); } return 0; } private: CAnyVisitor() = default; private: //! Wraps up call to any_cast and dynamicSize. template<typename T> static std::size_t dynamicSizeCallback(const std::any& any) { try { return sizeof(T) + memory::dynamicSize(std::any_cast<const T&>(any)); } catch (const std::exception& e) { LOG_ERROR(<< "Failed to calculate size " << e.what()); } return 0; } private: TTypeInfoDynamicSizeFuncPrVec m_Callbacks; }; //! Get the any visitor singleton. CORE_EXPORT CAnyVisitor& anyVisitor(); } namespace memory_debug { //! Default implementation for non-pointer types. template<typename T> void dynamicSize(const char* name, const T& t, const CMemoryUsage::TMemoryUsagePtr& mem, std::enable_if_t<!std::is_pointer_v<T>>*) { // If you hit this assert there are one of three reasons: // 1. You need to add a debugMemoryUsage function to your type, // 2. You need to add a dynamicSizeAlwaysZero function to your type, // 3. You need to include CMemoryDefStd.h. static_assert(!memory_detail::expectDebugMemoryOverload<T>(), "Maybe miss accounting for memory usage"); memory_detail::SDebugMemoryDynamicSize<T>::dispatch(name, t, mem); } //! Default implementation for pointer types. template<typename T> void dynamicSize(const char* name, const T& t, const CMemoryUsage::TMemoryUsagePtr& mem, std::enable_if_t<std::is_pointer_v<T>>*) { if (t != nullptr) { std::string ptrName(name); ptrName += "_ptr"; mem->addItem(ptrName.c_str(), memory::staticSize(*t)); memory_detail::SDebugMemoryDynamicSize<T>::dispatch(name, *t, mem); } } template<typename T> void dynamicSize(const char* name, const std::unique_ptr<T>& t, const CMemoryUsage::TMemoryUsagePtr& mem) { if (t != nullptr) { std::string ptrName(name); ptrName += "_ptr"; mem->addItem(ptrName.c_str(), memory::staticSize(*t)); memory_detail::SDebugMemoryDynamicSize<T>::dispatch(name, *t, mem); } } template<typename T> void dynamicSize(const char* name, const std::shared_ptr<T>& t, const CMemoryUsage::TMemoryUsagePtr& mem) { // The check for nullptr here may seem unnecessary but there are situations // where an unset shared_ptr can have a use_count greater than 0, see // https://stackoverflow.com/questions/48885252/c-sharedptr-use-count-for-nullptr/48885643 long uc{t == nullptr ? 0 : t.use_count()}; if (uc == 0) { return; } // If the pointer is shared by multiple users, each one // might count it, so divide by the number of users. // However, if only 1 user has it, do a full debug. std::string ptrName(name); if (uc == 1) { // Note we add on sizeof(long) here to account for // the memory used by the shared reference count. ptrName += "_shared_ptr"; // Note we add on sizeof(long) here to account for // the memory used by the shared reference count. mem->addItem(ptrName, sizeof(long) + memory::staticSize(*t)); memory_debug::dynamicSize(name, *t, mem); } else { ptrName += "_shared_ptr (x" + std::to_string(uc) + ")"; // Note we add on sizeof(long) here to account for // the memory used by the shared reference count. // Also, round up. mem->addItem(ptrName, (sizeof(long) + memory::staticSize(*t) + memory::dynamicSize(*t) + std::size_t(uc - 1)) / uc); } } template<typename T, std::size_t N> void dynamicSize(const char* name, const std::array<T, N>& t, const CMemoryUsage::TMemoryUsagePtr& mem) { if constexpr (!memory_detail::SDynamicSizeAlwaysZero<T>::value()) { std::string elementName{name}; CMemoryUsage::TMemoryUsagePtr ptr = mem->addChild(); memory_debug::elementDynamicSize(std::move(elementName), t, mem); } } template<typename T, typename A> void dynamicSize(const char* name, const std::vector<T, A>& t, const CMemoryUsage::TMemoryUsagePtr& mem) { std::string componentName(name); std::size_t items = t.size(); std::size_t capacity = t.capacity(); CMemoryUsage::SMemoryUsage usage(componentName + "::" + typeid(T).name(), capacity * sizeof(T), (capacity - items) * sizeof(T)); CMemoryUsage::TMemoryUsagePtr ptr = mem->addChild(); ptr->setName(usage); memory_debug::elementDynamicSize(std::move(componentName), t, mem); } template<typename T, std::size_t N> void dynamicSize(const char* name, const CSmallVector<T, N>& t, const CMemoryUsage::TMemoryUsagePtr& mem) { std::string componentName(name); std::size_t items = memory_detail::inplace(t) ? 0 : t.size(); std::size_t capacity = memory_detail::inplace(t) ? 0 : t.capacity(); CMemoryUsage::SMemoryUsage usage(componentName + "::" + typeid(T).name(), capacity * sizeof(T), (capacity - items) * sizeof(T)); CMemoryUsage::TMemoryUsagePtr ptr = mem->addChild(); ptr->setName(usage); memory_debug::elementDynamicSize(std::move(componentName), t, mem); } template<typename K, typename V, typename H, typename P, typename A> void dynamicSize(const char* name, const boost::unordered_map<K, V, H, P, A>& t, const CMemoryUsage::TMemoryUsagePtr& mem) { std::string componentName(name); componentName += "_umap"; std::size_t mapSize = (t.bucket_count() * memory::storageNodeOverhead(t)) + (t.size() * (sizeof(K) + sizeof(V) + memory::storageNodeOverhead(t))) + memory::bucketGroupOverhead(t); CMemoryUsage::SMemoryUsage usage(componentName, mapSize); CMemoryUsage::TMemoryUsagePtr ptr = mem->addChild(); ptr->setName(usage); memory_debug::associativeElementDynamicSize(std::move(componentName), t, mem); } template<typename K, typename V, typename C, typename A> void dynamicSize(const char* name, const boost::container::flat_map<K, V, C, A>& t, const CMemoryUsage::TMemoryUsagePtr& mem) { std::string componentName(name); componentName += "_fmap"; std::size_t items = t.size(); std::size_t capacity = t.capacity(); CMemoryUsage::SMemoryUsage usage( componentName + "::" + typeid(std::pair<K, V>).name(), capacity * sizeof(std::pair<K, V>), (capacity - items) * sizeof(std::pair<K, V>)); CMemoryUsage::TMemoryUsagePtr ptr = mem->addChild(); ptr->setName(usage); memory_debug::associativeElementDynamicSize(std::move(componentName), t, mem); } template<typename T, typename H, typename P, typename A> void dynamicSize(const char* name, const boost::unordered_set<T, H, P, A>& t, const CMemoryUsage::TMemoryUsagePtr& mem) { std::string componentName(name); componentName += "_uset"; std::size_t setSize = (t.bucket_count() * memory::storageNodeOverhead(t)) + (t.size() * (sizeof(T) + memory::storageNodeOverhead(t))) + memory::bucketGroupOverhead(t); CMemoryUsage::SMemoryUsage usage(componentName, setSize); CMemoryUsage::TMemoryUsagePtr ptr = mem->addChild(); ptr->setName(usage); memory_debug::elementDynamicSize(std::move(componentName), t, mem); } template<typename T, typename C, typename A> void dynamicSize(const char* name, const boost::container::flat_set<T, C, A>& t, const CMemoryUsage::TMemoryUsagePtr& mem) { std::string componentName(name); componentName += "_fset"; std::size_t items = t.size(); std::size_t capacity = t.capacity(); CMemoryUsage::SMemoryUsage usage(componentName + "::" + typeid(T).name(), capacity * sizeof(T), (capacity - items) * sizeof(T)); CMemoryUsage::TMemoryUsagePtr ptr = mem->addChild(); ptr->setName(usage); memory_debug::elementDynamicSize(std::move(componentName), t, mem); } template<typename T, typename A> void dynamicSize(const char* name, const boost::circular_buffer<T, A>& t, const CMemoryUsage::TMemoryUsagePtr& mem) { std::string componentName(name); std::size_t items = t.size(); std::size_t capacity = t.capacity(); CMemoryUsage::SMemoryUsage usage(componentName + "::" + typeid(T).name(), capacity * sizeof(T), (capacity - items) * sizeof(T)); CMemoryUsage::TMemoryUsagePtr ptr = mem->addChild(); ptr->setName(usage); memory_debug::elementDynamicSize(std::move(componentName), t, mem); } template<typename T> void dynamicSize(const char* name, const std::optional<T>& t, const CMemoryUsage::TMemoryUsagePtr& mem) { if (t) { memory_debug::dynamicSize(name, *t, mem); } } template<typename T> void dynamicSize(const char* /*name*/, const std::reference_wrapper<T>& /*t*/, const CMemoryUsage::TMemoryUsagePtr& /*mem*/) { } template<typename U, typename V> void dynamicSize(const char* name, const std::pair<U, V>& t, const CMemoryUsage::TMemoryUsagePtr& mem) { if (!memory_detail::SDynamicSizeAlwaysZero<U>::value()) { std::string keyName(name); keyName += "_first"; memory_debug::dynamicSize(keyName.c_str(), t.first, mem); } if (!memory_detail::SDynamicSizeAlwaysZero<V>::value()) { std::string valueName(name); valueName += "_second"; memory_debug::dynamicSize(valueName.c_str(), t.second, mem); } } template<typename CONTAINER> void associativeElementDynamicSize(std::string name, const CONTAINER& t, const CMemoryUsage::TMemoryUsagePtr& mem) { if constexpr (!memory_detail::SDynamicSizeAlwaysZero<typename CONTAINER::value_type>::value()) { std::string keyName{name + "_key"}; std::string valueName{name + "_value"}; for (const auto & [ key, value ] : t) { memory_debug::dynamicSize(keyName.c_str(), key, mem); memory_debug::dynamicSize(valueName.c_str(), value, mem); } } } template<typename CONTAINER> void elementDynamicSize(std::string name, const CONTAINER& t, const CMemoryUsage::TMemoryUsagePtr& mem) { if constexpr (!memory_detail::SDynamicSizeAlwaysZero<typename CONTAINER::value_type>::value()) { std::string elementName{name}; elementName += "_item"; for (const auto& v : t) { memory_debug::dynamicSize(elementName.c_str(), v, mem); } } } //! Implements a visitor pattern for computing the size of types //! stored in std::any. //! //! DESCRIPTION:\n //! See memory::CAnyVisitor for details. class CORE_EXPORT CAnyVisitor { public: using TDynamicSizeFunc = void (*)(const char*, const std::any& any, const CMemoryUsage::TMemoryUsagePtr& mem); using TTypeInfoDynamicSizeFuncPr = std::pair<TTypeInfoCRef, TDynamicSizeFunc>; using TTypeInfoDynamicSizeFuncPrVec = std::vector<TTypeInfoDynamicSizeFuncPr>; public: static CAnyVisitor& instance() { static CAnyVisitor instance; return instance; } public: CAnyVisitor(const CAnyVisitor&) = delete; CAnyVisitor& operator=(const CAnyVisitor&) = delete; //! Insert a callback to compute the size of the type T //! if it is stored in std::any. template<typename T> bool registerCallback() { auto i = std::lower_bound(m_Callbacks.begin(), m_Callbacks.end(), std::cref(typeid(T)), memory_detail::STypeInfoLess()); if (i == m_Callbacks.end()) { m_Callbacks.emplace_back(std::cref(typeid(T)), &CAnyVisitor::dynamicSizeCallback<T>); return true; } if (i->first.get() != typeid(T)) { m_Callbacks.insert( i, {std::cref(typeid(T)), &CAnyVisitor::dynamicSizeCallback<T>}); return true; } return false; } //! Calculate the dynamic size of x if a callback has been //! registered for its type. void dynamicSize(const char* name, const std::any& x, const CMemoryUsage::TMemoryUsagePtr& mem) const { if (x.has_value()) { auto i = std::lower_bound(m_Callbacks.begin(), m_Callbacks.end(), std::cref(x.type()), memory_detail::STypeInfoLess()); if (i != m_Callbacks.end() && i->first.get() == x.type()) { (*i->second)(name, x, mem); return; } LOG_ERROR(<< "No callback registered for " << x.type().name()); } } private: CAnyVisitor() = default; private: //! Wraps up call to any_cast and dynamicSize. template<typename T> static void dynamicSizeCallback(const char* name, const std::any& any, const CMemoryUsage::TMemoryUsagePtr& mem) { try { mem->addItem(name, sizeof(T)); memory_debug::dynamicSize(name, std::any_cast<const T&>(any), mem); } catch (const std::exception& e) { LOG_ERROR(<< "Failed to calculate size " << e.what()); } } private: TTypeInfoDynamicSizeFuncPrVec m_Callbacks; }; //! Get the any visitor singleton. CORE_EXPORT CAnyVisitor& anyVisitor(); } } } #endif // INCLUDED_ml_core_CMemoryDef_h