diff --git a/Documentation/release/dev/ensight-reader-improvements.md b/Documentation/release/dev/ensight-reader-improvements.md new file mode 100644 index 0000000000000000000000000000000000000000..3781d2cb63e710456582d04ebab54ff4090cfafc --- /dev/null +++ b/Documentation/release/dev/ensight-reader-improvements.md @@ -0,0 +1,7 @@ +## EnsightCombinedReader: Performance improvements pass + +Improve performances of the new vtkEnSightGoldCombinedReader in various ways, especially when reading large binary files containing nfaced/tetrahedron cells: +* The tetrahedron cell-building logic now uses a tetrahedron-specific vtkUnstructredGrid::InsertNextCell call +* Tetrahedron cells connectivity data are read as one block rather than cell-by-cell +* Add new "skip" methods to vtkEnSightDataSet which implement dedicated logic to ignore file cell sections. The logic is duplicated a bit from the equivalent reading methods at the benefit of much faster parsing during the RequestInfo pass (to get the parts names) +* Fix an int overflow that could occur while seeking ahead in large files diff --git a/IO/EnSight/core/EnSightDataSet.cxx b/IO/EnSight/core/EnSightDataSet.cxx index efca43a662db100e7485526c2617aa697012e921..86531f02d76c64ee71f4f55a6b20e6ba3eafa580 100644 --- a/IO/EnSight/core/EnSightDataSet.cxx +++ b/IO/EnSight/core/EnSightDataSet.cxx @@ -14,11 +14,8 @@ #include "vtkFloatArray.h" #include "vtkIdTypeArray.h" #include "vtkInformation.h" -#include "vtkInformationVector.h" #include "vtkLogger.h" -#include "vtkMultiProcessController.h" #include "vtkNew.h" -#include "vtkObjectFactory.h" #include "vtkPartitionedDataSet.h" #include "vtkPartitionedDataSetCollection.h" #include "vtkPointData.h" @@ -37,6 +34,7 @@ #include <vtksys/SystemTools.hxx> +#include <algorithm> #include <cstdlib> #include <numeric> #include <regex> @@ -1049,6 +1047,7 @@ bool EnSightDataSet::ReadGeometry(vtkPartitionedDataSetCollection* output, vtkGenericWarningMacro("Part Id " << partId << " could not be found in PartInfoMap"); return false; } + auto& partInfo = it->second; result = this->GeometryFile.ReadNextLine(); // part description line @@ -2501,11 +2500,11 @@ void EnSightDataSet::PassThroughUnstructuredGrid(const GridOptions& vtkNotUsed(o { if (elementType == ElementType::NSided) { - this->ReadNSidedSection(numCellsPerType[static_cast<int>(elementType)], nullptr); + this->SkipNSidedSection(numCellsPerType[static_cast<int>(elementType)]); } else if (elementType == ElementType::NFaced) { - this->ReadNFacedSection(numCellsPerType[static_cast<int>(elementType)], nullptr); + this->SkipNFacedSection(numCellsPerType[static_cast<int>(elementType)]); } else { @@ -2746,57 +2745,178 @@ void EnSightDataSet::ReadNSidedSection(int& numElements, vtkUnstructuredGrid* ou delete[] numNodesPerElement; } +//------------------------------------------------------------------------------ +void EnSightDataSet::SkipNFacedSection(int& numElements) +{ + this->GeometryFile.ReadNumber(&numElements); + + // (optional) Element IDs + if (this->ElementIdsListed) + { + this->GeometryFile.SkipNNumbers<int>(numElements); + } + + // Number of faces per element + std::vector<int> numFacesPerElement(numElements, 0); + this->GeometryFile.ReadArray(numFacesPerElement.data(), numElements); + + vtkIdType totalNumFaces = std::accumulate( + numFacesPerElement.begin(), numFacesPerElement.end(), static_cast<vtkIdType>(0)); + + if (this->GeometryFile.Format == FileType::ASCII) + { + for (vtkIdType i = 0; i < totalNumFaces; ++i) + { + // Skip 2 lines: number of point per face per element, face connectivity + this->GeometryFile.SkipLine(); + this->GeometryFile.SkipLine(); + } + } + else + { + std::vector<int> numNodesPerFacePerElement(totalNumFaces); + this->GeometryFile.ReadArray(numNodesPerFacePerElement.data(), totalNumFaces); + + vtkIdType totalNumNodes = std::accumulate(numNodesPerFacePerElement.begin(), + numNodesPerFacePerElement.end(), static_cast<vtkIdType>(0)); + + this->GeometryFile.SkipNNumbers<int>(totalNumNodes); + } +} + +//------------------------------------------------------------------------------ +void EnSightDataSet::SkipNSidedSection(int& numElements) +{ + this->GeometryFile.ReadNumber(&numElements); + + if (this->ElementIdsListed) + { + this->GeometryFile.SkipNNumbers<int>(numElements); + } + + if (this->GeometryFile.Format == FileType::ASCII) + { + // Skip 2 lines per element: number of nodes, node numbers for this element + for (int elementIdx = 0; elementIdx < numElements; ++elementIdx) + { + this->GeometryFile.SkipLine(); + this->GeometryFile.SkipLine(); + } + } + else + { + std::vector<int> numNodesPerElement(numElements); + this->GeometryFile.ReadArray(numNodesPerElement.data(), numElements); + + vtkIdType totalNumNodes = std::accumulate( + numNodesPerElement.begin(), numNodesPerElement.end(), static_cast<vtkIdType>(0)); + this->GeometryFile.SkipNNumbers<int>(totalNumNodes); + } +} + //------------------------------------------------------------------------------ void EnSightDataSet::ReadNFacedSection(int& numElements, vtkUnstructuredGrid* output) { + vtkLogScopeFunction(TRACE); + + // Number of elements this->GeometryFile.ReadNumber(&numElements); + // (optional) Element IDs if (this->ElementIdsListed) { this->GeometryFile.SkipNNumbers<int>(numElements); } - // read number of faces per nfaced element + // Number of faces per element std::vector<int> numFacesPerElement(numElements, 0); this->GeometryFile.ReadArray(numFacesPerElement.data(), numElements); - // read number of nodes per face of each element - std::vector<std::vector<int>> nodesPerFacePerElement(numElements); - std::vector<int> totalNodesPerElement(numElements, 0); - for (int elem = 0; elem < numElements; elem++) + // Read the whole block in one go + vtkIdType totalNumFaces = std::accumulate( + numFacesPerElement.begin(), numFacesPerElement.end(), static_cast<vtkIdType>(0)); + + std::vector<int> numNodesPerFacePerElement(totalNumFaces); + this->GeometryFile.ReadArray(numNodesPerFacePerElement.data(), totalNumFaces); + + vtkIdType totalNumNodes = std::accumulate( + numNodesPerFacePerElement.begin(), numNodesPerFacePerElement.end(), static_cast<vtkIdType>(0)); + std::vector<int> faceNodesBuffer(totalNumNodes); + + vtkIdType offset = 0; + for (vtkIdType i = 0; i < totalNumFaces; ++i) { - auto& numFaces = numFacesPerElement[elem]; - auto& nodesPerFace = nodesPerFacePerElement[elem]; - nodesPerFace.resize(numFaces); - this->GeometryFile.ReadArray(nodesPerFace.data(), numFaces); - totalNodesPerElement[elem] = std::accumulate(nodesPerFace.begin(), nodesPerFace.end(), 0); + this->GeometryFile.ReadArray( + faceNodesBuffer.data() + offset, numNodesPerFacePerElement[i], true); + offset += numNodesPerFacePerElement[i]; } + // Now build the actual cells auto cellInfo = getVTKCellType(ElementType::NFaced); - for (int elem = 0; elem < numElements; elem++) + + auto numNodesInFaceIt = numNodesPerFacePerElement.begin(); + auto nodeIt = faceNodesBuffer.begin(); + + // Break through all loops if numNodesInFaceIt or nodeIt reach the end of vector + bool endReached = false; + vtkNew<vtkCellArray> faceStream; + + for (int elemIdx = 0; elemIdx < numElements; elemIdx++) { - auto& numNodesAllFaces = totalNodesPerElement[elem]; - auto& numFaces = numFacesPerElement[elem]; - auto arraySize = numNodesAllFaces + numFaces; - std::vector<vtkIdType> nodeIds(arraySize, 0); - int arrayIdx = 0; - std::vector<int> tempIds; - auto& numNodesPerFace = nodesPerFacePerElement[elem]; - for (int face = 0; face < numFaces; face++) + const int numFacesInElement = numFacesPerElement[elemIdx]; + /// @note: we could save that value from the earlier "total" computation. It's not significant + // compared to the read time though + const vtkIdType numNodesInElement = std::accumulate( + numNodesInFaceIt, numNodesInFaceIt + numFacesInElement, static_cast<vtkIdType>(0)); + + std::vector<vtkIdType> uniqueCellIDs; + uniqueCellIDs.reserve(numNodesInElement); + + faceStream->Reset(); + faceStream->AllocateExact(numFacesInElement, numNodesInElement); + + for (int faceIdx = 0; faceIdx < numFacesInElement; ++faceIdx) { - auto& numNodes = numNodesPerFace[face]; - nodeIds[arrayIdx++] = numNodes; - tempIds.clear(); - tempIds.resize(numNodes); - this->GeometryFile.ReadArray(tempIds.data(), numNodes, true); - for (auto& nodeId : tempIds) + const int numNodesInFace = *numNodesInFaceIt; + faceStream->InsertNextCell(numNodesInFace); + + for (int i = 0; i < numNodesInFace; ++i) + { + vtkIdType correctedId = (*nodeIt) - 1; // Ensight node IDs are 1-based + faceStream->InsertCellPoint(correctedId); + + /// @note: We use an unsorted, unique vector instead of a set because: + // 1) This is a per-cell unique point list; we expect it to be relatively small + // 2) It allows us to use the insertNextCell call below which expects a contiguous container + if (std::find(uniqueCellIDs.begin(), uniqueCellIDs.end(), correctedId) == + std::end(uniqueCellIDs)) + { + uniqueCellIDs.push_back(correctedId); + } + + ++nodeIt; + if (nodeIt == faceNodesBuffer.end()) + { + endReached = true; + break; + } + } + ++numNodesInFaceIt; + if (endReached || numNodesInFaceIt == numNodesPerFacePerElement.end()) { - nodeIds[arrayIdx++] = nodeId - 1; + endReached = true; + break; } } + if (output) { - output->InsertNextCell(cellInfo.first, numFaces, nodeIds.data()); + output->InsertNextCell( + cellInfo.first, uniqueCellIDs.size(), uniqueCellIDs.data(), faceStream); + } + if (endReached) + { + break; } } } diff --git a/IO/EnSight/core/EnSightDataSet.h b/IO/EnSight/core/EnSightDataSet.h index 13d1f3220ae7228301294d3431e780fd0be649b1..39e8eb67ffa287e4fd59452a1382bbe170140f56 100644 --- a/IO/EnSight/core/EnSightDataSet.h +++ b/IO/EnSight/core/EnSightDataSet.h @@ -280,6 +280,14 @@ private: void ReadNSidedSection(int& numElements, vtkUnstructuredGrid* output); void ReadNFacedSection(int& numElements, vtkUnstructuredGrid* output); + ///@{ + /** + * Pass through element sections ignoring as much info as possible. + */ + void SkipNSidedSection(int& numElements); + void SkipNFacedSection(int& numElements); + ///@} + void ReadVariableNodes(EnSightFile& file, const std::string& arrayName, int numComponents, vtkPartitionedDataSetCollection* output, vtkDataArraySelection* selection, bool isComplex = false, bool isReal = true); diff --git a/IO/EnSight/core/EnSightFile.cxx b/IO/EnSight/core/EnSightFile.cxx index 42cee83e919b75e2ef7ec1100eace20c116bf8b7..6a4c68eeeead88f3376f741e59615199aa7eff8a 100644 --- a/IO/EnSight/core/EnSightFile.cxx +++ b/IO/EnSight/core/EnSightFile.cxx @@ -542,6 +542,19 @@ std::pair<bool, std::string> EnSightFile::ReadNextLine(int size /* = MAX_LINE_LE return result; } +//------------------------------------------------------------------------------ +void EnSightFile::SkipLine(vtkTypeInt64 size) +{ + if (this->Format == FileType::ASCII) + { + this->Stream->ignore(size, '\n'); + } + else + { + this->MoveReadPosition(this->FortranSkipBytes * 2 + size); + } +} + //------------------------------------------------------------------------------ std::pair<bool, std::string> EnSightFile::ReadLine(int size /* = MAX_LINE_LENGTH*/) { @@ -638,7 +651,7 @@ bool EnSightFile::DetectByteOrder(int* result) } //------------------------------------------------------------------------------ -void EnSightFile::MoveReadPosition(int numBytes) +void EnSightFile::MoveReadPosition(vtkTypeInt64 numBytes) { this->Stream->seekg(numBytes, ios::cur); } diff --git a/IO/EnSight/core/EnSightFile.h b/IO/EnSight/core/EnSightFile.h index c47bef212f81433bdfa83e49ee94bc72513be12a..1ff28867f8455b8c40eb31174cd77b7f354e7bed 100644 --- a/IO/EnSight/core/EnSightFile.h +++ b/IO/EnSight/core/EnSightFile.h @@ -8,6 +8,8 @@ #include "vtksys/FStream.hxx" +#include <cassert> +#include <limits> #include <memory> #include <sstream> #include <string> @@ -64,6 +66,7 @@ struct EnSightFile Endianness ByteOrder = Endianness::Unknown; int TimeSet = -1; int FileSet = -1; + bool InBlockRead = false; EnSightFile(); ~EnSightFile(); @@ -133,6 +136,12 @@ struct EnSightFile */ std::pair<bool, std::string> ReadLine(int size = MAX_LINE_LENGTH); + /** Ignore the the next characters until either the line end delimiter is met or size characters + * have been ignored (if provided). + * For binary formats, ignore the next size characters + padding (but you should probably use + * another method)*/ + void SkipLine(vtkTypeInt64 size = std::numeric_limits<std::streamsize>::max()); + /** * Skip the specified number of non-numeric lines when reading. * WARNING: Should only be used for non-numeric lines, even in ASCII mode! @@ -182,7 +191,7 @@ struct EnSightFile /** * Move the read position ahead n bytes. */ - void MoveReadPosition(int numBytes); + void MoveReadPosition(vtkTypeInt64 numBytes); /** * Get current position of reader in stream. @@ -254,7 +263,7 @@ void EnSightFile::SkipNNumbers(vtkIdType n, int numsPerLine /* = 1 */) // for float, 12 characters total // there's also white space allowed between numbers int size = getNumChars<T>() * numsPerLine + 10 * numsPerLine; - int lineIdx = 0; + vtkIdType lineIdx = 0; while (lineIdx < n) { auto result = this->ReadLine(size); @@ -267,7 +276,7 @@ void EnSightFile::SkipNNumbers(vtkIdType n, int numsPerLine /* = 1 */) } else { - int numBytes = n * static_cast<int>(sizeof(T)) + this->FortranSkipBytes * 2; + vtkTypeInt64 numBytes = n * sizeof(T) + this->FortranSkipBytes * 2; this->MoveReadPosition(numBytes); } } @@ -285,6 +294,7 @@ bool EnSightFile::ReadNumber(T* result, bool padBeginning /* = true*/, bool padE { if (padBeginning) { + assert(!this->InBlockRead); this->MoveReadPosition(this->FortranSkipBytes); } if (!this->Stream->read((char*)result, sizeof(T))) @@ -294,6 +304,7 @@ bool EnSightFile::ReadNumber(T* result, bool padBeginning /* = true*/, bool padE } if (padEnd) { + assert(!this->InBlockRead); this->MoveReadPosition(this->FortranSkipBytes); } if (this->ByteOrder == Endianness::Little)