imglib2-neon project for runtime bytecode transformation

classic Classic list List threaded Threaded
11 messages Options
Reply | Threaded
Open this post in threaded view
|

imglib2-neon project for runtime bytecode transformation

Tobias Pietzsch
Hi guys,

As a weekend project I have started to look into bytecode modification using the wonderful ASM library (http://asm.ow2.org).
I have cleaned up what I have played with and put it on github https://github.com/tpietzsch/neon.

It tackles a long-standing imglib obstacle, namely megamorphic call-sites in certain methods, where the JIT fails to recognise that the runtime target of the polymorphic changes between calls to a method but doesn’t change in the hot inner loop of the method during a single call. I have been talking about ideas to address this for quite some time, most recently here https://github.com/imglib/imglib/issues/71#issuecomment-51227237. Now I went ahead and actually tried to do something about it.

I have applied it to an example in imglib, which is described below. But for anyone not familiar with this particular issue (which is everyone except Christian probably) there
is an illustrative example with explanations in the README on github https://github.com/tpietzsch/neon/blob/master/README.md.
This does not involve imglib at all and is a clear illustration of the problem (and my solution).

I’m quite happy with how it turned out so far. It certainly has to be applied with care, but I think this can be potentially huge for imglib2. It might open up new possibilities that we have shied away from because of performance reasons, such as internal iteration for IterableIntervals.

Curtis, Johannes and Christian, I would also be interested what you think of this as a potential tool for imagej-ops.
I think it is orthogonal to what you do with compile-time code generation currently and therefore might complement it nicely.

I hope you have a look and tell me what you think.
I would be especially interested in whether you can think of optimization idioms besides the @Instantiate @ByTypeOf that is implemented right now.
It would be cool if we discuss this in the upcoming imglib hackathon.

Okay, everybody except Christian might as well stop reading now.

all the best,
Tobias





PS: the imglib stuff...

For the imglib issue https://github.com/imglib/imglib/issues/71, we played with ways of iterating pixels which can be optimized for certain subintervals of larger images.
The optimizations work out nicely when done on their own, but everything really breaks down when a single method is used with differnent Cursor incarnations.
This is actually already a potential problem in standard imglib, when Cursors from different Img types are uses in a single method. But adding the new optimized versions
only made it more probable that the problem actually occurs.

Here is numbers from a recent test, at a stage where 4 different kinds of cursors are in play:

normal cursor | array img
walk through a subinterval
        | Unoptimized | Optimized | Speedup Time | Speedup % |
Best | 566 | 371 | 195ms   | 34.4%   |

localizing cursor | array img
walk through a subinterval
        | Unoptimized | Optimized | Speedup Time | Speedup % |
Best | 907 | 584 | 323ms   | 35.6%   |

normal cursor | planar img
walk through a subinterval
        | Unoptimized | Optimized | Speedup Time | Speedup % |
Best | 562 | 373 | 189ms   | 33.6%   |

localizing cursor | planar img
walk through a subinterval
        | Unoptimized | Optimized | Speedup Time | Speedup % |
Best | 928 | 611 | 317ms   | 34.1%   |


With the neon java agent this improves to:

normal cursor | array img
walk through a subinterval
        | Unoptimized | Optimized | Speedup Time | Speedup % |
Best | 153 | 8 | 145ms   | 94.7%   |

localizing cursor | array img
walk through a subinterval
        | Unoptimized | Optimized | Speedup Time | Speedup % |
Best | 235 | 200 | 35ms   | 14.8%   |

normal cursor | planar img
walk through a subinterval
        | Unoptimized | Optimized | Speedup Time | Speedup % |
Best | 128 | 8 | 120ms   | 93.7%   |

localizing cursor | planar img
walk through a subinterval
        | Unoptimized | Optimized | Speedup Time | Speedup % |
Best | 217 | 208 | 9ms   | 4.1%   |


A speedup of factor ~4 to ~40 can be observed.
These two runs were made with exactly the same code, but for the second one, the program was run with the option
   java -javaagent:/path/to/neon-1.0.0-SNAPSHOT.jar …

I just pushed the example to https://github.com/imglib/imglib/commit/a9b70d923e9a84c4055acae96f71d05ca4a26344


_______________________________________________
ImageJ-devel mailing list
[hidden email]
http://imagej.net/mailman/listinfo/imagej-devel

signature.asc (465 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: imglib2-neon project for runtime bytecode transformation

Preibisch, Stephan
Hi Tobi,

this does look pretty impressive indeed. As it is modified on runtime, it should even work with new Types and Accessibles that are not part of ImgLib2 but some external program that is based on ImgLib2 and adds for example its own types, or?

Did you test if this scales well with larger number of instances? For ImgLib2 it would be something like |accessibles| x |types|, right? In many cases it would not use a lot of the possibilities, but for something like KNIME or ImageJ2 this might actually happen.

Again, looks awesome! And if it turns out to be working in all cases, I agree, this could be a huge game changer.

Cheers,
Steffi
---

Dr. Stephan Preibisch
HFSP Fellow
Robert H. Singer / Eugene Myers lab

Albert Einstein College of Medicine / HHMI Janelia Farm / MPI-CBG


On Sep 23, 2014, at 19:02 , Tobias Pietzsch <[hidden email]> wrote:

Hi guys,

As a weekend project I have started to look into bytecode modification using the wonderful ASM library (http://asm.ow2.org).
I have cleaned up what I have played with and put it on github https://github.com/tpietzsch/neon.

It tackles a long-standing imglib obstacle, namely megamorphic call-sites in certain methods, where the JIT fails to recognise that the runtime target of the polymorphic changes between calls to a method but doesn’t change in the hot inner loop of the method during a single call. I have been talking about ideas to address this for quite some time, most recently here https://github.com/imglib/imglib/issues/71#issuecomment-51227237. Now I went ahead and actually tried to do something about it.

I have applied it to an example in imglib, which is described below. But for anyone not familiar with this particular issue (which is everyone except Christian probably) there
is an illustrative example with explanations in the README on github https://github.com/tpietzsch/neon/blob/master/README.md.
This does not involve imglib at all and is a clear illustration of the problem (and my solution).

I’m quite happy with how it turned out so far. It certainly has to be applied with care, but I think this can be potentially huge for imglib2. It might open up new possibilities that we have shied away from because of performance reasons, such as internal iteration for IterableIntervals.

Curtis, Johannes and Christian, I would also be interested what you think of this as a potential tool for imagej-ops.
I think it is orthogonal to what you do with compile-time code generation currently and therefore might complement it nicely.

I hope you have a look and tell me what you think.
I would be especially interested in whether you can think of optimization idioms besides the @Instantiate @ByTypeOf that is implemented right now.
It would be cool if we discuss this in the upcoming imglib hackathon.

Okay, everybody except Christian might as well stop reading now.

all the best,
Tobias





PS: the imglib stuff...

For the imglib issue https://github.com/imglib/imglib/issues/71, we played with ways of iterating pixels which can be optimized for certain subintervals of larger images.
The optimizations work out nicely when done on their own, but everything really breaks down when a single method is used with differnent Cursor incarnations.
This is actually already a potential problem in standard imglib, when Cursors from different Img types are uses in a single method. But adding the new optimized versions
only made it more probable that the problem actually occurs.

Here is numbers from a recent test, at a stage where 4 different kinds of cursors are in play:

normal cursor | array img
walk through a subinterval
| Unoptimized | Optimized | Speedup Time | Speedup % |
Best | 566 | 371 | 195ms    | 34.4%    |

localizing cursor | array img
walk through a subinterval
| Unoptimized | Optimized | Speedup Time | Speedup % |
Best | 907 | 584 | 323ms    | 35.6%    |

normal cursor | planar img
walk through a subinterval
| Unoptimized | Optimized | Speedup Time | Speedup % |
Best | 562 | 373 | 189ms    | 33.6%    |

localizing cursor | planar img
walk through a subinterval
| Unoptimized | Optimized | Speedup Time | Speedup % |
Best | 928 | 611 | 317ms    | 34.1%    |


With the neon java agent this improves to:

normal cursor | array img
walk through a subinterval
| Unoptimized | Optimized | Speedup Time | Speedup % |
Best | 153 | 8 | 145ms    | 94.7%    |

localizing cursor | array img
walk through a subinterval
| Unoptimized | Optimized | Speedup Time | Speedup % |
Best | 235 | 200 | 35ms    | 14.8%    |

normal cursor | planar img
walk through a subinterval
| Unoptimized | Optimized | Speedup Time | Speedup % |
Best | 128 | 8 | 120ms    | 93.7%    |

localizing cursor | planar img
walk through a subinterval
| Unoptimized | Optimized | Speedup Time | Speedup % |
Best | 217 | 208 | 9ms    | 4.1%    |


A speedup of factor ~4 to ~40 can be observed.
These two runs were made with exactly the same code, but for the second one, the program was run with the option
  java -javaagent:/path/to/neon-1.0.0-SNAPSHOT.jar …

I just pushed the example to https://github.com/imglib/imglib/commit/a9b70d923e9a84c4055acae96f71d05ca4a26344



_______________________________________________
ImageJ-devel mailing list
[hidden email]
http://imagej.net/mailman/listinfo/imagej-devel
Reply | Threaded
Open this post in threaded view
|

Re: [fiji-devel] imglib2-neon project for runtime bytecode transformation

Albert Cardona
In reply to this post by Tobias Pietzsch
Hi Tobias,

Just to add to the cheer. It is not everyday that one sees 97% improvements on performance. I understand this is preliminary. Looking forward to seeing it applied in real world code.

Albert


> On Sep 23, 2014, at 7:02 PM, Tobias Pietzsch <[hidden email]> wrote:
>
> Hi guys,
>
> As a weekend project I have started to look into bytecode modification using the wonderful ASM library (http://asm.ow2.org).
> I have cleaned up what I have played with and put it on github https://github.com/tpietzsch/neon.
>
> It tackles a long-standing imglib obstacle, namely megamorphic call-sites in certain methods, where the JIT fails to recognise that the runtime target of the polymorphic changes between calls to a method but doesn’t change in the hot inner loop of the method during a single call. I have been talking about ideas to address this for quite some time, most recently here https://github.com/imglib/imglib/issues/71#issuecomment-51227237. Now I went ahead and actually tried to do something about it.
>
> I have applied it to an example in imglib, which is described below. But for anyone not familiar with this particular issue (which is everyone except Christian probably) there
> is an illustrative example with explanations in the README on github https://github.com/tpietzsch/neon/blob/master/README.md.
> This does not involve imglib at all and is a clear illustration of the problem (and my solution).
>
> I’m quite happy with how it turned out so far. It certainly has to be applied with care, but I think this can be potentially huge for imglib2. It might open up new possibilities that we have shied away from because of performance reasons, such as internal iteration for IterableIntervals.
>
> Curtis, Johannes and Christian, I would also be interested what you think of this as a potential tool for imagej-ops.
> I think it is orthogonal to what you do with compile-time code generation currently and therefore might complement it nicely.
>
> I hope you have a look and tell me what you think.
> I would be especially interested in whether you can think of optimization idioms besides the @Instantiate @ByTypeOf that is implemented right now.
> It would be cool if we discuss this in the upcoming imglib hackathon.
>
> Okay, everybody except Christian might as well stop reading now.
>
> all the best,
> Tobias
>
>
>
>
>
> PS: the imglib stuff...
>
> For the imglib issue https://github.com/imglib/imglib/issues/71, we played with ways of iterating pixels which can be optimized for certain subintervals of larger images.
> The optimizations work out nicely when done on their own, but everything really breaks down when a single method is used with differnent Cursor incarnations.
> This is actually already a potential problem in standard imglib, when Cursors from different Img types are uses in a single method. But adding the new optimized versions
> only made it more probable that the problem actually occurs.
>
> Here is numbers from a recent test, at a stage where 4 different kinds of cursors are in play:
>
> normal cursor | array img
> walk through a subinterval
>    | Unoptimized    | Optimized    | Speedup Time    | Speedup %    |
> Best    |    566    |    371    | 195ms       | 34.4%       |
>
> localizing cursor | array img
> walk through a subinterval
>    | Unoptimized    | Optimized    | Speedup Time    | Speedup %    |
> Best    |    907    |    584    | 323ms       | 35.6%       |
>
> normal cursor | planar img
> walk through a subinterval
>    | Unoptimized    | Optimized    | Speedup Time    | Speedup %    |
> Best    |    562    |    373    | 189ms       | 33.6%       |
>
> localizing cursor | planar img
> walk through a subinterval
>    | Unoptimized    | Optimized    | Speedup Time    | Speedup %    |
> Best    |    928    |    611    | 317ms       | 34.1%       |
>
>
> With the neon java agent this improves to:
>
> normal cursor | array img
> walk through a subinterval
>    | Unoptimized    | Optimized    | Speedup Time    | Speedup %    |
> Best    |    153    |    8    | 145ms       | 94.7%       |
>
> localizing cursor | array img
> walk through a subinterval
>    | Unoptimized    | Optimized    | Speedup Time    | Speedup %    |
> Best    |    235    |    200    | 35ms       | 14.8%       |
>
> normal cursor | planar img
> walk through a subinterval
>    | Unoptimized    | Optimized    | Speedup Time    | Speedup %    |
> Best    |    128    |    8    | 120ms       | 93.7%       |
>
> localizing cursor | planar img
> walk through a subinterval
>    | Unoptimized    | Optimized    | Speedup Time    | Speedup %    |
> Best    |    217    |    208    | 9ms       | 4.1%       |
>
>
> A speedup of factor ~4 to ~40 can be observed.
> These two runs were made with exactly the same code, but for the second one, the program was run with the option
>   java -javaagent:/path/to/neon-1.0.0-SNAPSHOT.jar …
>
> I just pushed the example to https://github.com/imglib/imglib/commit/a9b70d923e9a84c4055acae96f71d05ca4a26344
>

_______________________________________________
ImageJ-devel mailing list
[hidden email]
http://imagej.net/mailman/listinfo/imagej-devel
Reply | Threaded
Open this post in threaded view
|

Re: [fiji-devel] imglib2-neon project for runtime bytecode transformation

Tobias Pietzsch
In reply to this post by Preibisch, Stephan
Hi Steffi,

On 24 Sep 2014, at 02:12, Preibisch, Stephan <[hidden email]> wrote:

Hi Tobi,

this does look pretty impressive indeed. As it is modified on runtime, it should even work with new Types and Accessibles that are not part of ImgLib2 but some external program that is based on ImgLib2 and adds for example its own types, or?

Yes, absolutely.


Did you test if this scales well with larger number of instances? For ImgLib2 it would be something like |accessibles| x |types|, right? In many cases it would not use a lot of the possibilities, but for something like KNIME or ImageJ2 this might actually happen.

I did not do any larger tests yet.

Whether it would be |accessibles| x |types| for imglib depends on how it is used.
Lets assume that you have a function
  <T extends Type<T>> T getMax( Cursor<T> c, T type )
Then it depends on how you annotate it. For
  @Instantiate <T extends Type<T>> T getMax( @ByTypeOf Cursor<T> c, T type )
it would be instantiate it for ArrayCursor and ArrayLocalizingCursor, but ArrayCursor<IntType> and ArrayCursor<FloatType> would share the same instance.
For
  @Instantiate <T extends Type<T>> T getMax( Cursor<T> c, @ByTypeOf T type )
it would be instantiated for IntType and FloatType, but ArrayCursor<IntType> and ArrayLocalizingCursor<IntType> would share the same instance.
For
  @Instantiate <T extends Type<T>> T getMax( @ByTypeOf Cursor<T> c, @ByTypeOf T type )
you would have the full |cursor| x |type| space.

I don’t see this becoming a big problem at the moment.
We should anyway not indiscriminately apply it everywhere, only where we can really show a performance increase.

I see it as more of an issue if we add more of these instantiation idioms, for example for specializing classes. Then the number of instances might multiply through chains of such instantiations. Imagine the above with Cursor classes being generated depending on Type and number of dimensions. And that with Types being generated depending on the underlying Access.

The nice thing is that you only pay for what you actually use.
In any case, it is easy to cap the number of instances that are generated at a fixed number, and use the same instance for every newly occurring type after that.


Again, looks awesome! And if it turns out to be working in all cases, I agree, this could be a huge game changer.

Cool, thanks!
Tobias


Cheers,
Steffi
---

Dr. Stephan Preibisch
HFSP Fellow
Robert H. Singer / Eugene Myers lab

Albert Einstein College of Medicine / HHMI Janelia Farm / MPI-CBG


On Sep 23, 2014, at 19:02 , Tobias Pietzsch <[hidden email]> wrote:

Hi guys,

As a weekend project I have started to look into bytecode modification using the wonderful ASM library (http://asm.ow2.org).
I have cleaned up what I have played with and put it on github https://github.com/tpietzsch/neon.

It tackles a long-standing imglib obstacle, namely megamorphic call-sites in certain methods, where the JIT fails to recognise that the runtime target of the polymorphic changes between calls to a method but doesn’t change in the hot inner loop of the method during a single call. I have been talking about ideas to address this for quite some time, most recently here https://github.com/imglib/imglib/issues/71#issuecomment-51227237. Now I went ahead and actually tried to do something about it.

I have applied it to an example in imglib, which is described below. But for anyone not familiar with this particular issue (which is everyone except Christian probably) there
is an illustrative example with explanations in the README on github https://github.com/tpietzsch/neon/blob/master/README.md.
This does not involve imglib at all and is a clear illustration of the problem (and my solution).

I’m quite happy with how it turned out so far. It certainly has to be applied with care, but I think this can be potentially huge for imglib2. It might open up new possibilities that we have shied away from because of performance reasons, such as internal iteration for IterableIntervals.

Curtis, Johannes and Christian, I would also be interested what you think of this as a potential tool for imagej-ops.
I think it is orthogonal to what you do with compile-time code generation currently and therefore might complement it nicely.

I hope you have a look and tell me what you think.
I would be especially interested in whether you can think of optimization idioms besides the @Instantiate @ByTypeOf that is implemented right now.
It would be cool if we discuss this in the upcoming imglib hackathon.

Okay, everybody except Christian might as well stop reading now.

all the best,
Tobias





PS: the imglib stuff...

For the imglib issue https://github.com/imglib/imglib/issues/71, we played with ways of iterating pixels which can be optimized for certain subintervals of larger images.
The optimizations work out nicely when done on their own, but everything really breaks down when a single method is used with differnent Cursor incarnations.
This is actually already a potential problem in standard imglib, when Cursors from different Img types are uses in a single method. But adding the new optimized versions
only made it more probable that the problem actually occurs.

Here is numbers from a recent test, at a stage where 4 different kinds of cursors are in play:

normal cursor | array img
walk through a subinterval
| Unoptimized | Optimized | Speedup Time | Speedup % |
Best | 566 | 371 | 195ms    | 34.4%    |

localizing cursor | array img
walk through a subinterval
| Unoptimized | Optimized | Speedup Time | Speedup % |
Best | 907 | 584 | 323ms    | 35.6%    |

normal cursor | planar img
walk through a subinterval
| Unoptimized | Optimized | Speedup Time | Speedup % |
Best | 562 | 373 | 189ms    | 33.6%    |

localizing cursor | planar img
walk through a subinterval
| Unoptimized | Optimized | Speedup Time | Speedup % |
Best | 928 | 611 | 317ms    | 34.1%    |


With the neon java agent this improves to:

normal cursor | array img
walk through a subinterval
| Unoptimized | Optimized | Speedup Time | Speedup % |
Best | 153 | 8 | 145ms    | 94.7%    |

localizing cursor | array img
walk through a subinterval
| Unoptimized | Optimized | Speedup Time | Speedup % |
Best | 235 | 200 | 35ms    | 14.8%    |

normal cursor | planar img
walk through a subinterval
| Unoptimized | Optimized | Speedup Time | Speedup % |
Best | 128 | 8 | 120ms    | 93.7%    |

localizing cursor | planar img
walk through a subinterval
| Unoptimized | Optimized | Speedup Time | Speedup % |
Best | 217 | 208 | 9ms    | 4.1%    |


A speedup of factor ~4 to ~40 can be observed.
These two runs were made with exactly the same code, but for the second one, the program was run with the option
  java -javaagent:/path/to/neon-1.0.0-SNAPSHOT.jar …

I just pushed the example to https://github.com/imglib/imglib/commit/a9b70d923e9a84c4055acae96f71d05ca4a26344



--
--
Please avoid top-posting, and please make sure to reply-to-all!
 
Mailing list web interface: http://groups.google.com/group/fiji-devel

---
You received this message because you are subscribed to the Google Groups "Fiji-devel" group.
To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
For more options, visit https://groups.google.com/d/optout.


_______________________________________________
ImageJ-devel mailing list
[hidden email]
http://imagej.net/mailman/listinfo/imagej-devel

signature.asc (465 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: imglib2-neon project for runtime bytecode transformation

Christian Dietz
In reply to this post by Tobias Pietzsch
Hi Tobias,

I know we talked a lot about these issues and it's great that you found
some time to tackle them. So, thank you very much for your effort. I
really see the potential. ImageJ-Ops definitively can benefit from this
approach indirectly (ImgLib2 using ASM) but also directly (using ASM n
ImageJ-Ops). Unfortunately I will not take part at the next hackathon in
Madison, but I can easily join you with Skype whenever you want to
discuss ASM & Ops.

Anyway, I'm a bit puzzled concerning the benchmark on SubIntervals. Even
the "unoptimized" iteration has a dramatic improvement in run-time using
the agent. Is this due to the fact, the ImgLib2 already suffered from
the megamorphic call-sites? Or do I misunderstand something?

Again, thank you for the effort! This seems to be exactly what we need
on the ImgLib2 side.

Christian






On 24.09.2014 01:02, Tobias Pietzsch wrote:

> Hi guys,
>
> As a weekend project I have started to look into bytecode modification using the wonderful ASM library (http://asm.ow2.org).
> I have cleaned up what I have played with and put it on github https://github.com/tpietzsch/neon.
>
> It tackles a long-standing imglib obstacle, namely megamorphic call-sites in certain methods, where the JIT fails to recognise that the runtime target of the polymorphic changes between calls to a method but doesn’t change in the hot inner loop of the method during a single call. I have been talking about ideas to address this for quite some time, most recently here https://github.com/imglib/imglib/issues/71#issuecomment-51227237. Now I went ahead and actually tried to do something about it.
>
> I have applied it to an example in imglib, which is described below. But for anyone not familiar with this particular issue (which is everyone except Christian probably) there
> is an illustrative example with explanations in the README on github https://github.com/tpietzsch/neon/blob/master/README.md.
> This does not involve imglib at all and is a clear illustration of the problem (and my solution).
>
> I’m quite happy with how it turned out so far. It certainly has to be applied with care, but I think this can be potentially huge for imglib2. It might open up new possibilities that we have shied away from because of performance reasons, such as internal iteration for IterableIntervals.
>
> Curtis, Johannes and Christian, I would also be interested what you think of this as a potential tool for imagej-ops.
> I think it is orthogonal to what you do with compile-time code generation currently and therefore might complement it nicely.
>
> I hope you have a look and tell me what you think.
> I would be especially interested in whether you can think of optimization idioms besides the @Instantiate @ByTypeOf that is implemented right now.
> It would be cool if we discuss this in the upcoming imglib hackathon.
>
> Okay, everybody except Christian might as well stop reading now.
>
> all the best,
> Tobias
>
>
>
>
>
> PS: the imglib stuff...
>
> For the imglib issue https://github.com/imglib/imglib/issues/71, we played with ways of iterating pixels which can be optimized for certain subintervals of larger images.
> The optimizations work out nicely when done on their own, but everything really breaks down when a single method is used with differnent Cursor incarnations.
> This is actually already a potential problem in standard imglib, when Cursors from different Img types are uses in a single method. But adding the new optimized versions
> only made it more probable that the problem actually occurs.
>
> Here is numbers from a recent test, at a stage where 4 different kinds of cursors are in play:
>
> normal cursor | array img
> walk through a subinterval
> | Unoptimized | Optimized | Speedup Time | Speedup % |
> Best | 566 | 371 | 195ms   | 34.4%   |
>
> localizing cursor | array img
> walk through a subinterval
> | Unoptimized | Optimized | Speedup Time | Speedup % |
> Best | 907 | 584 | 323ms   | 35.6%   |
>
> normal cursor | planar img
> walk through a subinterval
> | Unoptimized | Optimized | Speedup Time | Speedup % |
> Best | 562 | 373 | 189ms   | 33.6%   |
>
> localizing cursor | planar img
> walk through a subinterval
> | Unoptimized | Optimized | Speedup Time | Speedup % |
> Best | 928 | 611 | 317ms   | 34.1%   |
>
>
> With the neon java agent this improves to:
>
> normal cursor | array img
> walk through a subinterval
> | Unoptimized | Optimized | Speedup Time | Speedup % |
> Best | 153 | 8 | 145ms   | 94.7%   |
>
> localizing cursor | array img
> walk through a subinterval
> | Unoptimized | Optimized | Speedup Time | Speedup % |
> Best | 235 | 200 | 35ms   | 14.8%   |
>
> normal cursor | planar img
> walk through a subinterval
> | Unoptimized | Optimized | Speedup Time | Speedup % |
> Best | 128 | 8 | 120ms   | 93.7%   |
>
> localizing cursor | planar img
> walk through a subinterval
> | Unoptimized | Optimized | Speedup Time | Speedup % |
> Best | 217 | 208 | 9ms   | 4.1%   |
>
>
> A speedup of factor ~4 to ~40 can be observed.
> These two runs were made with exactly the same code, but for the second one, the program was run with the option
>     java -javaagent:/path/to/neon-1.0.0-SNAPSHOT.jar …
>
> I just pushed the example to https://github.com/imglib/imglib/commit/a9b70d923e9a84c4055acae96f71d05ca4a26344
>


_______________________________________________
ImageJ-devel mailing list
[hidden email]
http://imagej.net/mailman/listinfo/imagej-devel
Reply | Threaded
Open this post in threaded view
|

Re: imglib2-neon project for runtime bytecode transformation

Tobias Pietzsch
Hi Christian,

On 24 Sep 2014, at 11:47, Christian Dietz <[hidden email]> wrote:

> Hi Tobias,
>
> I know we talked a lot about these issues and it's great that you found some time to tackle them. So, thank you very much for your effort. I really see the potential. ImageJ-Ops definitively can benefit from this approach indirectly (ImgLib2 using ASM) but also directly (using ASM n ImageJ-Ops). Unfortunately I will not take part at the next hackathon in Madison, but I can easily join you with Skype whenever you want to discuss ASM & Ops.
>
> Anyway, I'm a bit puzzled concerning the benchmark on SubIntervals. Even the "unoptimized" iteration has a dramatic improvement in run-time using the agent. Is this due to the fact, the ImgLib2 already suffered from the megamorphic call-sites? Or do I misunderstand something?

Yes, exactly.
This is the last of several iterations through the whole benchmark, so the call in the loop has already been made megamorphic.
In the first iteration, you would see for the non-neon version the same performance for
>> normal cursor | array img

and then degrading from there.
So indeed, just the fact that several cursors have “gone through the code” makes the "normal cursor | array img | Optimized” go from 8ms runtime up to 371ms.
(And similar degradation for the other cursors.)

best regards,
Tobias

>
> Again, thank you for the effort! This seems to be exactly what we need on the ImgLib2 side.
>
> Christian
>
>
>
>
>
>
> On 24.09.2014 01:02, Tobias Pietzsch wrote:
>> Hi guys,
>>
>> As a weekend project I have started to look into bytecode modification using the wonderful ASM library (http://asm.ow2.org).
>> I have cleaned up what I have played with and put it on github https://github.com/tpietzsch/neon.
>>
>> It tackles a long-standing imglib obstacle, namely megamorphic call-sites in certain methods, where the JIT fails to recognise that the runtime target of the polymorphic changes between calls to a method but doesn’t change in the hot inner loop of the method during a single call. I have been talking about ideas to address this for quite some time, most recently here https://github.com/imglib/imglib/issues/71#issuecomment-51227237. Now I went ahead and actually tried to do something about it.
>>
>> I have applied it to an example in imglib, which is described below. But for anyone not familiar with this particular issue (which is everyone except Christian probably) there
>> is an illustrative example with explanations in the README on github https://github.com/tpietzsch/neon/blob/master/README.md.
>> This does not involve imglib at all and is a clear illustration of the problem (and my solution).
>>
>> I’m quite happy with how it turned out so far. It certainly has to be applied with care, but I think this can be potentially huge for imglib2. It might open up new possibilities that we have shied away from because of performance reasons, such as internal iteration for IterableIntervals.
>>
>> Curtis, Johannes and Christian, I would also be interested what you think of this as a potential tool for imagej-ops.
>> I think it is orthogonal to what you do with compile-time code generation currently and therefore might complement it nicely.
>>
>> I hope you have a look and tell me what you think.
>> I would be especially interested in whether you can think of optimization idioms besides the @Instantiate @ByTypeOf that is implemented right now.
>> It would be cool if we discuss this in the upcoming imglib hackathon.
>>
>> Okay, everybody except Christian might as well stop reading now.
>>
>> all the best,
>> Tobias
>>
>>
>>
>>
>>
>> PS: the imglib stuff...
>>
>> For the imglib issue https://github.com/imglib/imglib/issues/71, we played with ways of iterating pixels which can be optimized for certain subintervals of larger images.
>> The optimizations work out nicely when done on their own, but everything really breaks down when a single method is used with differnent Cursor incarnations.
>> This is actually already a potential problem in standard imglib, when Cursors from different Img types are uses in a single method. But adding the new optimized versions
>> only made it more probable that the problem actually occurs.
>>
>> Here is numbers from a recent test, at a stage where 4 different kinds of cursors are in play:
>>
>> normal cursor | array img
>> walk through a subinterval
>> | Unoptimized | Optimized | Speedup Time | Speedup % |
>> Best | 566 | 371 | 195ms   | 34.4%   |
>>
>> localizing cursor | array img
>> walk through a subinterval
>> | Unoptimized | Optimized | Speedup Time | Speedup % |
>> Best | 907 | 584 | 323ms   | 35.6%   |
>>
>> normal cursor | planar img
>> walk through a subinterval
>> | Unoptimized | Optimized | Speedup Time | Speedup % |
>> Best | 562 | 373 | 189ms   | 33.6%   |
>>
>> localizing cursor | planar img
>> walk through a subinterval
>> | Unoptimized | Optimized | Speedup Time | Speedup % |
>> Best | 928 | 611 | 317ms   | 34.1%   |
>>
>>
>> With the neon java agent this improves to:
>>
>> normal cursor | array img
>> walk through a subinterval
>> | Unoptimized | Optimized | Speedup Time | Speedup % |
>> Best | 153 | 8 | 145ms   | 94.7%   |
>>
>> localizing cursor | array img
>> walk through a subinterval
>> | Unoptimized | Optimized | Speedup Time | Speedup % |
>> Best | 235 | 200 | 35ms   | 14.8%   |
>>
>> normal cursor | planar img
>> walk through a subinterval
>> | Unoptimized | Optimized | Speedup Time | Speedup % |
>> Best | 128 | 8 | 120ms   | 93.7%   |
>>
>> localizing cursor | planar img
>> walk through a subinterval
>> | Unoptimized | Optimized | Speedup Time | Speedup % |
>> Best | 217 | 208 | 9ms   | 4.1%   |
>>
>>
>> A speedup of factor ~4 to ~40 can be observed.
>> These two runs were made with exactly the same code, but for the second one, the program was run with the option
>>    java -javaagent:/path/to/neon-1.0.0-SNAPSHOT.jar …
>>
>> I just pushed the example to https://github.com/imglib/imglib/commit/a9b70d923e9a84c4055acae96f71d05ca4a26344
>>
>

_______________________________________________
ImageJ-devel mailing list
[hidden email]
http://imagej.net/mailman/listinfo/imagej-devel

signature.asc (465 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: imglib2-neon project for runtime bytecode transformation

Johannes Schindelin
In reply to this post by Tobias Pietzsch
Hi Tobias,

On Wed, 24 Sep 2014, Tobias Pietzsch wrote:

> As a weekend project I have started to look into bytecode modification
> using the wonderful ASM library (http://asm.ow2.org).

It is great that you continue our conversation from the hackathon last
year in Madison:

        http://fiji.sc/2013-05-03_-_ImgLib2_Hackathon_in_Madison

I am very glad that you have returned to this work, with a promising
initial foray into a general solution.

I do have some questions and suggestions:

- Why use ASM over Javassist? At the hackathon, we used Javassist because
  it is easier to use, we have much better documentation (e.g.
  http://fiji.sc/Javassist) and we already ship it with Fiji.

- If the plan is to use it inside Fiji, why not use version 4.0 of ASM
  which is in Fiji already as a transitive dependency of JRuby? Requiring
  a newer version (5.0.2) is prone to cause problems...

- The most challenging requirement of any potential performance
  improvement is the separation of concerns: to truly be able to optimize
  ImgLib2 routines, the optimization has to be decoupled from the
  implementation because ImgLib2 is data type, storage and dimension
  independent and developers need to be able to provide more sophisticated
  optimizations for specific scenarios than automatic byte code
  manipulation can provide

- As usual, we'll want to carefully consider the issue of dependencies relating
  to imglib2 core. Augmenting ImageJ OPS with this feature would avoid
  complicating the imglib2 core with any dependencies.

- I seem to recall that I demonstrated a much higher performance win at the
  hackathon April/May 2013, what is the reason that the new approach does
  not reach those numbers?

> I have cleaned up what I have played with and put it on github
> https://github.com/tpietzsch/neon.

- A quick web search shows that there is an active, successful Neon library for
  WebDAV communication. To avoid legal and social problems, we'll need to
  choose a different name for the project.

> I think it is orthogonal to what you do with compile-time code
> generation currently and therefore might complement it nicely.

- I agree that the bytecode manipulation and code generation strategies can
  complement each other nicely. I am looking forward to the upcoming ImgLib2
  hackathon so that we can show you how OPS tackles the performance issue in a
  way that facilitates extensibility and keeps concerns well separated! If you
  have a chance to explore OPS in depth before the hackathon, it would be very
  helpful to expedite later discussion.

- I encourage you to study ImageJ OPS before continuing this project because it
  provides the necessary infrastructure already, matured over a course of
  several iterations.

- For demonstration purposes, using a Java Agent at startup is great; We will
  definitely want to explore practical ways of applying the bytecode
  manipulation. It should be achievable -- we do it already in the ImageJ Legacy
  project to rewrite portions of ImageJ1 as needed.

Ciao,
Johannes

_______________________________________________
ImageJ-devel mailing list
[hidden email]
http://imagej.net/mailman/listinfo/imagej-devel
Reply | Threaded
Open this post in threaded view
|

Re: [fiji-devel] imglib2-neon project for runtime bytecode transformation

Tobias Pietzsch
Hi all,

sorry, long (but hopefully informative) mail coming…

On 25 Sep 2014, at 19:58, Johannes Schindelin <[hidden email]> wrote:

It is great that you continue our conversation from the hackathon last
year in Madison:

http://fiji.sc/2013-05-03_-_ImgLib2_Hackathon_in_Madison

I am very glad that you have returned to this work, with a promising
initial foray into a general solution.

I need to make clear that (ex-)neon is not a continuation of our hackathon conversation. There is a crucial difference in scope. Let me quote the two relevant points from the above page:

  • Performance is another issue, completely. Over the course of several experiments, I became convinced that we were wrong to rely on the JIT so much: it is okay for simple things, but for ever so slightly complicated things, we need a precompiler that knows what we really want to do. For example, just the mere fact should have taught us something that stand-alone FloatType instances *must* refer to their *single* float value as a 1-element float array *just so* that the JIT does not do an utterly bad job when we access single values as well as pixel values in an array.
  • As a consequence of the findings regarding performance, I came up with a simple proof-of-concept that things could be made fast by using Javassist, a library for bytecode manipulation. We use this library extensively in ImageJ2 and Fiji already for just-in-time patching of, say, ImageJ1. It was a natural choice to turn to Javassist for trying to optimize ImgLib, too. Mind you, the example I made was very simple: it constructed a class working on ArrayImgs of FloatType directly, without even inspecting any code to know what to do, but instead hard-coding the "+" op. But it showed the way to how we could do things in the future: inspect what the, say, Expression is supposed to do and rewrite it in optimized bytecode. The speed improvement was—as I expected—impressive.

In contrast to what Johannes describes here, (ex-)neon explicitly relies on the JIT to “do it’s thing”.
I had hoped that this would be clear from the README which very explicitly explains what it currently does.
It addresses one particular pattern of polymorphic method calls that makes the JIT *deoptimize* optimistically compiled code. I address this by modifying bytecode to make the optimistic assumption valid.
Also answering the performance question below:
It should be clear, that this will never (!) produce something that runs faster than the optimistically compiled code, that is, the case where polymorphism was not realized at runtime.
A lot of ImgLib2 benchmarks we did fall into this category. They are fast as individual benchmarks but put a few of them together and use different image types etc, it paints a totally different picture. This is where I though (ex-)neon could help ImgLib2.

I also need to point out that I see the scope of (ex-)neon to be broader than imglib2, imagej2, scijava, etc. I think it could be useful in scenarios completely unrelated to our little corner of the world. Therefore, I would like to keep it a small, separate project to make it as easy as possible for whomever might be interested to take advantage of it.

Johannes, what you describe in the above page is different in scope. You used knowledge about implementation details to hand-code a faster version. That the hand-coded source goes through a layer of indirection with Javassist is for that particular example irrelevant, but I absolutely see the potential of assembling code from expressions recombining these hand-coded patterns. This is a different but completely valid approach, which was what I meant by saying that it is “orthogonal to imagej-ops”. However pushing this to a level where it works for many things (like in the imglib case something simple as user-made Types that are not part of the core library), gathering these implementation details automatically from places in the code that are potentially very far from where you currently are is extremely difficult, if not infeasible. It amounts to making a new JIT, but without access to profiling information.
So in my opinion this is a complementary approach. The advantage is potentially higher performance. The drawback is that you basically hand-code things. It’s targeted at a specific library and/or application. If the library code changes too much, it is likely to break.


Sorry for being so nit-picky about it but I really think it is important to not convolute these two (valid) approaches (and projects).


On the other hand I do not want to give the impression that I made this up out of thin air. There is lot’s of stuff that *is* closely related and I neglected to give credit for that (which seems to me to be part of your problem with this, Johannes?). To rectify that, I added a “Resources” section to the README on https://github.com/tpietzsch/none pointing to helpful discussions etc.


I do have some questions and suggestions:

- Why use ASM over Javassist? At the hackathon, we used Javassist because
 it is easier to use, we have much better documentation (e.g.
 http://fiji.sc/Javassist) and we already ship it with Fiji.

Here I have a different opinion. I think ASM has the better documentation.

There is no doc page for ASM on the Fiji wiki, but they have a very good guide on their own site:
which is both tutorial style and going into detail. It also contains an introduction to the java class file format and bytecode.

The API is well structured and the API documentation is complete, ASM 5.0 API. Especially after reading the guide, I had absolutely trouble finding my way around.

Finally, and this is really really helpful. They have a “Bytecode Outline” plugin for Eclipse.
This shows you not only the bytecode of the file you are currently editing:


...but also the ASM code needed to generate that:


This makes it trivial to get started. (Though you later figure out that there is a more powerful tree API that is to the above as DOM is to SAX).
For the non-Eclipse people, there is of course a command-line “ASMifier".

Importantly, I need low-level manipulations to arrive at code that I could *not* have produced by source code + javac.
I think these manipulations are possible using Javassist but I couldn’t easily find information about how.

Besides all that, ASM has going for it that it is bleeding edge, always at the latest java release version (and often beta versions).
There is overlap between OpenJDK and ASM people. ASM is used in the JDK itself (to implement java 8 lambda expressions for example).

So to summarize, I’m pretty much married to ASM now. I like the documentation, I like the tools, and it works great for what I want to do.
I see no reason to dig into Javassist now without any clear benefit.
Obviously, the same is probably true for the Javassist people: no reason to learn ASM.
So it is completely ok for me if, for example Johannes chooses to not get involved in (ex-)neon for that reason.

- If the plan is to use it inside Fiji, why not use version 4.0 of ASM
 which is in Fiji already as a transitive dependency of JRuby? Requiring
 a newer version (5.0.2) is prone to cause problems…

Sure, I’ll start by making a branch that uses the 4.0 version.
It should be as simple as using a now-deprecated ClassVisitor.visitMethodInsn() version.

- A quick web search shows that there is an active, successful Neon library for
 WebDAV communication. To avoid legal and social problems, we'll need to
 choose a different name for the project.

Ok, I understand. I changed the name. I just scrambled the letters and now it’s called “none”. I didn’t find a project called like this on google, so should be fine.
It’s a bit strange at first, but actually, I think it’s not so bad:
1) ample opportunity for evil chuckles whenever somebody claims his lib is “second to none”.
2) chemical elements logo still works out nicely.
I hope nobody has a problem with that.


By pure coincidence, “none” is exactly the amount of controversy I expected when announcing the project.
I could not have been more wrong. Off-list there has been a bit of a personal clash, which is not really of concern here.
However, it made me realize that I was not prepared for the, let’s say, political discussions that I triggered.
I wanted to get this out and off-my-plate in time for the hackathon. It turned out that by doing that I didn’t put it off-my-plate at all. To the contrary, it developed into a time- and fun-suck.

So in order to avoid that, I want to step back a little and make this back into the little fun playground that it was.
It has been made a much bigger deal than it actually is, and it needs time to mature before making it into imglib or fiji or wherever.
By mature I mean that it must be a place where I can try ideas without worrying about compatibility, integratability, stepping on peoples toes, etc.
So, this is what will happen in my github repository.

The code is under BSD license, so of course if someone wants to fork, pick pieces out of it etc they are more than welcome.
If there is a fork intended specifically for scijava/imglib2/imagej/fiji purposes, I’ll be glad to help with that.


I think it is orthogonal to what you do with compile-time code
generation currently and therefore might complement it nicely.

- I agree that the bytecode manipulation and code generation strategies can
 complement each other nicely. I am looking forward to the upcoming ImgLib2
 hackathon so that we can show you how OPS tackles the performance issue in a
 way that facilitates extensibility and keeps concerns well separated! If you
 have a chance to explore OPS in depth before the hackathon, it would be very
 helpful to expedite later discussion.

- I encourage you to study ImageJ OPS before continuing this project because it
 provides the necessary infrastructure already, matured over a course of
 several iterations.

I’ve been following ImageJ-Ops loosely. As I understand it still very much in flux and documentation is very sparse. I’m looking forward to learn about it in personal discussion at the hackathon. I’ll probably not be able to get an in-depth look before.

- For demonstration purposes, using a Java Agent at startup is great; We will
 definitely want to explore practical ways of applying the bytecode
 manipulation. It should be achievable -- we do it already in the ImageJ Legacy
 project to rewrite portions of ImageJ1 as needed.

The important thing is that it needs to be able to intercept any class as it is loaded into the JVM.

I think another perfect place to put it is in a ClassLoader.
Building on OSGi, that is probably a feasible way to go for KNIME.
Does ImageJ2 use custom ClassLoaders?


best regards,
Tobias


_______________________________________________
ImageJ-devel mailing list
[hidden email]
http://imagej.net/mailman/listinfo/imagej-devel

signature.asc (465 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: [fiji-devel] imglib2-neon project for runtime bytecode transformation

Johannes Schindelin
Hi Tobias,

On Tue, 30 Sep 2014, Tobias Pietzsch wrote:

> On 25 Sep 2014, at 19:58, Johannes Schindelin <[hidden email]> wrote:
>
> > It is great that you continue our conversation from the hackathon last
> > year in Madison:
> >
> > http://fiji.sc/2013-05-03_-_ImgLib2_Hackathon_in_Madison
> >
> > I am very glad that you have returned to this work, with a promising
> > initial foray into a general solution.
>
> I need to make clear that (ex-)neon is not a continuation of our
> hackathon conversation.
I apologize, then. I just thought that your work was intended to address
one of the two major advices I raised at the hackathon (in person, I did
not think of writing it up): 1) ImgLib2 should be made easier to use e.g.
for life scientists and 2) ImgLib2 should pay more attention to being
performant by default.

It does look that your work addresses 2) so I am happy.

> It should be clear, that this will never (!) produce something
> that runs faster than the optimistically compiled code, that is,
> the case where polymorphism was not realized at runtime.

This has not been clear to me, thanks for clarifying.

> [...] in my opinion [replacing algorithms by handcrafted code] is a
> complementary approach. The advantage is potentially higher performance.
> The drawback is that you basically hand-code things. It’s targeted at a
> specific library and/or application. If the library code changes too
> much, it is likely to break.

Oh, but I stressed the point at the hackathon that my demonstration was
intended to show where the performance needs to be, not how to reach it.

In the meantime, it has become even more obvious to me that a generic data
processing framework requires a proper API to allow overriding generic
algorithm implementations with specific, hand-optimized code, and that the
original implementation must not be allowed to prevent the hand-optimized
code from being executed.

> Sorry for being so nit-picky about it

In contrast, I am happy that the conversation about the performance of
data processing in ImageJ2 continues!

> There is overlap between OpenJDK and ASM people. ASM is used in the JDK
> itself (to implement java 8 lambda expressions for example).

Speaking of Java 8: you might want to consider using its Scala-inspired
features instead. This would limit the use for the general audience, but it
would probably benefit greatly individual developers who can afford to switch
to Java 8.

> > [...] If you have a chance to explore OPS in depth before the
> > hackathon, it would be very helpful to expedite later discussion [...]
> > because it provides the necessary infrastructure already, matured over
> > a course of several iterations.
>
> I’ve been following ImageJ-Ops loosely. As I understand it still very
> much in flux and documentation is very sparse. I’m looking forward to
> learn about it in personal discussion at the hackathon. I’ll probably
> not be able to get an in-depth look before.

If you do find some time in the next weeks to look at the Ops tutorials
https://github.com/imagej/imagej-tutorials/tree/master/using-ops and
https://github.com/imagej/imagej-tutorials/tree/master/create-a-new-op
it will help the discussion. Do not worry if you do not find the time: the
issue of ImgLib2 release management is the real topic of the hackathon, and
we will be plenty busy with it.

> Does ImageJ2 use custom ClassLoaders?

By virtue of the legacy ImageJ 1.x's PluginClassLoader loading all the
plugin classes: yes.

Thank you for keeping the conversation going,
Johannes


_______________________________________________
ImageJ-devel mailing list
[hidden email]
http://imagej.net/mailman/listinfo/imagej-devel
Reply | Threaded
Open this post in threaded view
|

Re: [fiji-devel] imglib2-neon project for runtime bytecode transformation

Tobias Pietzsch
Hi Johannes,

On 01 Oct 2014, at 17:17, Johannes Schindelin <[hidden email]> wrote:

> Speaking of Java 8: you might want to consider using its Scala-inspired
> features instead. This would limit the use for the general audience, but it
> would probably benefit greatly individual developers who can afford to switch
> to Java 8.

Could you please elaborate this a bit? I’m not sure which features you mean and how I could utilize them.

> If you do find some time in the next weeks to look at the Ops tutorials
> https://github.com/imagej/imagej-tutorials/tree/master/using-ops and
> https://github.com/imagej/imagej-tutorials/tree/master/create-a-new-op
> it will help the discussion. Do not worry if you do not find the time: the
> issue of ImgLib2 release management is the real topic of the hackathon, and
> we will be plenty busy with it.

Oh, then it’s all good, I already looked at those tutorials.

I agree that the focus of the hackathon should be on the ImgLib2 release.

>
>> Does ImageJ2 use custom ClassLoaders?
>
> By virtue of the legacy ImageJ 1.x's PluginClassLoader loading all the
> plugin classes: yes.

Hmm, that is probably too far down the chain. We would need a place to intercept loading of the core classes. After these classes have been loaded it’s too late.
If there is no elaborate ClassLoader magic going on, what would be the problem with using a Java Agent?
Let’s look into this at the hackathon if we find the time.

best regards,
Tobias

>
> Thank you for keeping the conversation going,
> Johannes
>
> --
> --
> Please avoid top-posting, and please make sure to reply-to-all!
>
> Mailing list web interface: http://groups.google.com/group/fiji-devel
>
> ---
> You received this message because you are subscribed to the Google Groups "Fiji-devel" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to [hidden email].
> For more options, visit https://groups.google.com/d/optout.

_______________________________________________
ImageJ-devel mailing list
[hidden email]
http://imagej.net/mailman/listinfo/imagej-devel

signature.asc (465 bytes) Download Attachment
Reply | Threaded
Open this post in threaded view
|

Re: [fiji-devel] imglib2-neon project for runtime bytecode transformation

Stephan Saalfeld-2

> > Speaking of Java 8: you might want to consider using its Scala-inspired
> > features instead. This would limit the use for the general audience, but it
> > would probably benefit greatly individual developers who can afford to switch
> > to Java 8.
>
> Could you please elaborate this a bit? I’m not sure which features you mean and how I could utilize them.
>

May be he means

http://www.scala-lang.org/api/2.10.4/index.html#scala.specialized

?  But that's not specific to Java 8 and not done at runtime?



_______________________________________________
ImageJ-devel mailing list
[hidden email]
http://imagej.net/mailman/listinfo/imagej-devel