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
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 ) );
}
}

View File

@ -9,6 +9,7 @@ static_assert( __cplusplus >= 2023'02 );
#include <map>
#include <vector>
#include <string>
#include <variant>
#include <deque>
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();

View File

@ -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

View File

@ -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 )