-
Notifications
You must be signed in to change notification settings - Fork 243
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
wazeroir: avoid allocations per Operation #1202
Comments
another idea could be to serialize Operation as bytes and store it in |
Have you considered changing type Operation struct {
Kind OperationKind
// all possible field types
// ...
} Go doesn't have union so you can either use a representation that is very memory greedy and declare each parameters as struct fields (this is the approach used in https://pkg.go.dev/go.jpap.org/zydis#DecodedInstruction), for example: type Operation struct {
Kind OperationKind
Label struct { // fields from OperationLabel
Label Label
}
Br struct { // fields from OperationBr
Target Label
}
} To reduce the memory footprint you can emulate the union by having a single struct field using enough memory to hold the largest argument set. I think this is somewhat similar to what you're suggesting with |
actually the interpreter uses that quasi union type effectively https://github.com/tetratelabs/wazero/blob/main/internal/engine/interpreter/interpreter.go#L207-L209 so we could just lift it into wazeroir package and reuse it in compiler package as well! |
Ah nice! Yes that sounds like a good strategy. There are two slices in that type that may cause a lot of heap allocations as well if the backing array end up escaping to the heap (tho it's much more nuanced than with interfaces). Here are a few approaches I've used for this:
|
@evacchi wanna take a shot? 😎 |
Alrighty 😎 |
so if I understand this correctly, this is kind of a large refactoring, so let me make sure I have understood correctly:
so the proposed solution is:
then, because Is this the idea? In this case, I think a (hopefully practical) way to proceed is to do this in phases, i.e. move one opcode at a time and make sure all tests still pass. Maybe I am overthinking it, let me know 😬 |
@evacchi you gave a very good description of what was my understanding of the work to do 😊 |
Yeah, #1296 is a good starting point. In the subsequent PRs, you should be able to refactor the interpreter/compiler engine so that it will do streaming rather than bulk production of the union type. |
Currently, we result in allocating an wazeroir.Operation object each time without caching or whatever. This is one of the bottlenecks during compilation (!= execution or instantiation) besides #1060:
memory profiling result on the compilation of GOOS=js binary
The root cause of this is that we emit a concrete type for
Operation
interface which is a translation for Wasm instruction.For example, each
global.get 1
allocateswazeroir.OperationGlobalGet{index: 1}
:wazero/internal/wazeroir/compiler.go
Lines 922 to 925 in 25493fe
And we store them in the slice of Operation interfaces
wazero/internal/wazeroir/compiler.go
Lines 221 to 222 in 25493fe
One possible solution to this is to make it streaming given that our current compiler/interpreter does stream compilation. Instead of translating the Wasm instructions all at once, just make wazeroir compiler stateful and use the fixed-length bytes array (the length could be the maximum size of concrete operation struct type which is statically known) to store the IR info from compilers/interpreters.
There might be an easier and better way to optimize this, so I'm open to discussion!
The text was updated successfully, but these errors were encountered: