for (var x = 0; x < im.getWidth(); x++) {
for (var y = 0; y < im.getHeight(); y++) {
That would repeatedly call the get*() methods, and for a 1000x1000px image, that is a lot of calls. Better call them once and then use variables for the loop boundaries.
var w = im.getWidth()
var h = im.getHeight()
for (var x = 0; x < w; x++) {
for (var y = 0; y < h; y++) {
Your insight into performance is obviously good, but I'd rather see the students focus on writing code which is obvious to them and have the compiler/interpreter realize there's a ton of redone work. I think you can do this with flow analysis.
When I see this sort of optimization I flag it to wonder how much "help" the student got during the assignment. Years ago I saw this on a student F77 assignment
y = x + x + x
instead of
y = x * 3
The real author the assignment clearly knew that for the particular machine it was executing on that 3 addition ops were faster than a multiply and couldn't help themselves.
When questioned as to why this was, the student couldn't explain the meaning of the expression and the jig was up
not for floats (which javascript numbers are) on x86 (using the XMM registers, I don't know the x87 numbers offhand), last I checked a MULSD is 5 cycles, and ADDSD is 3, therefor conversion doesn't generally make sense.
While it is true that all JavaScript numbers appear to be floats, there's nothing stopping them from being optimized by the JIT compiler into 32-bit integers if possible.
I think the original comment about x + x + x there was not about JavaScript.
For JavaScript, you get all sorts of additional complications depending on how good your JIT is. For example, you have to check both integer addition and integer multiplication for overflow (at which point you have to recompile as doubles) unless your interval analysis is good enough. So the cost is more than just the cost of the machine add in some cases.
In practice, I'd be suprised if current JS JITs do strength-reduction in this case. They might get there sometime in the next few years, but there's lower-hanging fruit to be had so far.
For integers only. For floats, a compiler will typically only do strength-reduction if you ask it to explicitly, because it changes the answer, so it technically an invalid optimization.
I find the efficient version much clearer, too. It extracts one layer of abstraction: a beginner may wondering, how can a variable being less than a function (a<func()), then find out, that the function returns a value, etc.
I've been back and forth about how to respond to your comment. My initial reaction was "'func()' is a call and therefore returns an expression." Having thought about it, I don't really remember what my neophyte reaction would have been.
It shouldn't confuse students with optimization advice, but with the authority the lecture holds on malleable students, your asterisk should still be mentioned as an FYI for good measure.
I agree that in an introduction course, it is important to keep the code as readable as possible. But it is likewise important to get performance considerations in student's heads as early as possible.
Comments
Think about performance
That would repeatedly call the get*() methods, and for a 1000x1000px image, that is a lot of calls. Better call them once and then use variables for the loop boundaries.Your insight into performance is obviously good, but I'd rather see the students focus on writing code which is obvious to them and have the compiler/interpreter realize there's a ton of redone work. I think you can do this with flow analysis.
When I see this sort of optimization I flag it to wonder how much "help" the student got during the assignment. Years ago I saw this on a student F77 assignment
y = x + x + x
instead of
y = x * 3
The real author the assignment clearly knew that for the particular machine it was executing on that 3 addition ops were faster than a multiply and couldn't help themselves.
When questioned as to why this was, the student couldn't explain the meaning of the expression and the jig was up
And the worst part of it is, converting x*3 to x+x+x is a really basic optimization that any decent compiler will do for you.
not for floats (which javascript numbers are) on x86 (using the XMM registers, I don't know the x87 numbers offhand), last I checked a MULSD is 5 cycles, and ADDSD is 3, therefor conversion doesn't generally make sense.
While it is true that all JavaScript numbers appear to be floats, there's nothing stopping them from being optimized by the JIT compiler into 32-bit integers if possible.
I think the original comment about x + x + x there was not about JavaScript.
For JavaScript, you get all sorts of additional complications depending on how good your JIT is. For example, you have to check both integer addition and integer multiplication for overflow (at which point you have to recompile as doubles) unless your interval analysis is good enough. So the cost is more than just the cost of the machine add in some cases.
In practice, I'd be suprised if current JS JITs do strength-reduction in this case. They might get there sometime in the next few years, but there's lower-hanging fruit to be had so far.
Strength reduction in this vein is pretty trivial though, even if there is lower hanging fruit. For example this is what transforming some_int * a_power_of_two looks like in PyPy: https://bitbucket.org/pypy/pypy/src/default/pypy/jit/metaint...
It's trivial if you don't mind the extra compilation time.
If you're trying to make sure that your compile times are also short, it might not be worth it.... yet.
For integers only. For floats, a compiler will typically only do strength-reduction if you ask it to explicitly, because it changes the answer, so it technically an invalid optimization.
the compiler also needs to figure out that getWidth() and getHeight() are pure.
but yes, i agree with you that for an intro class, students should focus on writing clear code and then optimizing for performance later.
I find the efficient version much clearer, too. It extracts one layer of abstraction: a beginner may wondering, how can a variable being less than a function (a<func()), then find out, that the function returns a value, etc.
Two more lines worth it.
I've been back and forth about how to respond to your comment. My initial reaction was "'func()' is a call and therefore returns an expression." Having thought about it, I don't really remember what my neophyte reaction would have been.
It shouldn't confuse students with optimization advice, but with the authority the lecture holds on malleable students, your asterisk should still be mentioned as an FYI for good measure.
I agree that in an introduction course, it is important to keep the code as readable as possible. But it is likewise important to get performance considerations in student's heads as early as possible.
Are you saying im.getWidth() is doing some expensive calculation underneath, or that the cost of calling it is expensive in Javascript?
I imagine it loops though the full width of the image every time it is called, which is every time the loop reaches the check.
Since storing it will require width-1 less calls to im.getWidth() it is much more efficient no matter how you slice it.