diff --git a/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.cpp b/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.cpp index 8932aa02b9..ed11d7f0fb 100644 --- a/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.cpp +++ b/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.cpp @@ -266,7 +266,7 @@ std::expected RicScheduleDataGenerator::generateDateSection( c { if ( !well ) continue; - if ( auto welspecs = generateWelspecsForWell( timeline, eclipseCase, *well, date ) ) + if ( auto welspecs = generateWelspecsForWell( timeline, eclipseCase, *well, date, events ) ) { mergeKeyword( keywordBlocks, "WELSPECS", std::move( *welspecs ) ); mergeKeyword( keywordBlocks, @@ -274,10 +274,10 @@ std::expected RicScheduleDataGenerator::generateDateSection( c RimKeywordFactory::compordKeyword( well->completionSettings()->wellNameForExport().toStdString() ) ); } - generateCompletionsForWell( timeline, eclipseCase, *well, date, keywordBlocks ); + generateCompletionsForWell( eclipseCase, *well, date, events, keywordBlocks ); - generateMswForWell( timeline, eclipseCase, *well, date, keywordBlocks, unmergedBlocks, mswWells ); - generateWellControlForWell( timeline, *well, date, keywordBlocks ); + generateMswForWell( eclipseCase, *well, date, events, keywordBlocks, unmergedBlocks, mswWells ); + generateWellControlForWell( *well, events, keywordBlocks ); } // Schedule-level keyword events (not tied to a specific well) @@ -424,15 +424,14 @@ std::expected RicScheduleDataGenerator::generateDateSection( c //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -std::optional RicScheduleDataGenerator::generateWelspecsForWell( const RimWellEventTimeline& timeline, - RimEclipseCase& eclipseCase, - RimWellPath& well, - const QDateTime& date ) +std::optional RicScheduleDataGenerator::generateWelspecsForWell( const RimWellEventTimeline& timeline, + RimEclipseCase& eclipseCase, + RimWellPath& well, + const QDateTime& date, + const std::vector& eventsAtDate ) { - auto events = timeline.getEventsAtDate( date ); - bool hasEvents = false; - for ( auto* event : events ) + for ( auto* event : eventsAtDate ) { if ( ( event->eventType() == RimWellEvent::EventType::PERF || event->eventType() == RimWellEvent::EventType::VALVE || event->eventType() == RimWellEvent::EventType::TUBING || event->eventType() == RimWellEvent::EventType::WELLSPEC ) && @@ -479,16 +478,14 @@ std::optional RicScheduleDataGenerator::generateWelspecsForWel //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -void RicScheduleDataGenerator::generateCompletionsForWell( const RimWellEventTimeline& timeline, - RimEclipseCase& eclipseCase, +void RicScheduleDataGenerator::generateCompletionsForWell( RimEclipseCase& eclipseCase, RimWellPath& well, const QDateTime& date, + const std::vector& eventsAtDate, std::map& keywordBlocks ) { - auto events = timeline.getEventsAtDate( date ); - bool hasPerfEvents = false; - for ( auto* event : events ) + for ( auto* event : eventsAtDate ) { if ( event->eventType() == RimWellEvent::EventType::PERF && event->wellName() == well.name() ) { @@ -511,10 +508,10 @@ void RicScheduleDataGenerator::generateCompletionsForWell( const RimWellEventTim //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -void RicScheduleDataGenerator::generateMswForWell( const RimWellEventTimeline& timeline, - RimEclipseCase& eclipseCase, +void RicScheduleDataGenerator::generateMswForWell( RimEclipseCase& eclipseCase, RimWellPath& wellPath, const QDateTime& date, + const std::vector& eventsAtDate, std::map& keywordBlocks, std::map>& unmergedBlocks, const std::set& mswWells ) @@ -532,10 +529,9 @@ void RicScheduleDataGenerator::generateMswForWell( const RimWellEventTimeline& } // MSW is generated only when there are valve/tubing/perforation events at this exact date. - auto events = timeline.getEventsAtDate( date ); bool hasMswEvents = false; bool hasPerfEvents = false; - for ( auto* event : events ) + for ( auto* event : eventsAtDate ) { if ( event->wellName() != wellPath.name() ) continue; @@ -591,14 +587,11 @@ void RicScheduleDataGenerator::generateMswForWell( const RimWellEventTimeline& //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- -void RicScheduleDataGenerator::generateWellControlForWell( const RimWellEventTimeline& timeline, - const RimWellPath& well, - const QDateTime& date, +void RicScheduleDataGenerator::generateWellControlForWell( const RimWellPath& well, + const std::vector& eventsAtDate, std::map& keywordBlocks ) { - auto events = timeline.getEventsAtDate( date ); - - for ( auto* event : events ) + for ( auto* event : eventsAtDate ) { if ( event->wellName() != well.name() ) continue; diff --git a/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.h b/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.h index b1a1b5df9b..5422d906d8 100644 --- a/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.h +++ b/ApplicationLibCode/Commands/CompletionExportCommands/RicScheduleDataGenerator.h @@ -76,33 +76,35 @@ private: bool dateAsComment = false, bool alignColumns = false ); - static std::optional - generateWelspecsForWell( const RimWellEventTimeline& timeline, RimEclipseCase& eclipseCase, RimWellPath& well, const QDateTime& date ); + static std::optional generateWelspecsForWell( const RimWellEventTimeline& timeline, + RimEclipseCase& eclipseCase, + RimWellPath& well, + const QDateTime& date, + const std::vector& eventsAtDate ); // Generate COMPDAT (and COMPLUMP, when perforations carry a completion number) for a well at a // specific date based on events, merging both into the accumulator. - static void generateCompletionsForWell( const RimWellEventTimeline& timeline, - RimEclipseCase& eclipseCase, + static void generateCompletionsForWell( RimEclipseCase& eclipseCase, RimWellPath& well, const QDateTime& date, + const std::vector& eventsAtDate, std::map& keywordBlocks ); // Generate WELSEGS / COMPSEGS / WSEGVALV / WSEGAICD for a well at a specific date. // WSEGVALV / WSEGAICD are merged into keywordBlocks; WELSEGS / COMPSEGS cannot be merged across // wells and are appended as separate per-well blocks in unmergedBlocks. // All four keywords are emitted only when the well is present in mswWells; otherwise none are. - static void generateMswForWell( const RimWellEventTimeline& timeline, - RimEclipseCase& eclipseCase, + static void generateMswForWell( RimEclipseCase& eclipseCase, RimWellPath& well, const QDateTime& date, + const std::vector& eventsAtDate, std::map& keywordBlocks, std::map>& unmergedBlocks, const std::set& mswWells ); // Generate well control / well keyword event keywords for a well at a specific date, merging into the accumulator - static void generateWellControlForWell( const RimWellEventTimeline& timeline, - const RimWellPath& well, - const QDateTime& date, + static void generateWellControlForWell( const RimWellPath& well, + const std::vector& eventsAtDate, std::map& keywordBlocks ); // Append records of `kw` into the entry for `name`, creating it from `kw` if absent diff --git a/ApplicationLibCode/FileInterface/RifEventKeywordFormatter.cpp b/ApplicationLibCode/FileInterface/RifEventKeywordFormatter.cpp index 62b8041728..3bbf1376c3 100644 --- a/ApplicationLibCode/FileInterface/RifEventKeywordFormatter.cpp +++ b/ApplicationLibCode/FileInterface/RifEventKeywordFormatter.cpp @@ -46,6 +46,16 @@ namespace { +// Constructing an Opm::Parser builds the complete keyword database (several thousand keywords) and +// costs milliseconds. The parser is stateless once constructed, so a single shared instance is used +// for all keyword lookups. Building one per keyword made schedule generation scale linearly in the +// number of events with a very large constant (see issue: GenerateSchedule does not scale). +const Opm::Parser& sharedParser() +{ + static const Opm::Parser parser; + return parser; +} + QString keywordToString( const Opm::DeckKeyword& kw ) { if ( kw.name().empty() ) return {}; @@ -63,8 +73,7 @@ bool RifEventKeywordFormatter::isRecordlessKeyword( const Opm::DeckKeyword& keyw { try { - static const Opm::Parser parser; - const auto& parserKeyword = parser.getKeyword( keyword.name() ); + const auto& parserKeyword = sharedParser().getKeyword( keyword.name() ); return parserKeyword.begin() == parserKeyword.end(); } catch ( const std::exception& ) @@ -85,8 +94,7 @@ std::optional RifEventKeywordFormatter::buildKeyword( const QS try { - Opm::Parser parser; - const Opm::ParserKeyword& parserKw = parser.getKeyword( kwName ); + const Opm::ParserKeyword& parserKw = sharedParser().getKeyword( kwName ); Opm::DeckKeyword kw( parserKw ); const size_t numRecords = static_cast( std::distance( parserKw.begin(), parserKw.end() ) ); diff --git a/ApplicationLibCode/ProjectDataModel/Jobs/RimKeywordFactory.cpp b/ApplicationLibCode/ProjectDataModel/Jobs/RimKeywordFactory.cpp index 8f2748c83e..37bcfceed8 100644 --- a/ApplicationLibCode/ProjectDataModel/Jobs/RimKeywordFactory.cpp +++ b/ApplicationLibCode/ProjectDataModel/Jobs/RimKeywordFactory.cpp @@ -67,6 +67,18 @@ namespace RimKeywordFactory { +namespace +{ +// A single shared parser instance is used for all keyword schema lookups. Constructing an +// Opm::Parser populates the complete keyword database and costs milliseconds, which is far too +// expensive to repeat for every keyword being serialised. +const Opm::Parser& sharedParser() +{ + static const Opm::Parser parser; + return parser; +} +} // namespace + //-------------------------------------------------------------------------------------------------- /// //-------------------------------------------------------------------------------------------------- @@ -932,8 +944,7 @@ QString deckKeywordToAlignedString( const Opm::DeckKeyword& keyword ) std::vector canonicalItemOrder; try { - Opm::Parser parser; - const Opm::ParserKeyword& parserKeyword = parser.getKeyword( keyword.name() ); + const Opm::ParserKeyword& parserKeyword = sharedParser().getKeyword( keyword.name() ); if ( std::distance( parserKeyword.begin(), parserKeyword.end() ) == 1 ) { const Opm::ParserRecord& parserRecord = parserKeyword.getRecord( 0 ); @@ -1078,8 +1089,7 @@ QString deckKeywordToAlignedString( const Opm::DeckKeyword& keyword ) bool slashTerminated = true; try { - Opm::Parser parser; - slashTerminated = !parser.getKeyword( keyword.name() ).hasFixedSize(); + slashTerminated = !sharedParser().getKeyword( keyword.name() ).hasFixedSize(); } catch ( ... ) {