Overview
Examples
Screenshots
Comparisons
Applications
Download
Documentation
Tutorials
Bazaar
Status & Roadmap
FAQ
Authors & License
Forums
Funding Ultimate++
Search on this site
Search in forums












SourceForge.net Logo
Home » Developing U++ » U++ Developers corner » Little experiment compiling to function pointer calls...
Little experiment compiling to function pointer calls... [message #34342] Wed, 16 November 2011 19:54 Go to next message
mirek is currently offline  mirek
Messages: 13975
Registered: November 2005
Ultimate Member
Thinking about TCC and runtime compilation frameworks (also web templates) I have got an idea how represent the code structure in the tree of virtual objects (basically, via function pointers).

I have put together a litte experimental snippet:

#include <Core/Core.h>

using namespace Upp;

struct Oper {
	virtual double Execute() = 0;
	virtual ~Oper() {}
};

struct BinOper : Oper {
	One<Oper> a;
	One<Oper> b;
};

struct Add : BinOper {
	virtual double Execute() { return a->Execute() + b->Execute(); }
};

struct Sub : BinOper {
	virtual double Execute() { return a->Execute() - b->Execute(); }
};

struct Mul : BinOper {
	virtual double Execute() { return a->Execute() * b->Execute(); }
};

struct Div : BinOper {
	virtual double Execute() { return a->Execute() / b->Execute(); }
};

struct Const : Oper {
	double value;
	virtual double Execute() { return value; }
};

struct Var : Oper {
	double *var;
	virtual double Execute() { return *var; }
};

struct Compiler {
	VectorMap<String, double *> var;
	One<Oper> Term(CParser& p);
	One<Oper> Exp(CParser& p);
	One<Oper> Factor(CParser& p);
};

One<Oper> Compiler::Term(CParser& p)
{
	One<Oper> result;
	if(p.IsId()) {
		double *v = var.Get(p.ReadId(), NULL);
		if(!v)
			p.ThrowError("unknown variable");
		result.Create<Var>().var = v;
	}
	else
	if(p.Char('(')) {
		result = Exp(p);
		p.PassChar(')');
	}
	else
		result.Create<Const>().value = p.ReadDouble();
	return result;
}

One<Oper> Compiler::Factor(CParser& p)
{
	One<Oper> result = Term(p);
	for(;;)
		if(p.Char('*')) {
			One<Oper> rr;
			Mul& m = rr.Create<Mul>();
			m.a = result;
			m.b = Term(p);
			result = rr;
		}
		else
		if(p.Char('/')) {
			One<Oper> rr;
			Div& m = rr.Create<Div>();
			m.a = result;
			m.b = Term(p);
			result = rr;
		}
		else
			return result;
}

One<Oper> Compiler::Exp(CParser& p)
{
	One<Oper> result = Factor(p);
	for(;;)
		if(p.Char('+')) {
			One<Oper> rr;
			Add& m = rr.Create<Add>();
			m.a = result;
			m.b = Factor(p);
			result = rr;
		}
		else
		if(p.Char('-')) {
			One<Oper> rr;
			Sub& m = rr.Create<Sub>();
			m.a = result;
			m.b = Factor(p);
			result = rr;
		}
		else
			return result;
}

VectorMap<String, double> var;

double Exp(CParser& p);

double Term(CParser& p)
{
	if(p.Id("abs")) {
		p.PassChar('(');
		double x = Exp(p);
		p.PassChar(')');
		return fabs(x);
	}
	if(p.IsId())
		return var.Get(p.ReadId(), 0);
	if(p.Char('(')) {
		double x = Exp(p);
		p.PassChar(')');
		return x;
	}
	return p.ReadDouble();
}

double Mul(CParser& p)
{
	double x = Term(p);
	for(;;)
		if(p.Char('*'))
			x = x * Term(p);
		else
		if(p.Char('/')) {
			double y = Term(p);
			if(y == 0)
				p.ThrowError("Divide by zero");
			x = x / y;
		}
		else
			return x;
}

double Exp(CParser& p)
{
	double x = Mul(p);
	for(;;)
		if(p.Char('+'))
			x = x + Mul(p);
		else
		if(p.Char('-'))
			x = x - Mul(p);
		else
			return x;
}


CONSOLE_APP_MAIN
{
	double x, y;
	
	Compiler c;
	c.var.Add("x", &x);
	c.var.Add("y", &y);
	
	CParser p("1 / (1 - x * y + x - y)");
	One<Oper> fn = c.Exp(p);
	
	x = 5;
	y = 10;
	
	RDUMP(1 / (1 - x * y + x - y));
	RDUMP(fn->Execute());

	{
		RTIMING("Interpreted");
		double sum = 0;
		for(x = 0; x < 1; x += 0.001)
			for(y = 0; y < 1; y += 0.001) {
				var.GetAdd("x") = x;
				var.GetAdd("y") = y;
				sum += Exp(CParser("1 / (1 - x * y + x - y)"));
			}
		RDUMP(sum);
	}
	{
		RTIMING("Compiled");
		double sum = 0;
		for(x = 0; x < 1; x += 0.001)
			for(y = 0; y < 1; y += 0.001)
				sum += fn->Execute();
		RDUMP(sum);
	}
	{
		RTIMING("Direct");
		double sum = 0;
		for(x = 0; x < 1; x += 0.001)
			for(y = 0; y < 1; y += 0.001)
				sum += 1 / (1 - x * y + x - y);
		RDUMP(sum);
	}
}


This compares three methods to evaluate expression - interpreted, compiled to the virtual tree and 'native' ("compile time compiled").

Results are interesting:

TIMING Direct         :  8.00 ms -  8.00 ms ( 8.00 ms / 1 ), min:  8.00 ms, max:  8.00 ms, nesting: 1 - 1
TIMING Compiled       : 29.00 ms - 29.00 ms (29.00 ms / 1 ), min: 29.00 ms, max: 29.00 ms, nesting: 1 - 1
TIMING Interpreted    : 551.00 ms - 551.00 ms (551.00 ms / 1 ), min: 551.00 ms, max: 551.00 ms, nesting: 1 - 1


Means 'compiled' expression evaluation is only 3 times slower than native code, while being 20 times faster than interpreted results.

I believe this approach would scale up to complete compiler of e.g. C. The advantage over TCC is that such code is 100% portable, no need to emit assembler opcodes...

[/code]
Re: Little experiment compiling to function pointer calls... [message #34352 is a reply to message #34342] Fri, 18 November 2011 09:13 Go to previous messageGo to next message
koldo is currently offline  koldo
Messages: 3356
Registered: August 2008
Senior Veteran
Hello Mirek

I understand the results of your test but, could you explain a little further this Smile :
Quote:

I believe this approach would scale up to complete compiler of e.g. C. The advantage over TCC is that such code is 100% portable, no need to emit assembler opcodes...


Best regards
Iñaki
Re: Little experiment compiling to function pointer calls... [message #34361 is a reply to message #34352] Fri, 18 November 2011 14:51 Go to previous messageGo to next message
mirek is currently offline  mirek
Messages: 13975
Registered: November 2005
Ultimate Member
koldo wrote on Fri, 18 November 2011 03:13

Hello Mirek

I understand the results of your test but, could you explain a little further this Smile :
Quote:

I believe this approach would scale up to complete compiler of e.g. C. The advantage over TCC is that such code is 100% portable, no need to emit assembler opcodes...



Well, TCC produces CPU opcodes. That is definitely faster, but also CPU dependent and a little bit harder to interface with 'host' platform (in this case U++).

Using this, you can more easily create 'runtime compiler' for most scripting languages, which would be slower than one producing CPU opcodes, but very likely faster than interpreted bytecode. And very easily interfacable with host (see how 'x' and 'y' variables are easily iterfaced).

The only problem I can see is jumps. 'goto' is likely out of question and I am still unsure if 'break' is possible (maybe the one way is to implement it using exceptions).

Mirek
Re: Little experiment compiling to function pointer calls... [message #34365 is a reply to message #34361] Fri, 18 November 2011 16:14 Go to previous messageGo to next message
koldo is currently offline  koldo
Messages: 3356
Registered: August 2008
Senior Veteran
Ok Smile

Best regards
Iñaki
Re: Little experiment compiling to function pointer calls... [message #34367 is a reply to message #34365] Fri, 18 November 2011 18:10 Go to previous message
mirek is currently offline  mirek
Messages: 13975
Registered: November 2005
Ultimate Member
I have played with the concept a bit more and got it to be able to compile:

{
	sum = 0;
	for(x = 0; x < 1; x = x + step)
		for(y = 0; y < 1; y = y + step)
			sum = sum + 1 / (1 - x * y + x - y);
}


Runs 6 times slower than native C++, 10 times faster than interpreted.

Code can be seen in sandbox... (tooks about 300 lines of U++ for this 'JIT' compiler).

Well, nice idea that can perhaps be of some use in the future. I am ending the experiment now Smile
Previous Topic: upp GTK compatibility for Ubuntu broken?
Next Topic: Trouble with SizePos...
Goto Forum:
  


Current Time: Fri Apr 19 17:48:39 CEST 2024

Total time taken to generate the page: 0.02634 seconds