Replace boost::lexical_cast<> with std functions

The boost provided lexical cast are inefficient and is shown to be a
slowdown in the inner loop. Replaces them with std::atoi/std::atof and
some simple correctness checking.
This commit is contained in:
Jørgen Kvalsvik 2016-03-03 11:46:10 +01:00
parent e4ddf884f1
commit 93b7c0739b
4 changed files with 70 additions and 64 deletions

View File

@ -168,7 +168,7 @@ namespace Opm {
deckItem.push_backDefault( value );
}
} else {
ValueType value = readValueToken<ValueType>(token.string());
ValueType value = readValueToken<ValueType>( token );
deckItem.push_back(value);
}
}
@ -212,7 +212,7 @@ namespace Opm {
for (size_t i=0; i < st.count() - 1; i++)
rawRecord->push_front(singleRepetition);
} else {
ValueType value = readValueToken<ValueType>(token.string() );
ValueType value = readValueToken<ValueType>( token );
deckItem.push_back(value);
}
}

View File

@ -17,9 +17,11 @@
along with OPM. If not, see <http://www.gnu.org/licenses/>.
*/
#include <array>
#include <algorithm>
#include <cctype>
#include <string>
#include <stdexcept>
#include <boost/lexical_cast.hpp>
#include <opm/parser/eclipse/RawDeck/StarToken.hpp>
#include <opm/parser/eclipse/Utility/Stringview.hpp>
@ -31,7 +33,7 @@ namespace Opm {
bool isStarToken(const std::string& token,
std::string& countString,
std::string& valueString) {
return isStarToken( string_view( token.begin(), token.end() ), countString, valueString );
return isStarToken( string_view( token ), countString, valueString );
}
bool isStarToken(const string_view& token,
@ -65,60 +67,67 @@ namespace Opm {
// if a star is prefixed by an unsigned integer N, then this should be
// interpreted as "repeat value after star N times"
try {
boost::lexical_cast<int>(token.substr(0, pos));
}
catch (...) {
// the lexical cast may fail as the number of digits may be too large...
return false;
}
countString = token.substr(0, pos);
valueString = token.substr(pos + 1);
return true;
}
template< typename T >
T readValueToken(const std::string& valueString) {
try {
return boost::lexical_cast<T>(valueString);
}
catch (boost::bad_lexical_cast&) {
throw std::invalid_argument("Unable to convert string '" + valueString + "' to typeid: " + typeid(T).name());
}
template<>
int readValueToken< int >( string_view view ) {
std::array< char, 64 > buffer {};
std::copy( view.begin(), view.end(), buffer.begin() );
/*
* isdigit can be a macro and is unreliable with std::algorithm. By
* wrapping it in a lambda (which will be inlined anyway) this problem
* goes away.
*/
const auto is_digit = []( char ch ) { return std::isdigit( ch ); };
if( !std::all_of( buffer.begin(), buffer.begin() + view.size(), is_digit ) )
throw std::invalid_argument( "Error: expected all digits, got: '" + view + "'" );
return std::atoi( buffer.data() );
}
static inline bool fortran_float( char ch ) {
return ch == 'd' || ch == 'D' || ch == 'E';
}
static inline bool is_double_lexeme( char ch ) {
return isdigit( ch ) || ch == '.' || ch == 'e'
|| ch == '-' || ch == '+';
}
template<>
double readValueToken< double >( string_view view ) {
std::array< char, 64 > buffer {};
std::copy( view.begin(), view.end(), buffer.begin() );
auto fortran = std::find_if( buffer.begin(), buffer.end(), fortran_float );
// Eclipse supports Fortran syntax for specifying exponents of floating point
// numbers ('D' and 'E', e.g., 1.234d5) while C++ only supports the 'e' (e.g.,
// 1.234e5). the quick fix is to replace 'D' by 'E' and 'd' by 'e' before trying
// to convert the string into a floating point value.
if( fortran != buffer.end() )
*fortran = 'e';
if( !std::all_of( buffer.begin(), buffer.begin() + view.size(), is_double_lexeme ) )
throw std::invalid_argument( "Error: expected 'double' lexemes, got: '" + view + "'" );
return std::atof( buffer.data() );
}
template <>
double readValueToken< double >(const std::string& valueString) {
try {
return boost::lexical_cast<double>(valueString);
}
catch (boost::bad_lexical_cast&) {
// Eclipse supports Fortran syntax for specifying exponents of floating point
// numbers ('D' and 'E', e.g., 1.234d5) while C++ only supports the 'e' (e.g.,
// 1.234e5). the quick fix is to replace 'D' by 'E' and 'd' by 'e' before trying
// to convert the string into a floating point value. This may not be the most
// performant thing to do, but it is probably fast enough.
std::string vs(valueString);
std::replace(vs.begin(), vs.end(), 'D', 'E');
std::replace(vs.begin(), vs.end(), 'd', 'e');
std::string readValueToken< std::string >( string_view view ) {
if( view.size() == 0 || view[ 0 ] != '\'' )
return view.string();
try { return boost::lexical_cast<double>(vs); }
catch (boost::bad_lexical_cast&) {
throw std::invalid_argument("Unable to convert string '" + valueString + "' to double");
}
}
}
if( view.size() < 2 || view[ view.size() - 1 ] != '\'')
throw std::invalid_argument("Unable to parse string '" + view + "' as a string token");
template <>
std::string readValueToken< std::string >(const std::string& valueString) {
if (valueString.size() > 0 && valueString[0] == '\'') {
if (valueString.size() < 2 || valueString[valueString.size() - 1] != '\'')
throw std::invalid_argument("Unable to parse string '" + valueString + "' as a string token");
return valueString.substr(1, valueString.size() - 2);
}
else
return valueString;
return view.substr( 1, view.size() - 1 );
}
void StarToken::init_( const string_view& token ) {
@ -142,6 +151,5 @@ namespace Opm {
}
}
template int readValueToken< int >(const std::string& );
}

View File

@ -24,8 +24,6 @@
#include <opm/parser/eclipse/Utility/Stringview.hpp>
#include <boost/lexical_cast.hpp>
namespace Opm {
bool isStarToken(const string_view& token,
std::string& countString,
@ -36,12 +34,12 @@ namespace Opm {
std::string& valueString);
template <class T>
T readValueToken(const std::string& valueString);
T readValueToken( string_view );
class StarToken {
public:
StarToken(const std::string& token) :
StarToken( string_view( token.begin(), token.end() ) )
StarToken( string_view( token ) )
{}
StarToken(const string_view& token)

View File

@ -99,15 +99,15 @@ BOOST_AUTO_TEST_CASE( ContainsStar_WithStar_ReturnsTrue ) {
}
BOOST_AUTO_TEST_CASE( readValueToken_basic_validity_tests ) {
BOOST_CHECK_THROW( Opm::readValueToken<int>("3.3"), std::invalid_argument );
BOOST_CHECK_THROW( Opm::readValueToken<double>("truls"), std::invalid_argument );
BOOST_CHECK_EQUAL( "3.3", Opm::readValueToken<std::string>("3.3") );
BOOST_CHECK_CLOSE( 3.3, Opm::readValueToken<double>("3.3e0"), 1e-6 );
BOOST_CHECK_CLOSE( 3.3, Opm::readValueToken<double>("3.3d0"), 1e-6 );
BOOST_CHECK_CLOSE( 3.3, Opm::readValueToken<double>("3.3E0"), 1e-6 );
BOOST_CHECK_CLOSE( 3.3, Opm::readValueToken<double>("3.3D0"), 1e-6 );
BOOST_CHECK_EQUAL( "OLGA", Opm::readValueToken<std::string>("OLGA") );
BOOST_CHECK_EQUAL( "OLGA", Opm::readValueToken<std::string>("'OLGA'") );
BOOST_CHECK_EQUAL( "123*456", Opm::readValueToken<std::string>("123*456") );
BOOST_CHECK_EQUAL( "123*456", Opm::readValueToken<std::string>("'123*456'") );
BOOST_CHECK_THROW( Opm::readValueToken<int>( std::string( "3.3" ) ), std::invalid_argument );
BOOST_CHECK_THROW( Opm::readValueToken<double>( std::string( "truls" ) ), std::invalid_argument );
BOOST_CHECK_EQUAL( "3.3", Opm::readValueToken<std::string>( std::string( "3.3" ) ) );
BOOST_CHECK_CLOSE( 3.3, Opm::readValueToken<double>( std::string( "3.3e0" ) ), 1e-6 );
BOOST_CHECK_CLOSE( 3.3, Opm::readValueToken<double>( std::string( "3.3d0" ) ), 1e-6 );
BOOST_CHECK_CLOSE( 3.3, Opm::readValueToken<double>( std::string( "3.3E0" ) ), 1e-6 );
BOOST_CHECK_CLOSE( 3.3, Opm::readValueToken<double>( std::string( "3.3D0" ) ), 1e-6 );
BOOST_CHECK_EQUAL( "OLGA", Opm::readValueToken<std::string>( std::string( "OLGA" ) ) );
BOOST_CHECK_EQUAL( "OLGA", Opm::readValueToken<std::string>( std::string( "'OLGA'" ) ) );
BOOST_CHECK_EQUAL( "123*456", Opm::readValueToken<std::string>( std::string( "123*456" ) ) );
BOOST_CHECK_EQUAL( "123*456", Opm::readValueToken<std::string>( std::string( "'123*456'" ) ) );
}