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.
This commit is contained in:
2025-09-16 17:21:29 -04:00
parent 02c5daca3c
commit a3dc7e980e
4 changed files with 73 additions and 26 deletions

View File

@ -24,27 +24,30 @@ namespace Dillo::Hydrogen::JavaScriptForge ::detail:: StackMachine_m
void void
StackMachine::run() StackMachine::run()
{ {
while( not tokens.empty() ) while( not tokenStack.empty() and tokenStack.back().hasNext() )
{ {
runWord( next() ); runWord( next() );
if( C::debug ) std::cerr << "After processing stack is now: " << std::endl; 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; if( C::debug ) std::cerr << "Run done with stack at size: " << stack.size() << std::endl;
} }
std::string std::string_view
StackMachine::next() StackMachine::next()
{ {
if( tokens.empty() ) if( tokenStack.empty() )
{ {
throw std::runtime_error{ "FATAL: Token required, no more tokens left." }; throw std::runtime_error{ "FATAL: Token required, no more tokens left." };
} }
const auto rv= tokens.front(); const auto rv= tokenStack.back().next();
tokens.pop_front(); while( not tokenStack.empty() and not tokenStack.back().hasNext() ) tokenStack.pop_back();
return rv; return rv;
} }
@ -161,7 +164,7 @@ namespace Dillo::Hydrogen::JavaScriptForge ::detail:: StackMachine_m
} }
else if( word == "@print" ) else if( word == "@print" )
{ {
output << pop() << std::endl; output << pop< std::string >() << std::endl;
} }
else if( word == "@if" ) 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." }; 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; 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 ); 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 else
{ {
@ -203,10 +208,12 @@ namespace Dillo::Hydrogen::JavaScriptForge ::detail:: StackMachine_m
T T
StackMachine::pop() 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() StackMachine::pop()
{ {
const auto rv= peek(); const auto rv= peek();
@ -214,24 +221,38 @@ namespace Dillo::Hydrogen::JavaScriptForge ::detail:: StackMachine_m
return rv; return rv;
} }
const std::string & const std::variant< std::string, StackMachine::Integer > &
StackMachine::peek() StackMachine::peek()
{ {
if( stack.empty() ) throw std::runtime_error{ "FATAL: No more elements in stack." }; if( stack.empty() ) throw std::runtime_error{ "FATAL: No more elements in stack." };
return stack.back(); return stack.back();
} }
void
StackMachine::push( std::variant< std::string, Integer > element )
{
stack.push_back( std::move( element ) );
}
void void
StackMachine::push( std::string element ) StackMachine::push( std::string element )
{ {
stack.push_back( std::move( element ) ); stack.push_back( std::move( element ) );
} }
void
StackMachine::push( Integer element )
{
stack.push_back( std::move( element ) );
}
template< typename T > template< typename T >
void void
StackMachine::push( const T &t ) 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 ) );
} }
} }

View File

@ -9,6 +9,7 @@ static_assert( __cplusplus >= 2023'02 );
#include <map> #include <map>
#include <vector> #include <vector>
#include <string> #include <string>
#include <variant>
#include <deque> #include <deque>
namespace Dillo::Hydrogen::JavaScriptForge ::detail:: StackMachine_m namespace Dillo::Hydrogen::JavaScriptForge ::detail:: StackMachine_m
@ -27,22 +28,38 @@ namespace Dillo::Hydrogen::JavaScriptForge ::detail:: StackMachine_m
class exports::StackMachine class exports::StackMachine
{ {
private: private:
using Integer= std::int64_t;
using Float= long double;
// The main runtime stack of the forth engine. // The main runtime stack of the forth engine.
// It grows on the "back". // It grows on the "back".
// A `std::deque` is used instead of `std::vector`, because // A `std::deque` is used instead of `std::vector`, because
// it helps cut down on large allocations, by subdividing things. // 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? // TODO: Should these tokens be binary?
// Upside: compressed. // Upside: compressed.
// Downside: more memory management in the machine end? // Downside: more memory management in the machine end?
std::deque< std::string > tokens; std::deque< std::string > tokens;
using Integer= std::int64_t; struct Tokenizer
using Float= long double; {
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. // Which side of the current conditional to take.
enum ConditionalState { If, Else, Skipped }; enum ConditionalState { If, Else, Skipped };
@ -58,10 +75,12 @@ namespace Dillo::Hydrogen::JavaScriptForge ::detail:: StackMachine_m
void runWord( std::string_view word ); void runWord( std::string_view word );
std::string pop(); std::variant< std::string, Integer > pop();
const std::string &peek(); const std::variant< std::string, Integer > &peek();
void push( std::string s ); void push( std::string s );
void push( Integer s );
void push( std::variant< std::string, Integer > s );
template< typename T > template< typename T >
T pop(); T pop();
@ -69,7 +88,10 @@ namespace Dillo::Hydrogen::JavaScriptForge ::detail:: StackMachine_m
template< typename T > template< typename T >
void push( const T &t ); void push( const T &t );
std::string next();
std::string_view next();
void recurse( std::string_view func );
public: public:
StackMachine( std::ostream &output= std::cout ); StackMachine( std::ostream &output= std::cout );
@ -78,6 +100,8 @@ namespace Dillo::Hydrogen::JavaScriptForge ::detail:: StackMachine_m
loadProgram( std::deque< std::string > tokens ) loadProgram( std::deque< std::string > tokens )
{ {
this->tokens= std::move( tokens ); this->tokens= std::move( tokens );
tokenStack.clear();
tokenStack.emplace_back( this->tokens );
} }
void run(); void run();

View File

@ -29,8 +29,8 @@ do_fibs
@dup @dup
@i_dec @i_dec
@do_fibs @do_fibs
@print
@fib @fib
@print
@else @else
0 0
@endif @endif
@ -42,6 +42,6 @@ junk
} }
37 @do_fibs 39 @do_fibs

View File

@ -1,15 +1,17 @@
def fib( a ): def fib( a ):
if a == 0: if a == 0:
return 1 return 0
elif a == 1: elif a == 1:
return 1 return 1
else: else:
return fib( a - 1 ) + fib( a - 2 ) return fib( a - 1 ) + fib( a - 2 )
def do_fibs( cnt ): def do_fibs( cnt ):
if cnt != 0: if cnt > 0:
do_fibs( cnt - 1 ) do_fibs( cnt - 1 )
print( fib( cnt ) ) print( fib( cnt ) )
else:
print( fib( 0 ) )
do_fibs( 35 ) do_fibs( 38 )