Files
2026-07-13 12:47:05 +08:00

367 lines
13 KiB
C++

/* ******************************************************************************
*
* This program and the accompanying materials are made available under the
* terms of the Apache License, Version 2.0 which is available at
* https://www.apache.org/licenses/LICENSE-2.0.
*
* See the NOTICE file distributed with this work for additional
* information regarding copyright ownership.
* 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.
*
* SPDX-License-Identifier: Apache-2.0
******************************************************************************/
#ifndef LIBND4J_DIRECTTADTRIE_H
#define LIBND4J_DIRECTTADTRIE_H
#include <array/TadPack.h>
#include <system/common.h>
#include <array>
#include <atomic>
#include <memory>
#include <unordered_set>
#include "./generic/StripedLocks.h"
#include <vector>
#include "array/TadCalculator.h"
// Add include for MSVC compiler intrinsics
#ifdef _MSC_VER
#include <intrin.h>
#endif
namespace sd {
#ifndef __JAVACPP_HACK__
/**
* Stores cached metadata about a TadPack for fast comparison without recomputation.
* Matches only on shape, rank, and dataType (not strides or order).
*/
struct TadPackSignature {
LongType* shape = nullptr;
int rank = 0;
DataType dataType = DataType::FLOAT32;
~TadPackSignature() {
if (shape) delete[] shape;
}
// Store signature from shapeInfo (FIXED: No longer stores strides/order)
void store(LongType* shapeInfo) {
if (!shapeInfo) return;
rank = shape::rank(shapeInfo);
dataType = ArrayOptions::dataType(shapeInfo);
// Allocate and copy shape only (strides removed)
if (shape) delete[] shape;
shape = new LongType[rank];
LongType* srcShape = shape::shapeOf(shapeInfo);
for (int i = 0; i < rank; i++) {
shape[i] = srcShape[i];
}
}
// Compare with another shapeInfo (FIXED: No longer compares strides/order)
bool matches(LongType* shapeInfo) const {
if (!shapeInfo || !shape) return false;
int otherRank = shape::rank(shapeInfo);
if (rank != otherRank) return false;
if (dataType != ArrayOptions::dataType(shapeInfo)) return false;
LongType* otherShape = shape::shapeOf(shapeInfo);
for (int i = 0; i < rank; i++) {
if (shape[i] != otherShape[i]) return false;
}
return true;
}
};
class SD_LIB_EXPORT TadTrieNode {
private:
std::vector<std::unique_ptr<TadTrieNode>> _children;
LongType _value;
int _level;
bool _isDimension;
std::shared_ptr<TadPack> _tadPack; // Use shared_ptr to prevent deletion while in use
int _shapeRank; // Store the rank of the original shape for verification
size_t _nodeHash; // Additional hash for quick comparison
TadPackSignature* _packSignature; // Cached signature for fast comparison
public:
TadTrieNode(LongType value = 0, int level = 0, bool isDimension = true, int shapeRank = 0)
: _value(value), _level(level), _isDimension(isDimension), _tadPack(nullptr),
_shapeRank(shapeRank), _nodeHash(0), _packSignature(nullptr) {}
// Delete copy operations to prevent issues with unique_ptr
TadTrieNode(const TadTrieNode&) = delete;
TadTrieNode& operator=(const TadTrieNode&) = delete;
// Enable move operations
TadTrieNode(TadTrieNode&&) = default;
TadTrieNode& operator=(TadTrieNode&&) = default;
~TadTrieNode() {
if (_packSignature) {
delete _packSignature;
_packSignature = nullptr;
}
// _tadPack is now shared_ptr - it cleans up automatically via RAII
// No manual delete needed
}
// Improved child finding with hash-based optimization
TadTrieNode* findOrCreateChild(LongType value, int level, bool isDimension, int shapeRank = 0) {
// First check using hash-based comparison if available
size_t childHash = computeChildHash(value, isDimension, shapeRank);
for (auto& child : _children) {
if (child->_nodeHash == childHash) {
// Fast path: hash match, do detailed comparison
if (child->value() == value &&
child->isDimension() == isDimension &&
child->shapeRank() == shapeRank) {
return child.get();
}
}
}
// Not found, create new child
// NOTE: Removed #ifndef __JAVACPP_HACK__ guard to fix TAD cache memory leak
// The guard was preventing proper ownership chain when JavaCPP is used (production mode)
// Without this, child nodes were never added to _children vector, preventing automatic
// cleanup when parent nodes are destroyed, causing 100% TAD pack leak rate
auto newNode = std::make_unique<TadTrieNode>(value, level, isDimension, shapeRank);
newNode->_nodeHash = childHash;
auto* ptr = newNode.get();
_children.push_back(std::move(newNode));
return ptr;
}
// Compute hash for faster child comparison
size_t computeChildHash(LongType value, bool isDimension, int shapeRank) const {
size_t hash = 17;
hash = hash * 31 + static_cast<size_t>(value);
hash = hash * 13 + (isDimension ? 1 : 0);
hash = hash * 7 + static_cast<size_t>(shapeRank);
return hash;
}
// Standard getters
const std::vector<std::unique_ptr<TadTrieNode>>& children() const { return _children; }
LongType value() const { return _value; }
int level() const { return _level; }
bool isDimension() const { return _isDimension; }
int shapeRank() const { return _shapeRank; }
size_t nodeHash() const { return _nodeHash; }
const TadPackSignature* packSignature() const { return _packSignature; }
// Enhanced TadPack setter with signature caching
void setPack(std::shared_ptr<TadPack> pack) {
// Thread-safe assignment using atomic operations
std::atomic_store(&_tadPack, pack);
// Cache the signature for future fast comparisons
if (pack && pack->primaryShapeInfo() && !_packSignature) {
_packSignature = new TadPackSignature();
_packSignature->store(pack->primaryShapeInfo());
}
}
std::shared_ptr<TadPack> pack() const {
return std::atomic_load(&_tadPack);
}
};
class SD_LIB_EXPORT DirectTadTrie {
private:
static const size_t NUM_STRIPES = 128; // Increased from 32 to reduce collision chance
std::array<std::unique_ptr<TadTrieNode>, NUM_STRIPES> _roots;
mutable std::array<MUTEX_TYPE, NUM_STRIPES> _mutexes = {};
std::array<std::atomic<int>, NUM_STRIPES> _stripeCounts = {};
// Shutdown guard to prevent crash during JVM shutdown
// When set to true, clear() will skip trie traversal to avoid SIGSEGV
std::atomic<bool> _shutdownInProgress{false};
// Cache statistics tracking
mutable std::atomic<LongType> _current_entries{0};
mutable std::atomic<LongType> _current_bytes{0};
mutable std::atomic<LongType> _peak_entries{0};
mutable std::atomic<LongType> _peak_bytes{0};
// Internal helper to recursively count entries and bytes in a subtrie
void countEntriesAndBytes(const TadTrieNode* node, LongType& entries, LongType& bytes) const;
public:
// Constructor
DirectTadTrie() {
// NOTE: Removed #ifndef __JAVACPP_HACK__ guard to fix TAD cache memory leak
// Without proper initialization, roots remain nullptr causing crashes
// or preventing proper cache management
for (size_t i = 0; i < NUM_STRIPES; i++) {
_roots[i] = std::make_unique<TadTrieNode>(0, 0, false);
// Make sure mutexes are properly initialized
new (&_mutexes[i]) MUTEX_TYPE(); // Explicit initialization
}
}
// Destructor - DO NOT call clear() here!
// During JVM shutdown / static destruction, the order of destruction is undefined.
// Memory allocators and other infrastructure may have already been destroyed,
// causing corrupted pointers in the trie. Traversing the tree in this state
// causes SIGSEGV crashes (e.g., in deleteTadPacksRecursive).
//
// The OS will reclaim all memory when the process exits anyway, so explicit
// cleanup during shutdown is unnecessary and dangerous.
//
// For explicit cleanup during runtime (e.g., testing), use clear() directly.
~DirectTadTrie() = default;
// Delete copy constructor and assignment
DirectTadTrie(const DirectTadTrie&) = delete;
DirectTadTrie& operator=(const DirectTadTrie&) = delete;
// Delete move operations
DirectTadTrie(DirectTadTrie&&) = delete;
DirectTadTrie& operator=(DirectTadTrie&&) = delete;
std::shared_ptr<TadPack> enhancedSearch(const std::vector<LongType>& dimensions, LongType* originalShape, size_t stripeIdx);
bool exists(const std::vector<LongType>& dimensions, LongType* originalShape);
// Enhanced stride-aware hash computation
size_t computeStrideAwareHash(const std::vector<LongType>& dimensions, LongType* originalShape) ;
// Enhanced getOrCreate with improved thread safety
std::shared_ptr<TadPack> getOrCreate(std::vector<LongType>& dimensions, LongType* originalShape);
// Original methods preserved
size_t computeStripeIndex(const std::vector<LongType>& dimensions, LongType* originalShape) const {
size_t hash = 17; // Prime number starting point
// Add dimension-specific hash contribution with position-dependence
for (size_t i = 0; i < dimensions.size(); i++) {
hash = hash * 31 + static_cast<size_t>(dimensions[i]) * (i + 1);
}
int rank = shape::rank(originalShape);
hash = hash * 13 + rank * 19;
// Add shape signature based on shape dimensions with position-dependence
LongType* shapeInfo = shape::shapeOf(originalShape);
for (int i = 0; i < rank; i++) {
hash = hash * 17 + static_cast<size_t>(shapeInfo[i]) * (11 + i);
}
// Add total element count to distinguish differently sized arrays
hash = hash * 41 + shape::length(originalShape);
return hash % NUM_STRIPES;
}
bool exists(const std::vector<LongType>& dimensions, LongType* originalShape) const;
// Original helper methods preserved
std::shared_ptr<TadPack> search(const std::vector<LongType>& dimensions, int originalShapeRank, size_t stripeIdx) const;
std::vector<LongType> sortDimensions(const std::vector<LongType>& dimensions) const;
const TadTrieNode* findChild(const TadTrieNode* node, LongType value, int level, bool isDimension, int shapeRank) const;
std::shared_ptr<TadPack> insert(std::vector<LongType>& dimensions, LongType* originalShape);
/**
* Clear all cached TAD packs to prevent memory leaks during testing.
* This recreates the root nodes, which will delete all child nodes and their TadPacks.
* NOTE: Will return early without action if setShutdownInProgress(true) was called.
*/
void clear();
/**
* Mark that shutdown is in progress.
* When true, clear() will skip tree traversal to avoid SIGSEGV from corrupted memory
* during JVM/static destruction.
* @param inProgress true to mark shutdown in progress, false otherwise
*/
void setShutdownInProgress(bool inProgress) {
_shutdownInProgress.store(inProgress, std::memory_order_release);
}
/**
* Check if shutdown is in progress.
* @return true if shutdown is marked as in progress
*/
bool isShutdownInProgress() const {
return _shutdownInProgress.load(std::memory_order_acquire);
}
/**
* Get the total number of cached TAD pack entries.
*
* @return Total number of cached TAD packs across all stripes
*/
LongType getCachedEntries() const;
/**
* Get the total memory used by cached TAD packs in bytes.
* This includes both shape_info and offset buffer sizes.
*
* @return Total memory used in bytes
*/
LongType getCachedBytes() const;
/**
* Get the peak number of TAD pack entries that were cached simultaneously.
*
* @return Peak number of cached TAD packs
*/
LongType getPeakCachedEntries() const;
/**
* Get the peak memory usage by cached TAD packs in bytes.
*
* @return Peak memory usage in bytes
*/
LongType getPeakCachedBytes() const;
/**
* Generate a human-readable string representation of the trie structure.
* Shows the hierarchy of nodes and cached TAD packs for debugging.
*
* @param maxDepth Maximum depth to traverse (default: 10, -1 for unlimited)
* @param maxEntries Maximum number of entries to show (default: 100, -1 for unlimited)
* @return String representation of the trie
*/
std::string toString(int maxDepth = 10, int maxEntries = 100) const;
/**
* Get all TadPack pointers currently in the cache.
* This is used by lifecycle tracking to distinguish cached entries from real leaks.
*
* @param out_pointers Set to fill with pointers to all cached TadPack objects
*/
void getCachedPointers(std::unordered_set<void*>& out_pointers) const;
private:
// Internal helper to build string representation recursively
void buildStringRepresentation(const TadTrieNode* node, std::stringstream& ss,
const std::string& indent, int currentDepth,
int maxDepth, int& entriesShown, int maxEntries) const;
// Internal helper to collect TadPack pointers recursively
void collectCachedPointers(const TadTrieNode* node, std::unordered_set<void*>& out_pointers) const;
};
} // namespace sd
#endif
#endif //LIBND4J_DIRECTTADTRIE_H