Prev: FAQ Topic - Why does simple decimal arithmetic give strange results? (2010-03-31)
Next: Consolidate Credit Card Debt
From: Lasse Reichstein Nielsen on 2 Apr 2010 07:47 Thomas 'PointedEars' Lahn <PointedEars(a)web.de> writes: To nitpick out of context: > 2. Several function calls will be always more expensive than just one and > the creation of an Array instance. That's not a given. It's possible that four function calls take longer than creating an Array and calling one function that loops over the array and extracts the elements again (possible, but not given), but the latter also allocates space for the array. If this happens often enough, the memory overhead will also cause earlier garbage collection. After writing that, I decided a test was in order, instead of relying on intuition. The following code is attempted written without obvious inefficiencies in either test case (all variables are local, loop invariant expressions are lifted, etc.). I have run it in Opera 10.51, Chrome 5(dev), Firefox 3.6.2 and IE 9 preview. <script type="text/javascript"> function Obj() { this.props = {}; var self = this; this.setChained = function setProp(name, value) { self.props[name] = value; return setProp; }; }; Obj.prototype.setProp = function (name,value) { this.props[name] = value; }; Obj.prototype.setProps = function (arr) { var props = this.props; for (var i = 0, n = arr.length; i < n; i += 2) { props[arr[i]] = props[arr[i+1]]; } }; function test1() { var o = new Obj(); var t0 = new Date(); for (var i = 0; i < 100000; i++) { o.setProp("a", i); o.setProp("b", i); o.setProp("c", i); o.setProp("d", i); } var t1 = new Date(); return t1-t0; } function test2() { var o = new Obj(); var t0 = new Date(); for (var i = 0; i < 100000; i++) { o.setChained("a", i)("b", i)("c", i)("d", i); } var t1 = new Date(); return t1-t0; } function test3() { var o = new Obj(); var t0 = new Date(); for (var i = 0; i < 100000; i++) { o.setProps(["a", i, "b", i, "c", i, "d", i]); } var t1 = new Date(); return t1-t0; } alert([test1(), test2(), test3()]); </script> In the first three browsers (not IE), doing four calls is consistently faster than creating one eight-element array and doing one call. And not by a small amount - it is between 1.75 and 3 times as fast. In IE9, they take about the same time. Using the chained way of calling the setter is about the same as just doing the four calls in Firefox and Chrome, and somewhat slower in Opera, but not as slow as using an Array. In IE9, this approach is actually the fastest. Go figure. In summary: Function calls aren't significantly slower than other language features like object creation and property lookup. Object creation, and inspection, spends a lot of time in memory access, where function calls probably uses the stack (which is very fast memory, because it's almost always in the first level cache of the processor). /L -- Lasse Reichstein Holst Nielsen 'Javascript frameworks is a disruptive technology'
From: Lasse Reichstein Nielsen on 2 Apr 2010 07:49 nick <nick___(a)fastmail.fm> writes: > On Apr 1, 6:30�am, Thomas 'PointedEars' Lahn <PointedE...(a)web.de> > wrote: >> >> 1. It requires you to keep an external reference to the initial calling >> � �object as Richard has already explained. >> > > What about arguments.callee? That's good enough for returning a > reference to the same function. If performance is important, don't use "arguments" at all. "arguments.callee" always referes to the function being executed. You can just give that function a name and refer to it by that name. I.e., DON'T: function (args) { ... arguments.callee ... } DO : function foo(args) { ... foo ... } /L -- Lasse Reichstein Holst Nielsen 'Javascript frameworks is a disruptive technology'
From: Scott Sauyet on 2 Apr 2010 08:55 nick wrote: > On Apr 1, 4:38 pm, Scott Sauyet <scott.sau...(a)gmail.com> wrote: > >> nick wrote: >>> myApp.keys.register >>> ('w', panNorth) >>> ('s', panSouth) >>> ('a', panWest) >>> ('d', panEast); > >> I agree with the other posters that it is in fact less clear. But >> there are other options as well: > > Is it really so much less clear that you'd really spend any > significant time wondering what that code did? Perhaps not. I would definitely wonder *how* it's doing what it does, though, and that's a distraction too. Chaining like this wouldn't surprise me at all: myApp.keys .register('w', panNorth) .register('s', panSouth) .register('a', panWest) .register('d', panEast); There I would know exactly what's going on, and I'd understand how it works too. > I mean, I see what you > guys are saying but I just don't see what else that code could > possibly be doing. The name of the function appears once, and all the > arg lists look the same, so I feel like it's pretty safe to assume > (to someone reading chained code like this) that the same function is > getting all the arg lists. I guess if the args all look so similar to one another as these ones do, I'd probably make the same assumption, but the minute they start looking different, I would likely get confused. >> myApp.keys.register({ >> 'w': panNorth, >> 's': panSouth, >> 'a': panWest, >> 'd': panEast >> }); > > That example isn't too bad. The issue is that 'register' can take more > parameters... That does make a substantial difference. With that I would definitely go with the chained calls to the named function, myApp.keys.register(/ * ... */).register(/* ... */) or even the most explicit myApp.keys.register(/* ... */); myApp.keys.register(/* ... */); > another onRelease callback, and I'll probably add some > kind of flags enum or something for modifier keys (still trying to > decide how to expose that). So this: > > var keys = myApp.keys; > keys.register > ('w', panNorth, endPanNorth, keys.CTRL | keys.SHIFT) > ('s', panSouth, endPanSouth, keys.CTRL | keys.SHIFT) > ('a', panWest, endPanWest, keys.CTRL | keys.SHIFT) > ('d', panEast, endPanEast, keys.CTRL | keys.SHIFT); > > ...Now becomes something like this: > > var keys = myApp.keys; > keys.register({ > 'w': [panNorth, endPanNorth, keys.CTRL | keys.SHIFT], > 's': [panSouth, endPanSouth, keys.CTRL | keys.SHIFT], > 'a': [panWest, endPanWest, keys.CTRL | keys.SHIFT], > 'd': [panEast, endPanEast, keys.CTRL | keys.SHIFT] > }); > > ...Which is still pretty decent I guess. You could even make the inner > arrays into options so you could have named parameters. Luckily the > way I am doing it now is flexible enough to incorporate something like > this if I decide to do it later ;) While this is certainly possible, it starts to look ugly enough to me that I wouldn't really want it on a public, shared API if I have any real choice. If you're the only one using the API, then whatever you think will remain easily readable to you after some time away from it is fine. >> or > >> myApp.keys.register( >> 'w', panNorth, >> 's', panSouth, >> 'a', panWest, >> 'd', panEast >> ); > > It would be hard to do much with that given the optional args, I > think. Yes, and that's the weakness of this method in general, but it's quite nice when you don't face that possibility. -- Scott
From: Thomas 'PointedEars' Lahn on 2 Apr 2010 13:46 Lasse Reichstein Nielsen wrote: > nick <nick___(a)fastmail.fm> writes: >> Thomas 'PointedEars' Lahn wrote: >>> 1. It requires you to keep an external reference to the initial calling >>> object as Richard has already explained. >> What about arguments.callee? That's good enough for returning a >> reference to the same function. > > If performance is important, don't use "arguments" at all. > > "arguments.callee" always referes to the function being executed. > You can just give that function a name and refer to it by that name. > > I.e., > DON'T: function (args) { ... arguments.callee ... } > DO : function foo(args) { ... foo ... } A recommendation to make maintenance harder than it needs to be while ignoring a known bug with named function expressions in several versions of the most widespread implementation available. DO NOT do this. PointedEars -- Use any version of Microsoft Frontpage to create your site. (This won't prevent people from viewing your source, but no one will want to steal it.) -- from <http://www.vortex-webdesign.com/help/hidesource.htm> (404-comp.)
From: Thomas 'PointedEars' Lahn on 2 Apr 2010 14:02
Lasse Reichstein Nielsen wrote: > Thomas 'PointedEars' Lahn <PointedEars(a)web.de> writes: >> 2. Several function calls will be always more expensive than just one >> and the creation of an Array instance. > > That's not a given. > > It's possible that four function calls take longer than creating an Array > and calling one function that loops over the array and extracts the > elements again (possible, but not given), but the latter also allocates > space for the array. If this happens often enough, the memory overhead > will also cause earlier garbage collection. That argument is dubious and the tests have no meaning because the function argument in chaining may be an Array or Object reference anyway. Incidentally, that is what can be observed in current general-purpose libraries that use chaining. You are correct that cost is not merely measured in runtime spent. However, you are ignoring here that for, say, ten registrations I would need ten chained function calls, or only one function call with an Array instance of length 10. There can be no doubt that the chaining grows more inefficient than the alternatives with each registration. With 1000 registrations I would still need only one Array instance or 1000 items to iterate over, instead of 1000 calls to be made. I find it particularly dubious that you have not noticed that the length of the effective prototype chain can be an important factor regarding efficiency. setChained() is an instance method with a short effective prototype chain, setProp() and setProps() are prototype methods with longer ones. I also don't see you considering that the chaining approach needs at least one Function object more than the non-chaining one in your example. All in all, I don't think your argument or test holds water. PointedEars -- realism: HTML 4.01 Strict evangelism: XHTML 1.0 Strict madness: XHTML 1.1 as application/xhtml+xml -- Bjoern Hoehrmann |