-
-
Notifications
You must be signed in to change notification settings - Fork 463
Architecture Refactoring: My Fork, Flexibility, and Performance Gains #834
-
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
Beta Was this translation helpful? Give feedback.
All reactions
Replies: 3 comments 8 replies
-
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).
Beta Was this translation helpful? Give feedback.
All reactions
-
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.
Beta Was this translation helpful? Give feedback.
All reactions
-
Cool! I'd like to take a look. Maybe we can bring some of this performance to the Expr project.
Beta Was this translation helpful? Give feedback.
All reactions
-
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.
Beta Was this translation helpful? Give feedback.
All reactions
-
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.
Beta Was this translation helpful? Give feedback.
All reactions
-
❤️ 1
-
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 }
}Beta Was this translation helpful? Give feedback.
All reactions
-
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.
Beta Was this translation helpful? Give feedback.
All reactions
-
Nice hack!
var srcC srcT + pSrc := &srcC ... return func(src srcT) outT { + srcC = src return fn() }, nilI'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.
- Pass through an interface value. (This will slow down the calculation, since there will be one interface cast during the calculation.)
- 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.)
- Pass through an unsafe pointer. (In my opinion, this is the best option; the only downside is the use of an unsafe pointer itself.)
Beta Was this translation helpful? Give feedback.
All reactions
-
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
Beta Was this translation helpful? Give feedback.
All reactions
-
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!
Beta Was this translation helpful? Give feedback.
All reactions
-
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
Beta Was this translation helpful? Give feedback.