Performance.

Don't parse PEGTL twice, once for modelType and once for
model itself.

Also don't do O(n^2) searches on parameter names when
reading libraries.

And, because it's still too slow, multi-thread it.
This commit is contained in:
Jeff Young 2024-08-31 15:04:15 +01:00
parent 8f348820f3
commit 1959bacf08
11 changed files with 163 additions and 126 deletions

View File

@ -836,12 +836,21 @@ void SIM_MODEL::doSetParamValue( int aParamIndex, const std::string& aValue )
void SIM_MODEL::SetParamValue( int aParamIndex, const std::string& aValue,
SIM_VALUE::NOTATION aNotation )
{
std::string value = aValue;
wxString value( aValue );
// PEGTL is slow as shit. Avoid if possible.
static const wxRegEx plainNumber( wxS( "^[0-9.]*$" ) );
if( plainNumber.Matches( value ) )
{
doSetParamValue( aParamIndex, aValue );
return;
}
if( aNotation != SIM_VALUE::NOTATION::SI || aValue.find( ',' ) != std::string::npos )
value = SIM_VALUE::ConvertNotation( value, aNotation, SIM_VALUE::NOTATION::SI );
value = SIM_VALUE::ConvertNotation( aValue, aNotation, SIM_VALUE::NOTATION::SI );
doSetParamValue( aParamIndex, value );
doSetParamValue( aParamIndex, aValue );
}

View File

@ -83,13 +83,23 @@ SIM_MODEL_NGSPICE::SIM_MODEL_NGSPICE( TYPE aType ) :
int SIM_MODEL_NGSPICE::doFindParam( const std::string& aParamName ) const
{
// Special case to allow escaped model parameters (suffixed with "_")
for( int ii = 0; ii < (int) GetParamCount(); ++ii )
{
const PARAM& param = GetParam( ii );
if( param.Matches( aParamName ) || param.Matches( aParamName + "_" ) )
if( param.Matches( aParamName ) )
return ii;
}
// Look for escaped param names as a second pass (as they're less common)
for( int ii = 0; ii < (int) GetParamCount(); ++ii )
{
const PARAM& param = GetParam( ii );
if( !param.info.name.ends_with( '_' ) )
continue;
if( param.Matches( aParamName + "_" ) )
return ii;
}
@ -111,7 +121,7 @@ void SIM_MODEL_NGSPICE::SetParamFromSpiceCode( const std::string& aParamName,
for( int ii = 0; ii < (int) GetParamCount(); ++ii )
{
const PARAM& param = GetParam(ii);
const PARAM& param = GetParam( ii );
if( param.info.isSpiceInstanceParam || param.info.category == PARAM::CATEGORY::SUPERFLUOUS )
continue;
@ -123,15 +133,18 @@ void SIM_MODEL_NGSPICE::SetParamFromSpiceCode( const std::string& aParamName,
}
}
// Look for escaped param names as a second pass (as they're less common)
for( int ii = 0; ii < (int) GetParamCount(); ++ii )
{
const PARAM& param = GetParam(ii);
const PARAM& param = GetParam( ii );
if( param.info.isSpiceInstanceParam || param.info.category == PARAM::CATEGORY::SUPERFLUOUS )
continue;
if( param.info.name.ends_with( "_" )
&& boost::iequals( param.info.name, aParamName + "_" ) )
if( !param.info.name.ends_with( '_' ) )
continue;
if( param.Matches( aParamName + "_" ) )
{
SetParamValue( ii, aValue, aNotation );
return;

View File

@ -2,7 +2,7 @@
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2022 Mikolaj Wielgus
* Copyright (C) 2022-2023 KiCad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 2022-2024 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -57,21 +57,24 @@ std::string SPICE_GENERATOR_SPICE::Preview( const SPICE_ITEM& aItem ) const
std::unique_ptr<SIM_MODEL_SPICE> SIM_MODEL_SPICE::Create( const SIM_LIBRARY_SPICE& aLibrary,
const std::string& aSpiceCode )
{
SIM_MODEL::TYPE modelType = SPICE_MODEL_PARSER::ReadType( aLibrary, aSpiceCode );
std::unique_ptr<SIM_MODEL> model = SIM_MODEL::Create( modelType );
SIM_MODEL::TYPE modelType = SIM_MODEL::TYPE::NONE;
if( SIM_MODEL_SPICE* spiceModel = dynamic_cast<SIM_MODEL_SPICE*>( model.release() ) )
try
{
try
std::unique_ptr<PARSE_TREE> root = SPICE_MODEL_PARSER::ParseModel( aSpiceCode );
modelType = SPICE_MODEL_PARSER::ReadType( aLibrary, root );
if( auto* model = dynamic_cast<SIM_MODEL_SPICE*>( SIM_MODEL::Create( modelType ).release() ) )
{
spiceModel->m_spiceModelParser->ReadModel( aLibrary, aSpiceCode );
return std::unique_ptr<SIM_MODEL_SPICE>( spiceModel );
}
catch( const IO_ERROR& )
{
// Fall back to raw spice code
model->m_spiceModelParser->ReadModel( aLibrary, root );
return std::unique_ptr<SIM_MODEL_SPICE>( model );
}
}
catch( const IO_ERROR& )
{
// Fall back to raw spice code
}
// Fall back to raw spice code
return std::make_unique<SIM_MODEL_SPICE_FALLBACK>( modelType, aSpiceCode );

View File

@ -64,13 +64,23 @@ std::vector<std::string> SIM_MODEL_SPICE_FALLBACK::GetPinNames() const
int SIM_MODEL_SPICE_FALLBACK::doFindParam( const std::string& aParamName ) const
{
// Special case to allow escaped model parameters (suffixed with "_")
for( int ii = 0; ii < GetParamCount(); ++ii )
{
const SIM_MODEL::PARAM& param = GetParam( ii );
if( param.Matches( aParamName ) || param.Matches( aParamName + "_" ) )
if( param.Matches( aParamName ) )
return ii;
}
// Look for escaped param names as a second pass (as they're less common)
for( int ii = 0; ii < GetParamCount(); ++ii )
{
const SIM_MODEL::PARAM& param = GetParam( ii );
if( !param.info.name.ends_with( '_' ) )
continue;
if( param.Matches( aParamName + "_" ) )
return ii;
}

View File

@ -2,7 +2,7 @@
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2022 Mikolaj Wielgus
* Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 2022-2024 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -26,7 +26,6 @@
#include <sim/spice_grammar.h>
#include <fmt/core.h>
#include <pegtl.hpp>
#include <pegtl/contrib/parse_tree.hpp>
@ -67,26 +66,11 @@ std::vector<std::string> SPICE_GENERATOR_SUBCKT::CurrentNames( const SPICE_ITEM&
void SPICE_MODEL_PARSER_SUBCKT::ReadModel( const SIM_LIBRARY_SPICE& aLibrary,
const std::string& aSpiceCode )
std::unique_ptr<PARSE_TREE>& aParseTree )
{
tao::pegtl::string_input<> in( aSpiceCode, "from_content" );
std::unique_ptr<tao::pegtl::parse_tree::node> root;
try
{
root = tao::pegtl::parse_tree::parse<SIM_MODEL_SUBCKT_SPICE_PARSER::spiceUnitGrammar,
SIM_MODEL_SUBCKT_SPICE_PARSER::spiceUnitSelector,
tao::pegtl::nothing,
SIM_MODEL_SUBCKT_SPICE_PARSER::control>( in );
}
catch( const tao::pegtl::parse_error& e )
{
THROW_IO_ERROR( e.what() );
}
SIM_MODEL_SUBCKT& model = static_cast<SIM_MODEL_SUBCKT&>( m_model );
for( const auto& node : root->children )
for( const auto& node : aParseTree->root->children )
{
if( node->is_type<SIM_MODEL_SUBCKT_SPICE_PARSER::dotSubckt>() )
{
@ -131,7 +115,8 @@ void SPICE_MODEL_PARSER_SUBCKT::ReadModel( const SIM_LIBRARY_SPICE& aLibrary,
}
}
model.m_spiceCode = aSpiceCode;
if( aParseTree->root->children.size() == 1 )
model.m_spiceCode = aParseTree->root->children[0]->string();
}

View File

@ -2,7 +2,7 @@
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2022 Mikolaj Wielgus
* Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 2022-2024 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -44,7 +44,8 @@ class SPICE_MODEL_PARSER_SUBCKT : public SPICE_MODEL_PARSER
public:
using SPICE_MODEL_PARSER::SPICE_MODEL_PARSER;
void ReadModel( const SIM_LIBRARY_SPICE& aLibrary, const std::string& aSpiceCode ) override;
void ReadModel( const SIM_LIBRARY_SPICE& aLibrary,
std::unique_ptr<PARSE_TREE>& aParseTree ) override;
};

View File

@ -33,6 +33,8 @@
#include <pegtl.hpp>
#include <pegtl/contrib/parse_tree.hpp>
#include <utility>
#include <core/thread_pool.h>
namespace SIM_LIBRARY_SPICE_PARSER
@ -120,7 +122,7 @@ void SPICE_LIBRARY_PARSER::readFallbacks( const wxString& aFilePath, REPORTER& a
{
wxString lib = m_library.m_pathResolver( tokenizer.GetNextToken(), aFilePath );
parseFile( lib, aReporter );
readFallbacks( lib, aReporter );
}
}
}
@ -131,7 +133,9 @@ void SPICE_LIBRARY_PARSER::readFallbacks( const wxString& aFilePath, REPORTER& a
}
void SPICE_LIBRARY_PARSER::parseFile( const wxString &aFilePath, REPORTER& aReporter )
void SPICE_LIBRARY_PARSER::parseFile( const wxString &aFilePath,
std::vector<std::pair<std::string, std::string>>* aModelQueue,
REPORTER& aReporter )
{
try
{
@ -146,24 +150,7 @@ void SPICE_LIBRARY_PARSER::parseFile( const wxString &aFilePath, REPORTER& aRepo
{
if( node->is_type<SIM_LIBRARY_SPICE_PARSER::modelUnit>() )
{
std::string model = node->string();
std::string modelName = node->children.at( 0 )->string();
try
{
m_library.m_models.push_back( SIM_MODEL_SPICE::Create( m_library, model ) );
m_library.m_modelNames.emplace_back( modelName );
}
catch( const IO_ERROR& e )
{
aReporter.Report( e.What(), RPT_SEVERITY_ERROR );
}
catch( ... )
{
aReporter.Report( wxString::Format( _( "Cannot create sim model from %s" ),
model ),
RPT_SEVERITY_ERROR );
}
aModelQueue->emplace_back( node->children.at( 0 )->string(), node->string() );
}
else if( node->is_type<SIM_LIBRARY_SPICE_PARSER::dotInclude>() )
{
@ -171,7 +158,7 @@ void SPICE_LIBRARY_PARSER::parseFile( const wxString &aFilePath, REPORTER& aRepo
try
{
parseFile( lib, aReporter );
parseFile( lib, aModelQueue, aReporter );
}
catch( const IO_ERROR& e )
{
@ -204,6 +191,8 @@ void SPICE_LIBRARY_PARSER::ReadFile( const wxString& aFilePath, REPORTER& aRepor
m_library.m_models.clear();
m_library.m_modelNames.clear();
std::vector<std::pair<std::string, std::string>> modelQueue;
// Aside from the simulation model editor dialog, about the only data we use from the
// complete models are the pin definitions for SUBCKTs. The standard LTSpice "cmp" libraries
// (cmp/standard.bjt, cmp/standard.mos, etc.) have copious error which trip up our parser,
@ -211,5 +200,47 @@ void SPICE_LIBRARY_PARSER::ReadFile( const wxString& aFilePath, REPORTER& aRepor
if( !m_forceFullParse && aFilePath.Contains( wxS( "/LTspiceXVII/lib/cmp/standard" ) ) )
readFallbacks( aFilePath, aReporter );
else
parseFile( aFilePath, aReporter );
parseFile( aFilePath, &modelQueue, aReporter );
m_library.m_models.reserve( modelQueue.size() );
m_library.m_modelNames.reserve( modelQueue.size() );
for( int ii = 0; ii < (int) modelQueue.size(); ++ii )
{
m_library.m_models.emplace_back( nullptr );
m_library.m_modelNames.emplace_back( "" );
}
thread_pool& tp = GetKiCadThreadPool();
tp.push_loop( modelQueue.size(),
[&]( const int a, const int b )
{
for( int ii = a; ii < b; ++ii )
{
std::unique_ptr<SIM_MODEL> model;
try
{
model = SIM_MODEL_SPICE::Create( m_library, modelQueue[ii].second );
}
catch( const IO_ERROR& e )
{
aReporter.Report( e.What(), RPT_SEVERITY_ERROR );
}
catch( ... )
{
aReporter.Report( wxString::Format( _( "Cannot create sim model from %s" ),
modelQueue[ii].second ),
RPT_SEVERITY_ERROR );
}
if( model )
{
m_library.m_models[ii] = std::move( model );
m_library.m_modelNames[ii] = modelQueue[ii].first;
}
}
} );
tp.wait_for_tasks();
}

View File

@ -45,7 +45,9 @@ public:
protected:
void readFallbacks( const wxString& aFilePath, REPORTER& aReporter );
void parseFile( const wxString& aFilePath, REPORTER& aReporter );
void parseFile( const wxString& aFilePath,
std::vector<std::pair<std::string, std::string>>* aModelQueue,
REPORTER& aReporter );
private:
bool m_forceFullParse;

View File

@ -27,20 +27,10 @@
#include <sim/spice_grammar.h>
#include <sim/sim_model_spice.h>
#include <sim/sim_library_spice.h>
#include <boost/algorithm/string/case_conv.hpp>
#include <boost/algorithm/string/predicate.hpp>
#include <pegtl.hpp>
#include <pegtl/contrib/parse_tree.hpp>
/**
* Flag to enable SPICE model parser debugging output.
*
* @ingroup trace_env_vars
*/
static const wxChar traceSpiceModelParser[] = wxT( "KICAD_SPICE_MODEL_PARSER" );
namespace SIM_MODEL_SPICE_PARSER
{
@ -59,27 +49,26 @@ namespace SIM_MODEL_SPICE_PARSER
}
SIM_MODEL::TYPE SPICE_MODEL_PARSER::ReadType( const SIM_LIBRARY_SPICE& aLibrary,
const std::string& aSpiceCode )
std::unique_ptr<PARSE_TREE> SPICE_MODEL_PARSER::ParseModel( const std::string& aSpiceCode )
{
tao::pegtl::string_input<> in( aSpiceCode, "Spice_Code" );
std::unique_ptr<tao::pegtl::parse_tree::node> root;
std::unique_ptr parseTree = std::make_unique<PARSE_TREE>();
try
{
root = tao::pegtl::parse_tree::parse<SIM_MODEL_SPICE_PARSER::spiceUnitGrammar,
SIM_MODEL_SPICE_PARSER::spiceUnitSelector,
tao::pegtl::nothing,
SIM_MODEL_SPICE_PARSER::control>
( in );
}
catch( const tao::pegtl::parse_error& e )
{
wxLogTrace( traceSpiceModelParser, wxS( "%s" ), e.what() );
return SIM_MODEL::TYPE::NONE;
}
parseTree->in = std::make_unique<tao::pegtl::string_input<>>( aSpiceCode, "Spice_Code" );
for( const auto& node : root->children )
parseTree->root = tao::pegtl::parse_tree::parse<SIM_MODEL_SPICE_PARSER::spiceUnitGrammar,
SIM_MODEL_SPICE_PARSER::spiceUnitSelector,
tao::pegtl::nothing,
SIM_MODEL_SPICE_PARSER::control>
( *parseTree->in );
return parseTree;
}
SIM_MODEL::TYPE SPICE_MODEL_PARSER::ReadType( const SIM_LIBRARY_SPICE& aLibrary,
std::unique_ptr<PARSE_TREE>& aParseTree )
{
for( const auto& node : aParseTree->root->children )
{
if( node->is_type<SIM_MODEL_SPICE_PARSER::dotModelAko>() )
{
@ -161,28 +150,12 @@ SIM_MODEL::TYPE SPICE_MODEL_PARSER::ReadType( const SIM_LIBRARY_SPICE& aLibrary,
void SPICE_MODEL_PARSER::ReadModel( const SIM_LIBRARY_SPICE& aLibrary,
const std::string& aSpiceCode )
std::unique_ptr<PARSE_TREE>& aParseTree )
{
// The default behavior is to treat the Spice param=value pairs as the model parameters and
// values (for many models the correspondence is not exact, so this function is overridden).
tao::pegtl::string_input<> in( aSpiceCode, "Spice_Code" );
std::unique_ptr<tao::pegtl::parse_tree::node> root;
try
{
root = tao::pegtl::parse_tree::parse<SIM_MODEL_SPICE_PARSER::spiceUnitGrammar,
SIM_MODEL_SPICE_PARSER::spiceUnitSelector,
tao::pegtl::nothing,
SIM_MODEL_SPICE_PARSER::control>
( in );
}
catch( tao::pegtl::parse_error& e )
{
THROW_IO_ERROR( e.what() );
}
for( const auto& node : root->children )
for( const auto& node : aParseTree->root->children )
{
if( node->is_type<SIM_MODEL_SPICE_PARSER::dotModelAko>() )
{
@ -232,14 +205,13 @@ void SPICE_MODEL_PARSER::ReadModel( const SIM_LIBRARY_SPICE& aLibrary,
}
else if( node->is_type<SIM_MODEL_SPICE_PARSER::dotModel>() )
{
std::string modelName;
std::string paramName;
for( const auto& subnode : node->children )
{
if( subnode->is_type<SIM_MODEL_SPICE_PARSER::modelName>() )
{
modelName = subnode->string();
// Do nothing.
}
else if( subnode->is_type<SIM_MODEL_SPICE_PARSER::dotModelType>() )
{
@ -267,7 +239,8 @@ void SPICE_MODEL_PARSER::ReadModel( const SIM_LIBRARY_SPICE& aLibrary,
}
}
m_model.m_spiceCode = aSpiceCode;
if( aParseTree->root->children.size() == 1 )
m_model.m_spiceCode = aParseTree->root->children[0]->string();
}

View File

@ -2,7 +2,7 @@
* This program source code file is part of KiCad, a free EDA CAD application.
*
* Copyright (C) 2022 Mikolaj Wielgus
* Copyright (C) 2022 KiCad Developers, see AUTHORS.txt for contributors.
* Copyright (C) 2022-2024 KiCad Developers, see AUTHORS.txt for contributors.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
@ -26,21 +26,31 @@
#define SPICE_MODEL_PARSER_H
#include <sim/sim_model.h>
#include <pegtl/contrib/parse_tree.hpp>
class SIM_MODEL_SPICE;
class SIM_LIBRARY_SPICE;
struct PARSE_TREE
{
std::unique_ptr<tao::pegtl::string_input<>> in;
std::unique_ptr<tao::pegtl::parse_tree::node> root;
};
class SPICE_MODEL_PARSER
{
public:
static std::unique_ptr<PARSE_TREE> ParseModel( const std::string& aSpiceCode );
static SIM_MODEL::TYPE ReadType( const SIM_LIBRARY_SPICE& aLibrary,
const std::string& aSpiceCode );
std::unique_ptr<PARSE_TREE>& aRoot );
SPICE_MODEL_PARSER( SIM_MODEL_SPICE& aModel ) : m_model( aModel ) {}
virtual ~SPICE_MODEL_PARSER() = default;
virtual void ReadModel( const SIM_LIBRARY_SPICE& aLibrary, const std::string& aSpiceCode );
virtual void ReadModel( const SIM_LIBRARY_SPICE& aLibrary, std::unique_ptr<PARSE_TREE>& aRoot );
protected:
static SIM_MODEL::TYPE ReadTypeFromSpiceStrings( const std::string& aTypeString,

View File

@ -275,14 +275,14 @@ BOOST_AUTO_TEST_CASE( Diodes )
BOOST_CHECK_EQUAL( modelName, "D1" );
BOOST_CHECK_EQUAL( model.FindParam( "is" )->value, "1.23n" );
BOOST_CHECK_EQUAL( model.FindParam( "n" )->value, "1.23" );
BOOST_CHECK_EQUAL( model.FindParam( "rs" )->value, "0.789" );
BOOST_CHECK_EQUAL( model.FindParam( "rs" )->value, ".7890" );
BOOST_CHECK_EQUAL( model.FindParam( "ikf" )->value, "12.34m" );
BOOST_CHECK_EQUAL( model.FindParam( "xti" )->value, "3" );
BOOST_CHECK_EQUAL( model.FindParam( "eg" )->value, "1.23" );
BOOST_CHECK_EQUAL( model.FindParam( "cjo" )->value, "0.9p" );
BOOST_CHECK_EQUAL( model.FindParam( "m_" )->value, "0.56" );
BOOST_CHECK_EQUAL( model.FindParam( "vj" )->value, "0.78" );
BOOST_CHECK_EQUAL( model.FindParam( "fc" )->value, "0.9" );
BOOST_CHECK_EQUAL( model.FindParam( "m_" )->value, ".56" );
BOOST_CHECK_EQUAL( model.FindParam( "vj" )->value, ".78" );
BOOST_CHECK_EQUAL( model.FindParam( "fc" )->value, ".9" );
BOOST_CHECK_EQUAL( model.FindParam( "isr" )->value, "12.34n" );
BOOST_CHECK_EQUAL( model.FindParam( "nr" )->value, "2.345" );
BOOST_CHECK_EQUAL( model.FindParam( "bv" )->value, "100" );
@ -362,7 +362,7 @@ BOOST_AUTO_TEST_CASE( Diodes )
BOOST_CHECK_EQUAL( model.FindParam( "ikf" )->value, "111.1" );
BOOST_CHECK_EQUAL( model.FindParam( "xti" )->value, "3" );
BOOST_CHECK_EQUAL( model.FindParam( "eg" )->value, "2.2" );
BOOST_CHECK_EQUAL( model.FindParam( "m_" )->value, "0.3" );
BOOST_CHECK_EQUAL( model.FindParam( "m_" )->value, ".3" );
break;
case 24: