-
Notifications
You must be signed in to change notification settings - Fork 39
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Alternative method for single returns #359
Comments
Doing that would mean you can no longer implement the method using a lambda (it's no longer a @FunctionalInterface
public interface ExportFunction {
Value[] apply(Value... args) throws ChicoryException;
static ExportFunction of(SimpleExportFunction function) {
return args -> new Value[]{function.apply(args)};
}
@FunctionalInterface
interface SimpleExportFunction {
Value apply(Value... args) throws ChicoryException;
}
} But the real issue is that this interface is both inefficient and inconvenient. It's bad for performance because we box everything (AOT doesn't need boxing for arguments or single return), and it's annoying to implement because we have to adapt the arguments and return types. We really need a low level interface based on @ExportFunction(args = {I32, I64, I32}, returns = {I32})
public static int myFunction(int a, long b, int c) {
return (int) (a + b + c);
} The JMH factorial benchmark shows a ~180x improvement for AOT vs interpreter on (As I write this, I'm realizing that it would be helpful for the benchmark to have a Java implementation of the factorial function as a baseline for comparison.) |
Thanks for the suggestions on fixing the broader problem. Initially I was thinking about addressing this as an API boundary problem and wasn't thinking so much about performance, but if we can create both a performant and ergonomic interface that works externally and internally then that's probably the way to go. |
My example there is more appropriate for a host function, as the implementation of an export function will come from the engine. For export functions, one option is for the user to provide a custom interface: public interface Factorial {
int execute(int n);
} Or use any appropriately typed existing interface such as For the interpreter, these interfaces could be implemented using We could also support interfaces with multiple methods, which could make it easier to implement larger APIs. These could be implemented using JDK proxies or code generation. I extended the factorial JMH benchmark with a baseline and found that AOT has identical performance to Java on |
Do we have an alternative to the usage of I might be missing something, but, maybe generating the code directly for the expected use cases is an option? All in all, I'm curious to see a strawman to better understand the invariants here! |
Do you have a specific objection to the usage of Generating specific interfaces is combinatorial with I can put together a prototype and see if we can get to zero overhead for AOT exports. This will also improve AOT performance for |
So far, we have been very conscious about adding complexity to the interpreter codebase, and attempted to have 0 usage of reflection(even if modern). It might be time to re-evaluate this decision when it provides a huge performance improvement; but let me play the devil's advocate a little more to be sure that's the best thing we can do to this codebase. Please note that there are 0 reserves to use this technique in the AOT. Thinking about it a little more, would a generative approach only for the most common use cases(up to 4 params and 0/1 returns) with a fallback for the remaining cases be a viable option? |
The only complexity is in the adapter layer, which is used to create a nicer API for users. The interpreter could keep the same Up to 4 arity would be
I'd like to understand this more. Do you think it is somehow bad, or causes a problem for adoption or another goal? I don't understand why this would itself be a goal. |
Let's pick this up in the meeting tomorrow. Might be easier to talk about in person. I don't want to speak for Andrea but I think the primary concern is on keeping the interpreter code-base straightforward and if possible, push more complexity into the AOT layer. I think it should be achievable while satisfying Andrea's concerns. |
Sorry for the delay!
Great question! And probably it's a good time to challenge those, but let me recap the reasons that we have been using so far:
All in all I'm in favor of:
when appropriate, but, if the performance gain is huge (especially if backed by real-world examples), I'm happy to keep the conversation going and iterate over those points. |
If we exclude the performance discussion from this thread and we look only at the final user API I decided to dump my brain in a demo repository (since code is worth thousands of words). This is my opinionated take on how I'm envisioning the user interaction with Chicory: Happy to hear feedback! |
We should probably pick up this dicscussion in #441 |
I initially made the ExportFunction have a single
apply
method which represents the real life interface of wasm functions (so it supports multiple returns). However, 99% of the time I just pull the first element. How can we make this more ergonomic?One idea I've had is to just add another method that returns the first element:
Unsure what we'd call the method,
applyOne
orapplySingle
are my only ideas. But, perhaps there is a better way to solve this. Wanted to post this to get some suggestions.The text was updated successfully, but these errors were encountered: