From a3dc7e980ea7c6226060274a1f328996a1f7fe6c682ee1337872cb835b6de4cb Mon Sep 17 00:00:00 2001 From: ADAM David Alan Martin Date: Tue, 16 Sep 2025 17:21:29 -0400 Subject: [PATCH] Significant speed improvements. By not cloning code for recursion directly into the processing buffer and by not copying as many strings and by not doing string->int conversions all the time, it's significantly faster. Specifically, it's about 5.5x slower than Python. --- js4g/StackMachine.cc | 49 +++++++++++++++++++++++++++++++------------- js4g/StackMachine.h | 38 +++++++++++++++++++++++++++------- js4g/fib.js4 | 4 ++-- js4g/fib.py | 8 +++++--- 4 files changed, 73 insertions(+), 26 deletions(-) diff --git a/js4g/StackMachine.cc b/js4g/StackMachine.cc index f7007a0..c340b3a 100644 --- a/js4g/StackMachine.cc +++ b/js4g/StackMachine.cc @@ -24,27 +24,30 @@ namespace Dillo::Hydrogen::JavaScriptForge ::detail:: StackMachine_m void StackMachine::run() { - while( not tokens.empty() ) + while( not tokenStack.empty() and tokenStack.back().hasNext() ) { runWord( next() ); if( C::debug ) std::cerr << "After processing stack is now: " << std::endl; - if( C::debug ) std::copy( rbegin( stack ), rend( stack ), std::ostream_iterator< std::string >{ std::cerr, "\n" } ); + if( C::debug ) for( const auto &element: stack ) + { + std::cerr << std::get< std::string >( element ) << std::endl; + } } if( C::debug ) std::cerr << "Run done with stack at size: " << stack.size() << std::endl; } - std::string + std::string_view StackMachine::next() { - if( tokens.empty() ) + if( tokenStack.empty() ) { throw std::runtime_error{ "FATAL: Token required, no more tokens left." }; } - const auto rv= tokens.front(); - tokens.pop_front(); + const auto rv= tokenStack.back().next(); + while( not tokenStack.empty() and not tokenStack.back().hasNext() ) tokenStack.pop_back(); return rv; } @@ -161,7 +164,7 @@ namespace Dillo::Hydrogen::JavaScriptForge ::detail:: StackMachine_m } else if( word == "@print" ) { - output << pop() << std::endl; + output << pop< std::string >() << std::endl; } else if( word == "@if" ) { @@ -174,7 +177,7 @@ namespace Dillo::Hydrogen::JavaScriptForge ::detail:: StackMachine_m { if( definition.has_value() ) throw std::runtime_error{ "Nested definitions not supported." }; - definition= pop(); + definition= pop< std::string >(); if( C::debug ) std::cerr << "Starting function definition for " << definition.value() << std::endl; } @@ -189,9 +192,11 @@ namespace Dillo::Hydrogen::JavaScriptForge ::detail:: StackMachine_m const auto &def= words.at( invoke ); - std::copy( rbegin( def ), rend( def ), front_inserter( tokens ) ); + //std::copy( rbegin( def ), rend( def ), front_inserter( tokens ) ); - if( tokens.size() >= 1000 ) abort(); + //if( tokens.size() >= 1000 ) abort(); + + tokenStack.emplace_back( def ); } else { @@ -203,10 +208,12 @@ namespace Dillo::Hydrogen::JavaScriptForge ::detail:: StackMachine_m T StackMachine::pop() { - return boost::lexical_cast< T >( pop() ); + if( std::holds_alternative< T >( peek() ) ) return std::get< T >( pop() ); + else if( std::holds_alternative< Integer >( peek() ) ) return boost::lexical_cast< T >( std::get< Integer >( pop() ) ); + else return boost::lexical_cast< T >( std::get< std::string >( pop() ) ); } - std::string + std::variant< std::string, StackMachine::Integer > StackMachine::pop() { const auto rv= peek(); @@ -214,24 +221,38 @@ namespace Dillo::Hydrogen::JavaScriptForge ::detail:: StackMachine_m return rv; } - const std::string & + const std::variant< std::string, StackMachine::Integer > & StackMachine::peek() { if( stack.empty() ) throw std::runtime_error{ "FATAL: No more elements in stack." }; return stack.back(); } + void + StackMachine::push( std::variant< std::string, Integer > element ) + { + stack.push_back( std::move( element ) ); + } + void StackMachine::push( std::string element ) { stack.push_back( std::move( element ) ); } + void + StackMachine::push( Integer element ) + { + stack.push_back( std::move( element ) ); + } + template< typename T > void StackMachine::push( const T &t ) { - push( boost::lexical_cast< std::string >( t ) ); + if constexpr( std::is_same_v< Integer, T > ) push( t ); + else if constexpr( std::is_same_v< std::string, T > ) push( t ); + else push( boost::lexical_cast< std::string >( t ) ); } } diff --git a/js4g/StackMachine.h b/js4g/StackMachine.h index 48bb3a8..55d1979 100644 --- a/js4g/StackMachine.h +++ b/js4g/StackMachine.h @@ -9,6 +9,7 @@ static_assert( __cplusplus >= 2023'02 ); #include #include #include +#include #include namespace Dillo::Hydrogen::JavaScriptForge ::detail:: StackMachine_m @@ -27,22 +28,38 @@ namespace Dillo::Hydrogen::JavaScriptForge ::detail:: StackMachine_m class exports::StackMachine { private: + using Integer= std::int64_t; + using Float= long double; + // The main runtime stack of the forth engine. // It grows on the "back". // A `std::deque` is used instead of `std::vector`, because // it helps cut down on large allocations, by subdividing things. - std::deque< std::string > stack; + std::deque< std::variant< std::string, Integer > > stack; // TODO: Should these tokens be binary? // Upside: compressed. // Downside: more memory management in the machine end? std::deque< std::string > tokens; - using Integer= std::int64_t; - using Float= long double; + struct Tokenizer + { + std::deque< std::string >::const_iterator pos; + std::deque< std::string >::const_iterator end; + + explicit + Tokenizer( const std::deque< std::string > &tokens ) + : pos( tokens.begin() ), end( tokens.end() ) {} + + bool hasNext() const { return pos != end; } + + std::string_view next() { return *pos++; } + }; + + std::vector< Tokenizer > tokenStack; - std::map< std::string, std::vector< std::string > > words; + std::map< std::string, std::deque< std::string > > words; // Which side of the current conditional to take. enum ConditionalState { If, Else, Skipped }; @@ -58,10 +75,12 @@ namespace Dillo::Hydrogen::JavaScriptForge ::detail:: StackMachine_m void runWord( std::string_view word ); - std::string pop(); - const std::string &peek(); + std::variant< std::string, Integer > pop(); + const std::variant< std::string, Integer > &peek(); void push( std::string s ); + void push( Integer s ); + void push( std::variant< std::string, Integer > s ); template< typename T > T pop(); @@ -69,7 +88,10 @@ namespace Dillo::Hydrogen::JavaScriptForge ::detail:: StackMachine_m template< typename T > void push( const T &t ); - std::string next(); + + std::string_view next(); + + void recurse( std::string_view func ); public: StackMachine( std::ostream &output= std::cout ); @@ -78,6 +100,8 @@ namespace Dillo::Hydrogen::JavaScriptForge ::detail:: StackMachine_m loadProgram( std::deque< std::string > tokens ) { this->tokens= std::move( tokens ); + tokenStack.clear(); + tokenStack.emplace_back( this->tokens ); } void run(); diff --git a/js4g/fib.js4 b/js4g/fib.js4 index 69db2c8..a3f41f7 100644 --- a/js4g/fib.js4 +++ b/js4g/fib.js4 @@ -29,8 +29,8 @@ do_fibs @dup @i_dec @do_fibs - @print @fib + @print @else 0 @endif @@ -42,6 +42,6 @@ junk } -37 @do_fibs +39 @do_fibs diff --git a/js4g/fib.py b/js4g/fib.py index 8437483..fda4fc3 100644 --- a/js4g/fib.py +++ b/js4g/fib.py @@ -1,15 +1,17 @@ def fib( a ): if a == 0: - return 1 + return 0 elif a == 1: return 1 else: return fib( a - 1 ) + fib( a - 2 ) def do_fibs( cnt ): - if cnt != 0: + if cnt > 0: do_fibs( cnt - 1 ) print( fib( cnt ) ) + else: + print( fib( 0 ) ) -do_fibs( 35 ) +do_fibs( 38 )