[Vm-dev] [NB] NativeBoost meets JIT
Eliot Miranda
eliot.miranda at gmail.com
Fri Sep 21 17:50:59 UTC 2012
Hi Igor,
great news!
On Fri, Sep 21, 2012 at 7:59 AM, Igor Stasenko <siguctua at gmail.com> wrote:
>
> Hello there,
>
> so, we're entered a new area, where native code, generated from image
> side can be run directly by JIT.
> This feature was one of the first things which i wanted to try, once
> Eliot released Cog :)
>
> The way how we do that, is when VM decides to JIT a specific method,
> we copying the native code (from method trailer)
> directly into the method's code.
> All you need to do is to use special primitive for that 220 (
> #primitiveVoltage)
>
> So, a first question, which we wanted to be answered is how faster to
> run native code by JIT,
> comparing to running native code via NativeBoost primitive , which is
> #primitiveNativeCall..
>
> For here are methods, which just answer 42:
>
> This one using #primitiveNativeCall
>
> nbFoo2
> <primitive: #primitiveNativeCall module: #NativeBoostPlugin error:
> errorCode>
>
> ^ NBNativeCodeGen methodAssembly: [:gen :proxy :asm |
> asm noStackFrame.
> asm
> mov: (42 << 1) + 1 to: asm EAX;
> ret.
> ]
>
> And this one uses JIT:
>
> nbFoo
> <primitive: 220 error: errorCode>
>
> [ errorCode = ErrRunningViaInterpreter ] whileTrue: [ ^ self
> nbFoo ].
>
> ^ NBNativeCodeGen jitMethodAssembly: [:gen :proxy :asm |
> asm noStackFrame.
> asm
> mov: (42 << 1) + 1 to: asm EDX;
> ret: 4 asUImm.
> ]
>
> And this one is code which JIT can do:
>
> nbFoo42
> ^ 42
>
> So, here the numbers:
>
> Time to run via #primitiveNativeCall :
>
> [100000000 timesRepeat: [ MyClass nbFoo2 ] ] timeToRun
> 6995
>
> Time to run via JIT:
>
> [100000000 timesRepeat: [ MyClass nbFoo ] ] timeToRun
> 897
>
> Time to run JITed method:
>
> [100000000 timesRepeat: [ MyClass nbFoo42 ] ] timeToRun
> 899
>
> so, as you can see, the JITed method and our custom generated code is
> on par (which is logical ;).
>
> Time to run an empty loop:
>
> [100000000 timesRepeat: [ ] ] timeToRun 679
>
>
> So, here the result, if we extract the loop overhead, we can see the
> difference in
> calling our native code when it uses JIT vs using #primitiveNativeCall :
>
> (6995 - 679 ) / (897- 679) asFloat 28.972477064220183
>
> 28 times faster!!!!
>
> So, with this new feature, we now can make our generated code to run
> with unmatched speed,
> without overhead related to #primitiveNativeCall.
> This is especially useful for implementing primives which involving
> heavy numeric crunching.
>
> I would release this code to public, but there's one little
> discrepancy i need to deal with first:
>
> (one little problem, which i hope Eliot can help to solve)
>
> it looks like primitivePerform: never enters the JIT mode, but always
> executing the method via interpreter.
>
I'll take a look. This is all very detailed so I'll need a little time.
This is why you see this code:
> [ errorCode = ErrRunningViaInterpreter ] whileTrue: [ ^ self
> nbFoo ].
>
> because if i do it inside of NBNativeCodeGen>>jitMethodAssembly:,
> which checks for same error and retries the send using perform
> primitive, it never enters the JIT mode,
> resulting in endless loop :(
>
> This is despite the fact that method is JITed, because we enforce the
> JITing of that method during error handling:
>
> lastError = ErrRunningViaInterpreter ifTrue: [
> "a method contains native code, but executed by
> interpreter "
> method forceJIT ifFalse: [ self error: 'Failed to JIT the
> compiled
> method. Try reducing it''s size ' ].
> ^ self retrySend: aContext
> ].
>
> The #forceJit is the primitive which i implemented like following:
>
> primitiveForceJIT
>
> <export: true >
>
> | val result |
>
> val := self stackTop.
>
> (self isIntegerObject: val) ifTrue: [ ^ self primitiveFail ].
> (self isCompiledMethod: val) ifFalse: [ ^ self primitiveFail ].
>
> (self methodHasCogMethod: val) ifFalse: [
> cogit cog: val selector: objectMemory nilObject ].
>
> result := (self methodHasCogMethod: val ) ifTrue: [ objectMemory
> trueObject ] ifFalse: [ objectMemory falseObject ].
>
> ^ self pop: 1 thenPush: result.
>
> As you can see from its usage, if VM, for some reason will fail to jit
> the method, the primitive will answer false,
> and we will stop with an error.. Which apparently never happens.
> Still, a #primitivePerform seems like ignoring that the method
> contains machine code an always runs it interpreted :(
>
> I do not like the idea, that users will be forced to manually put such
> loops in every method they will write..
> any ideas/suggestions how to overcome that?
>
Yes. The JIT should be told that methods that have NB code should be
jitted. But right now I don't understand enough of how NB code is
generated and methods marked that they have NB code etc to know exactly how
to do this. I need to play around a bit.
--
best,
Eliot
-------------- next part --------------
An HTML attachment was scrubbed...
URL: http://lists.squeakfoundation.org/pipermail/vm-dev/attachments/20120921/6db5fa8e/attachment-0001.htm
More information about the Vm-dev
mailing list