Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Architecture Refactoring: My Fork, Flexibility, and Performance Gains #834

Unanswered
guamoko995 asked this question in General
Discussion options

Hello!

I've created a fork of your expr repository—you can view my project here. During the process, I significantly reworked the architecture, focusing on optimization. One of the key changes was replacing the virtual machine with expression assembly in the form of a chain of closures, which significantly increased compilation speed by ~6x (unexpectedly) and execution speed by ~18x (as expected).

My fork is currently in its early stages: some functionality is still missing or not fully implemented. However, I believe the proposed architectural solutions could be useful for the further development of the project.

I'd be happy if the original repository's developers and anyone else interested would take a look at my fork, provide feedback, or perhaps join the discussion and collaboration on improving expr-cls.

Thank you for your work and this excellent project!

Sincerely,
guamoko995

You must be logged in to vote

Replies: 3 comments 8 replies

Comment options

That is a very impressive performance gain! I'm interested in trying this implementation in my project. It would be helpful to know a) a more complete list of missing functionality; b) how "drop-in" a replacement can it be for expr - do the top-level functions and structures have the same names and operation? Some areas that I make use of: overriding all math operators (mostly via patcher), many fucntions defiend on the environment, ternary operator (I mention because I think it is not ipmlemented in your version).

You must be logged in to vote
1 reply
Comment options

a) A more complete list of missing functionality isn't entirely appropriate, as I position my fork as a tool that provides a very simple way to create this functionality. (See https://github.com/guamoko995/expr-cls/tree/master/tests/example/def_env) In general: unary operators, binary operators, constants, functions, and variable sources (structures whose fields act as variables, even without nesting yet) are supported. Therefore, you correctly noted that the ternary operator is not supported. However, it can be replaced with a function that you define yourself. Of course, you will also have to call the function in the expressions themselves.

b) The answer follows from the previous one: all constructs that are not function calls, nor unary or binary operators, are currently unsupported. They will have to be replaced with functions. Therefore, the transition will need to include defining the required environment, as in the example linked above, and replacing complex constructs with functions in the expressions themselves.

In reality, the package is still in its infancy and probably isn't ready for use in other projects in its current form. At the very least, you might be dissatisfied with the approach to error handling during the AST build process. My goal now is to demonstrate the feasibility, thereby attracting interested developers and working together to bring the package to a usable state.

Comment options

Cool! I'd like to take a look. Maybe we can bring some of this performance to the Expr project.

You must be logged in to vote
5 replies
Comment options

I hope you find something useful! If you encounter any issues in the comments (English is not my native language) or have any other questions about the project, please feel free to email me or start a discussion.

Comment options

Nice hack!

	var srcC srcT
+	pSrc := &srcC
...
	return func(src srcT) outT {
+		srcC = src
		return fn()
	}, nil

I'd love to chat more about your implementation.

Comment options

How safe is this hack? What is the downside of swapping pointers, but keeping the same addresses?

	return func(pSrc unsafe.Pointer) base.LazyFunc[varT] {
		varPtr := (*varT)(unsafe.Pointer(uintptr(pSrc) + offset))
+		return func() varT { return *varPtr }
	}
Comment options

There's definitely something to think about here. Perhaps it would have been possible to simply store a dictionary of variables with their addresses. It's just that this part of the code was invented before the hack you mentioned with copying the address into the closure variable.

Comment options

Nice hack!

	var srcC srcT
+	pSrc := &srcC
...
	return func(src srcT) outT {
+		srcC = src
		return fn()
	}, nil

I'd love to chat more about your implementation.

I realized that this very hack is a bottleneck. It prevents compiled expressions from being used concurrently. But I think something can be done about it. The source definitely needs to be moved down from the AST root. I need to think about how exactly. I see three options so far.

  1. Pass through an interface value. (This will slow down the calculation, since there will be one interface cast during the calculation.)
  2. Pass through a regular pointer. (This will increase the memory consumption of the permanently allocated environment, since each source will essentially have its own version of the environment with the source type parameter.)
  3. Pass through an unsafe pointer. (In my opinion, this is the best option; the only downside is the use of an unsafe pointer itself.)
Comment options

As with all benchmarks I was able to find the expression which is faster in expr 🙃

expression: "(.0+1+2+3+4+5+6+7+8+9+10)/2"
expr-cls result: 27.5
expr result: 27.5
BenchmarkСalc/expr-cls-8 	27633798	 42.83 ns/op
BenchmarkСalc/expr-8 	41335419	 27.89 ns/op
You must be logged in to vote
2 replies
Comment options

Actually it was just optimization kicking in in Expr. With disabled optimization expr-cls is faster!

BenchmarkСalc/expr-cls-8 	28399611	 42.22 ns/op
BenchmarkСalc/expr-8 	 6512395	 183.3 ns/op

Nice!

Comment options

It turns out my constancy optimization didn't work at all. I fixed it here. However, there's still room for improvement. If the first argument is a variable, the current optimization won't do anything. But it's possible to implement an optimization for this case. Specifically, by exploiting the associativity property and rebuilding the tree. However, this optimization requires the programmer to somehow indicate whether a pair of operators is associative. This increases the mathematical requirements for users of the package.

expression: "(.0+1+2+3+4+5+6+7+8+9+10)/2"
... 
BenchmarkСalc/expr-cls-12 	442813360	 2.704 ns/op	 0 B/op	 0 allocs/op
BenchmarkСalc/expr-12 	18976196	 63.28 ns/op	 56 B/op	 3 allocs/op
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet

AltStyle によって変換されたページ (->オリジナル) /