#include "VirtualNodePoolBuilder.h" #include #include "../../inc/tbb/parallel_sort.h" #include "../../core/Util/BoolArray.h" #include std::string VirtualNodePoolBuilder::GetFullFileName(const std::string& filename) const { return filename + ".v.pool"; } size_t VirtualNodePoolBuilder::GetFullNodeSize(const BaseTree* tree, const unsigned8& level, const unsigned8& pointerSize) const { // 1 byte for childmask // 1 byte for "virtual mask" (indicating which nodes are virtual) // pointerSize bytes for pointer to the first child // + Additional node info if (level > tree->GetMaxLevel()) return 0; return 1 + 1 + pointerSize + tree->GetAdditionalBytesPerNode(level); } size_t VirtualNodePoolBuilder::GetVirtualNodeSize(const BaseTree* tree, const unsigned8& level, const unsigned8& pointerSize) const { if (level == 0) return 0; return pointerSize; } size_t VirtualNodePoolBuilder::GetFullNodeSize(const BaseTree* tree, const unsigned8& level, const std::vector& pointerSizesPerLevel) const { if (level > tree->GetMaxLevel()) return 0; return GetFullNodeSize(tree, level, pointerSizesPerLevel[level]); } size_t VirtualNodePoolBuilder::GetVirtualNodeSize(const BaseTree* tree, const unsigned8& level, const std::vector& pointerSizesPerLevel) const { // pointerSize bytes for pointer to the first child return GetVirtualNodeSize(tree, level, pointerSizesPerLevel[level - 1]); } size_t VirtualNodePoolBuilder::GetNormalNodeSize(const BaseTree* tree, const unsigned32& nodeId, const std::vector& pointerSizesPerLevel, std::vector& additionalPointerInfoSizesPerLevel, const bool& includingAdditionalPointerInfo) const { // 1 byte for childmask // Additional node info // pointerSize bytes for each pointer needed. const Node* node = tree->GetNode(nodeId); unsigned8 level = node->GetLevel(); return 1 + tree->GetAdditionalBytesPerNode(level) + node->GetChildCount() * pointerSizesPerLevel[level] + (includingAdditionalPointerInfo ? (additionalPointerInfoSizesPerLevel[level] * node->GetChildCount()) : 0); } std::vector VirtualNodePoolBuilder::CalculatePointerSizesPerLevel(const BaseTree* tree, const std::vector& parentsPerNode, const std::vector& useVirtualNodes) const { unsigned8 depth = tree->GetMaxLevel(); // Calculate some counts per level (needed to calculate the size of each level) std::vector virtualNodesPerLevel = CalculateVirtualNodesPerLevel(tree, parentsPerNode); std::vector fullNodesPerLevel = CalculateFullNodesPerLevel(tree); std::vector pointersToLevel = CalculatePointersToPerLevel(tree); std::vector additionalBytesPerPointer = tree->GetAdditionalBytesPerPointer(); // Now bottom-up calculate the pointer sizees required to point to each level std::vector res(depth + 1, 0); for (unsigned8 level = depth; level > 0; level--) { // Keep increasing the pointersize until we can point to all nodes within a level bool fits = false; while (!fits) { res[level - 1]++; size_t requiredSize = CalculateSizeOfLevel(tree, level, virtualNodesPerLevel[level], fullNodesPerLevel[level], pointersToLevel[level], level == depth ? 0 : pointersToLevel[level + 1], level == 0 ? 0 : res[level - 1], res[level], level == 0 ? 0 : additionalBytesPerPointer[level - 1], additionalBytesPerPointer[level], useVirtualNodes[level], level == 0 ? 0 : useVirtualNodes[level - 1]); size_t availableSize = BitHelper::Exp2(res[level - 1] * 8); // Available size is how much bytes we can reach with a pointer fits = requiredSize < availableSize; } } return res; } std::vector VirtualNodePoolBuilder::CalculateVirtualNodesPerLevel(const BaseTree* tree, const std::vector& parentsPerNode) const { unsigned8 depth = tree->GetMaxLevel(); unsigned32 nodeCount = (unsigned32)tree->GetNodeCount(); std::vector virtualNodesPerLevel(depth + 1); for (unsigned32 i = 0; i < nodeCount; i++) { const Node* node = tree->GetNode(i); unsigned8 level = node->GetLevel(); if (parentsPerNode[i] > 1) virtualNodesPerLevel[level] += parentsPerNode[i] - 1; } return virtualNodesPerLevel; } std::vector VirtualNodePoolBuilder::CalculateFullNodesPerLevel(const BaseTree* tree) const { // Every node appears exactly once in full return tree->GetNodesPerLevel(); } std::vector VirtualNodePoolBuilder::CalculatePointersToPerLevel(const BaseTree* tree) const { unsigned8 depth = tree->GetMaxLevel(); unsigned32 nodeCount = (unsigned32)tree->GetNodeCount(); std::vector pointersToPerLevel(depth + 1); for (unsigned32 i = 0; i < nodeCount; i++) { const Node* node = tree->GetNode(i); unsigned8 level = node->GetLevel(); if (level < depth) pointersToPerLevel[level + 1] += node->GetChildCount(); } return pointersToPerLevel; } size_t VirtualNodePoolBuilder::CalculateSizeOfLevel(const BaseTree* tree, const unsigned8& level, const size_t& virtualNodesThisLevel, const size_t& fullNodesThisLevel, const size_t& pointersToThisLevel, const size_t& pointersFromThisLevel, const unsigned8& pointerSizeToThisLevel, const unsigned8& pointerSizeFromThisLevel, const unsigned8& additionalBytesPointersToThisLevel, const unsigned8& additionalBytesPointersFromThisLevel, const bool& useVirtualNodesThisLevel, const bool& useVirtualNodesPreviousLevel) const { size_t requiredSize = 0; // Calculate size of virtual nodes placed in this level by the previous level if (useVirtualNodesPreviousLevel) requiredSize += virtualNodesThisLevel * GetVirtualNodeSize(tree, level, pointerSizeToThisLevel) + pointersToThisLevel * additionalBytesPointersToThisLevel; // Calculate the size of full nodes (or normal nodes) occupying this level if (useVirtualNodesThisLevel) requiredSize += fullNodesThisLevel * GetFullNodeSize(tree, level, pointerSizeFromThisLevel); else requiredSize += (1 + tree->GetAdditionalBytesPerNode(level)) * fullNodesThisLevel + pointersFromThisLevel * pointerSizeFromThisLevel + pointersFromThisLevel * additionalBytesPointersFromThisLevel; return requiredSize; } std::vector VirtualNodePoolBuilder::CalculateSizePerLevel(const BaseTree* tree, const std::vector pointerSizesPerLevel, const std::vector& parentsPerNode, const std::vector& useVirtualNodes) const { // Calculate some statistics needed to find the size of each level in memory unsigned8 depth = tree->GetMaxLevel(); std::vector virtualNodesPerLevel = CalculateVirtualNodesPerLevel(tree, parentsPerNode); std::vector fullNodesPerLevel = CalculateFullNodesPerLevel(tree); std::vector pointersToLevel = CalculatePointersToPerLevel(tree); std::vector additionalBytesPerPointer = tree->GetAdditionalBytesPerPointer(); // Calculate the actual size per level std::vector sizePerLevel(depth + 1); for (unsigned8 level = 0; level <= depth; level++) sizePerLevel[level] = CalculateSizeOfLevel(tree, level, virtualNodesPerLevel[level], fullNodesPerLevel[level], pointersToLevel[level], level == depth ? 0 : pointersToLevel[level + 1], level == 0 ? 0 : pointerSizesPerLevel[level - 1], pointerSizesPerLevel[level], level == 0 ? 0 : additionalBytesPerPointer[level - 1], additionalBytesPerPointer[level], useVirtualNodes[level], level == 0 ? 0 : useVirtualNodes[level - 1]); return sizePerLevel; } std::vector VirtualNodePoolBuilder::CalculateApproximateSizePerLevelVirtualNodes(const BaseTree* tree, const std::vector& parentsPerNode) const { // Calculate some statistics needed to find the size of each level in memory unsigned8 depth = tree->GetMaxLevel(); std::vector virtualNodesPerLevel = CalculateVirtualNodesPerLevel(tree, parentsPerNode); std::vector fullNodesPerLevel = CalculateFullNodesPerLevel(tree); std::vector pointersToLevel = CalculatePointersToPerLevel(tree); std::vector pointerSizesPerLevel(depth + 1, 4); // Assume 4 bytes pointers per level std::vector additionalBytesPerPointer = tree->GetAdditionalBytesPerPointer(); // Calculate the actual size per level std::vector sizePerLevel(depth + 1); for (unsigned8 level = 0; level <= depth; level++) sizePerLevel[level] = fullNodesPerLevel[level] * GetFullNodeSize(tree, level, pointerSizesPerLevel) + // Full nodes size (level == depth ? 0 : (virtualNodesPerLevel[level + 1] * GetVirtualNodeSize(tree, level + 1, pointerSizesPerLevel))) + // Virtual nodes size (level == depth ? 0 : (pointersToLevel[level + 1] * additionalBytesPerPointer[level])); // additional pointer bytes size return sizePerLevel; } std::vector VirtualNodePoolBuilder::CalculateApproximateSizePerLevelStandardNodes(const BaseTree* tree) const { unsigned8 depth = tree->GetMaxLevel(); unsigned32 nodeCount = (unsigned32)tree->GetNodeCount(); std::vector pointerSizesPerLevel(depth + 1, 4); // Assume 4 bytes pointers per level std::vector additionalBytesPerPointer = tree->GetAdditionalBytesPerPointer(); std::vector sizePerLevel(depth + 1); for (unsigned32 i = 0; i < nodeCount; i++) { const Node* node = tree->GetNode(i); unsigned8 level = node->GetLevel(); sizePerLevel[level] += GetNormalNodeSize(tree, i, pointerSizesPerLevel, additionalBytesPerPointer, true);; } return sizePerLevel; } std::vector VirtualNodePoolBuilder::DecideVirtualPointersPerLevel(const BaseTree* tree, const std::vector& parentsPerNode) const { unsigned8 depth = tree->GetMaxLevel(); std::vector sizePerStandardLevel = CalculateApproximateSizePerLevelStandardNodes(tree); std::vector sizePerVirtualNodesLevel = CalculateApproximateSizePerLevelVirtualNodes(tree, parentsPerNode); std::vector useVirtualNodes(depth + 1); for (unsigned8 level = 0; level <= depth; level++) useVirtualNodes[level] = sizePerVirtualNodesLevel[level] < sizePerStandardLevel[level]; //useVirtualNodes = std::vector(depth + 1, false); //useVirtualNodes[0] = true; //useVirtualNodes[1] = true; //useVirtualNodes[2] = true; return useVirtualNodes; } size_t VirtualNodePoolBuilder::CalculatePoolInfoSize(const BaseTree* tree) { unsigned8 depth = tree->GetMaxLevel(); // Each tree contains at least the level offsets (4 bytes per level) and pointer sizes per level (1 byte per level) // 1 byte per level for sizes of additional information per level // 4 bytes are used to indicate which levels use virtual nodes size_t poolInfoSize = (depth + 1) * 5 + 4; if (HasAdditionalBytesPerNode(tree)) poolInfoSize += depth + 1; if (HasAdditionalBytesPerPointer(tree)) poolInfoSize += depth + 1; // Additional pool info from the tree poolInfoSize += tree->GetAdditionalTreeInfoSize(); return poolInfoSize; } size_t VirtualNodePoolBuilder::GetPoolSize(const BaseTree* tree) { // Calculate the pool info size size_t minSize = CalculatePoolInfoSize(tree); // Calculate the main pool size std::vector parentsPerNode = tree->GetParentCounts(); std::vector useVirtualNodes = DecideVirtualPointersPerLevel(tree, parentsPerNode); std::vector pointerSizesPerLevel = CalculatePointerSizesPerLevel(tree, parentsPerNode, useVirtualNodes); std::vector sizesPerLevel = CalculateSizePerLevel(tree, pointerSizesPerLevel, parentsPerNode, useVirtualNodes); minSize += std::accumulate(sizesPerLevel.begin(), sizesPerLevel.end(), size_t(0)); std::vector virtualNodesPerLevel = CalculateVirtualNodesPerLevel(tree, parentsPerNode); std::vector fullNodesPerLevel = CalculateFullNodesPerLevel(tree); size_t virtualNodesSum = 0; size_t fullNodesSum = 0; size_t normalNodesSum = 0; for (unsigned8 level = 0; level < tree->GetMaxLevel(); level++) { if (useVirtualNodes[level]) fullNodesSum += fullNodesPerLevel[level]; else normalNodesSum += fullNodesPerLevel[level]; if (level > 0 && useVirtualNodes[level - 1]) virtualNodesSum += virtualNodesPerLevel[level - 1]; } printf("Virtual nodes: %llu, Complete nodes: %llu, Normal Nodes: %llu, Percentage virtual: %f\n", (unsigned64)virtualNodesSum, (unsigned64)fullNodesSum, (unsigned64)normalNodesSum, (double(virtualNodesSum) / double(virtualNodesSum + fullNodesSum)) * 100.0); return minSize; } //************************************ // Insert all nodes into final node pool and updates pointers //************************************ bool VirtualNodePoolBuilder::BuildPool(const BaseTree* tree, std::vector& pool) { if (tree == NULL) return false; mIsBuilding = true; unsigned32 nodeCount = (unsigned32)tree->GetNodeCount(); unsigned8 depth = tree->GetMaxLevel(); // Initialize the pool pool = std::vector(GetPoolSize(tree)); // Acquire some information about the pool std::vector parentsPerNode = tree->GetParentCounts(); std::vector useVirtualNodes = DecideVirtualPointersPerLevel(tree, parentsPerNode); std::vector pointerSizesPerLevel = CalculatePointerSizesPerLevel(tree, parentsPerNode, useVirtualNodes); std::vector sizePerLevel = CalculateSizePerLevel(tree, pointerSizesPerLevel, parentsPerNode, useVirtualNodes); std::vector additionalBytesPerPointer = tree->GetAdditionalBytesPerPointer(); std::vector additionalBytesPerNode = tree->GetAdditionalBytesPerNode(); std::vector nodePointers(nodeCount); // Calculate the level offsets std::vector levelOffsets(depth + 1); size_t curIndex = CalculatePoolInfoSize(tree); for (unsigned8 level = 0; level <= depth; level++) { levelOffsets[level] = curIndex; curIndex += sizePerLevel[level]; } // Calculate the node pointers for nodes in non-switch levels not using virtual nodes bool switchlevel = true; for (unsigned8 level = 0; level <= depth; level++) { if (!switchlevel) { curIndex = levelOffsets[level]; for (unsigned32 i = 0; i < nodeCount; i++) { const Node* node = tree->GetNode(i); if (node->GetLevel() == level) { nodePointers[i] = curIndex; curIndex += GetNormalNodeSize(tree, i, pointerSizesPerLevel, additionalBytesPerPointer, true); } } } if (useVirtualNodes[level]) switchlevel = true; else if (switchlevel == true) switchlevel = false; } curIndex = 0; // Write the level offsets for (unsigned8 level = 0; level <= depth; level++) BitHelper::SplitInBytesAndMove(levelOffsets[level], pool, level * 4, 4); curIndex += 4 * (depth + 1); // Write the pointer sizes per level for (unsigned8 level = 0; level <= depth; level++) pool[curIndex++] = pointerSizesPerLevel[level]; // Write 4 bytes indicating which levels use virtual nodes unsigned32 levelsUsingVirtualNodesMask = 0; for (unsigned8 level = 0; level <= depth; level++) BitHelper::SetLS(levelsUsingVirtualNodesMask, level, useVirtualNodes[level]); BitHelper::SplitInBytesAndMove(levelsUsingVirtualNodesMask, pool, curIndex); curIndex += 4; // Write additional bytes per node if (HasAdditionalBytesPerNode(tree)) { for (unsigned8 level = 0; level <= depth; level++) pool[curIndex++] = additionalBytesPerNode[level]; } // Write additional bytes per pointer if (HasAdditionalBytesPerPointer(tree)) { for (unsigned8 level = 0; level <= depth; level++) pool[curIndex++] = additionalBytesPerPointer[level]; } // Leave some space for the additional pool info. // As the actual node pointers are not yet known, we write them later size_t additionalTreeInfoStart = curIndex; curIndex += tree->GetAdditionalTreeInfoSize(); // Find all roots (to make sure we write all reachable nodes from any root). std::vector nextLevelNodes; std::vector thisLevelNodes; for (unsigned32 i = 0; i < nodeCount; i++) if (tree->GetNode(i)->GetLevel() == 0) thisLevelNodes.push_back(NodeToWrite(i, 0, useVirtualNodes[0] ? FULL : NORMAL, 0, 0)); size_t nextLevelIndex; BoolArray writtenNodes(nodeCount); // Write the full node pool for (unsigned8 level = 0; level <= depth; level++) { assert(curIndex = levelOffsets[level]); nextLevelNodes.clear(); nextLevelIndex = 0; unsigned8 additionalNodeBytes = additionalBytesPerNode[level]; unsigned8 additionalBytesForPointersToThisLevel = level > 0 ? additionalBytesPerPointer[level - 1] : 0; unsigned8 additionalBytesForPointersFromThisLevel = additionalBytesPerPointer[level]; size_t childFullNodeSize = GetFullNodeSize(tree, level + 1, pointerSizesPerLevel); size_t childVirtualNodeSize = GetVirtualNodeSize(tree, level + 1, pointerSizesPerLevel); for (NodeToWrite nodeInfo : thisLevelNodes) { const Node* node = nodeInfo.GetNode(tree); unsigned32 nodeId = nodeInfo.nodeId; assert(level == node->GetLevel()); if (nodeInfo.type == VIRTUAL) { // Write a virtual node size_t virtualNodeSize = GetVirtualNodeSize(tree, level, pointerSizesPerLevel); BitHelper::SplitInBytesAndMove(nodePointers[nodeId] - levelOffsets[level], pool, curIndex, virtualNodeSize); curIndex += virtualNodeSize; } else if (nodeInfo.type == FULL) { // Write a full node assert(useVirtualNodes[level]); nodePointers[nodeId] = curIndex; WriteFullNode(tree, nodeId, (unsigned32)nextLevelIndex, pointerSizesPerLevel[level], writtenNodes, additionalNodeBytes, pool, curIndex); // Tell the next level which nodes should be written and in what order unsigned8 vMask = pool[curIndex + 1 + additionalNodeBytes]; size_t nextLevelNodesOffset = nextLevelNodes.size(); for (ChildIndex c = 0; c < 8; c++) if (node->HasChild(c)) { NodeType type; if (!useVirtualNodes[level + 1]) type = BitHelper::GetLS(vMask, c) ? VIRTUAL : NORMAL; else type = BitHelper::GetLS(vMask, c) ? VIRTUAL : FULL; nextLevelNodes.push_back(NodeToWrite(node->GetChildIndex(c), level + 1, type, nodeId, c)); } // Calculate the size of those nodes in the next layer. for (auto c = nextLevelNodes.begin() + nextLevelNodesOffset; c != nextLevelNodes.end(); c++) { switch (c->type) { case NORMAL: nextLevelIndex += GetNormalNodeSize(tree, c->nodeId, pointerSizesPerLevel, additionalBytesPerPointer, true); break; case FULL: nextLevelIndex += childFullNodeSize; break; case VIRTUAL: nextLevelIndex += childVirtualNodeSize; break; } } nextLevelIndex += node->GetChildCount() * additionalBytesForPointersFromThisLevel; curIndex += GetFullNodeSize(tree, level, pointerSizesPerLevel); } else if (nodeInfo.type == NORMAL) { assert(nodePointers[nodeId] == 0 || nodePointers[nodeId] == curIndex); nodePointers[nodeId] = curIndex; WriteNormalNode(tree, nodeId, additionalNodeBytes, pointerSizesPerLevel[level], nodePointers, level == depth ? 0 : levelOffsets[level + 1], pool, curIndex); curIndex += GetNormalNodeSize(tree, nodeId, pointerSizesPerLevel, additionalBytesPerPointer, false); // Write additional bytes per pointer for (ChildIndex c = 0; c < 8; c++) { if (node->HasChild(c)) { WriteAdditionalPointerInfo(tree, nodeId, c, additionalBytesForPointersFromThisLevel, pool, curIndex); curIndex += additionalBytesForPointersFromThisLevel; } } } if (level > 0 && additionalBytesForPointersToThisLevel != 0 && useVirtualNodes[level - 1]) { WriteAdditionalPointerInfo(tree, nodeInfo.parentId, nodeInfo.childIndexOfParent, additionalBytesForPointersToThisLevel, pool, curIndex); curIndex += additionalBytesForPointersToThisLevel; } } if (useVirtualNodes[level]) thisLevelNodes = nextLevelNodes; else { thisLevelNodes.clear(); for (unsigned32 i = 0; i < nodeCount; i++) { const Node* node = tree->GetNode(i); if (node->GetLevel() == level + 1) thisLevelNodes.push_back(NodeToWrite(i, level + 1, NORMAL, 0, 0)); } } } std::vector additionalTreeInfo = tree->GetAdditionalTreeInfo(nodePointers); std::move(additionalTreeInfo.begin(), additionalTreeInfo.end(), pool.begin() + additionalTreeInfoStart); mIsBuilding = false; return true; } void VirtualNodePoolBuilder::WriteFullNode(const BaseTree* tree, const unsigned32& nodeId, const unsigned32& childPointer, const unsigned8& childPointerSize, BoolArray& writtenNodes, const unsigned8& additionalNodeBytes, std::vector& pool, const size_t& offset) const { size_t curIndex = offset; const Node* node = tree->GetNode(nodeId); pool[curIndex++] = node->GetChildmask().mask; // Write additional node info (if any) if (additionalNodeBytes != 0) { auto nodeBytes = tree->GetAdditionalNodeBytes(node); std::move(nodeBytes.begin(), nodeBytes.end(), pool.begin() + curIndex); assert(nodeBytes.size() == additionalNodeBytes); curIndex += additionalNodeBytes; } // Build the "virtual mask" indicating which nodes have already been written in the next level and are virtual in this level pool[curIndex++] = GetVMask(tree, nodeId, writtenNodes); // Write the pointer to the first child BitHelper::SplitInBytesAndMove(childPointer, pool, curIndex, childPointerSize); curIndex += 4; } void VirtualNodePoolBuilder::WriteNormalNode(const BaseTree* tree, const unsigned32& nodeId, const unsigned8& additionalNodeBytes, const unsigned8& pointerSize, const std::vector& nodePointers, const size_t& nextLevelOffset, std::vector& pool, const size_t& offset) const { size_t curIndex = offset; const Node* node = tree->GetNode(nodeId); pool[curIndex++] = node->GetChildmask().mask; // Write additional node info (if any) if (additionalNodeBytes != 0) { auto nodeBytes = tree->GetAdditionalNodeBytes(node); std::move(nodeBytes.begin(), nodeBytes.end(), pool.begin() + curIndex); assert(nodeBytes.size() == additionalNodeBytes); curIndex += additionalNodeBytes; } // Write the child pointers unsigned32* children = node->GetChildren(); for (ChildIndex c = 0; c < node->GetChildCount(); c++) { unsigned32 child = children[c]; size_t pointer = nodePointers[child] - nextLevelOffset; BitHelper::SplitInBytesAndMove(pointer, pool, curIndex, pointerSize); curIndex += pointerSize; } } void VirtualNodePoolBuilder::WriteAdditionalPointerInfo(const BaseTree* tree, const unsigned32& nodeId, const ChildIndex& childId, const unsigned8& additionalPointerBytes, std::vector& pool, const size_t& offset) const { auto pointerInfo = tree->GetAdditionalPointerBytes(tree->GetNode(nodeId), childId); std::move(pointerInfo.begin(), pointerInfo.end(), pool.begin() + offset); } unsigned8 VirtualNodePoolBuilder::GetVMask(const BaseTree* tree, const unsigned32& nodeId, BoolArray& writtenNodes) const { const Node* node = tree->GetNode(nodeId); unsigned8 vMask = 0; for (ChildIndex c = 0; c < 8; c++) { if (node->HasChild(c)) { unsigned32 childIndex = node->GetChildIndex(c); // A node is virtual if it has been written before BitHelper::SetLS(vMask, c, writtenNodes[childIndex]); writtenNodes.Set(childIndex, true); } } return vMask; } bool VirtualNodePoolBuilder::VerifyPool(std::vector& pool, const unsigned8& treeDepth) const { // TODO: Do some verification here. return true; }