-
-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
better isXXX checks #321
better isXXX checks #321
Conversation
So -- here's the history. Underscore.js But JavaScript engines, minus IE7 and IE8, have improved drastically since those times, so perhaps it's no longer something to worry about. If you want to get these changed back to the way they used to be, it would be great to get a comprehensive JSPerf to go along with 'em. |
@jashkenas: will do. |
So, finally went and benched this out of curiosity ... Underscore's current property checks are still far faster than the proposed alternatives. Up to 9x faster in Chrome. http://jsperf.com/underscore-js-istype-alternatives Closing this patch as a wontfix -- I'm still of the impression that although you can manufacture objects to break the isType checks, they tend to work pretty well in real world code. |
|
If people are actually running into false positives in real programs, we should reconsider ... but I don't think I've ever seen a report of that happening. Using JS objects as hashes is fraught with other problems ... but the isType checks should be able to tell strings from functions from arrays from regexes efficiently. |
I edited the jsPerf test case. The speed differences are no longer as dramatic in Chrome and I added jQuery as a performance sanity check. As for a false positive, I did a quick search of the issues and came up with this. Here is a summary of my comments on the jsPerf test. JSPerf CommentsComment 1I doubt the speed diff will matter in real world use. I believe @kitcambridge also mentioned that by using the internal Also keep in mind there are differences in the results of these implementations. For example _.isNumber(NaN); // false
_.isNumberNew(NaN); // true
jQuery.type(NaN); // "number" As with any sugar if performance ever becomes an issue devs can drop back to de-sugared simple Note: I tweaked the test to work in IE and added jQuery as a sanity check. Comment 3The concern over performance in these sugar methods is misplaced. It's better for these methods to follow spec precedents (set by As for Though by your preference Underscore.js v1.2.0 is actually inconsistent with what it considers a number because it allows You can't go wrong following spec and checking the internal NaN.constructor == Number; // true
Infinity.constructor == Number; // true
typeof NaN.toFixed // function
typeof Infinity.toPrecision // function
var n = Object(NaN);
isNaN(n); // true
n == n; // true
Number.prototype.toJSON = function() { return isFinite(this) ? this : String(this); };
JSON.stringify(n); // '"NaN"' The concern over |
@jdalton makes some very good points. Correctness (with |
@jdalton: Whoa -- you just edited the original JSPerf comparison? I didn't realize it was possible for other people to do that on JSPerf.com |
It's only possible by the jsPerf site maintainers. @mathias and I are the only ones that can. (I wrote benchmark.js and the JS that handles jsPerf's UI and Browserscope reporting/charting. I also have commit rights to Browserscope). |
Ok -- then do you mind explaining what exactly you changed so that a 90% performance difference yesterday is down to a 30% performance difference today, in the same browser? |
Yap.
isArgumentsNew: function(obj) {
return toString.call(obj) == '[object Arguments]' || !!(obj && hasOwnProperty.call(obj, 'callee'));
} to isArgumentsNew: toString.call(arguments) == '[object Arguments]'
? function(obj) {
return toString.call(obj) == '[object Arguments]';
}
: function(obj) {
return !!(obj && hasOwnProperty.call(obj, 'callee'));
} You were getting hit double for non-argument object values passed to
(I should've probably used `isArgumentsNew() for the jQuery sanity check as it's more in line w/ its other implementations but it still gets the point across) |
@jashkenas: if you can reopen this pull request for me, I have another commit to push to it. Github doesn't add extra commits to closed pull requests, only open ones. I've redone the
I think an additional check for a
Thoughts? |
Sure, reopened. I'd actually perf test the two isArguments implementations side by side before choosing one over the other. |
@jashkenas please revisit my previous comment. I've clarified it a bit. |
There we go -- it was just the global lookups of |
Let's be clear here -- is the only difference between the two implementations the result of:
Incompatibilities that arise when extending native objects and then trying to use Underscore don't count -- because large swaths of Underscore won't work properly with extended natives, not just the isType tests. If it's just |
@jashkenas: as a follow-up to my last comment, here's a perf test showing zero performance difference between the |
Sounds like some design flaws w/ underscore.js.
I think you're getting hung up on these perf details and losing a bit of context. As with other underscore.js issues, correctness and consistency trump these kinds of perf concerns. |
@michaelficarra In Chrome 14 and 15, though, the |
@kitcambridge: crap, I forgot about that. Fixing... update: fixed |
@michaelficarra Much better, thanks. The difference in performance is negligible in IE 6, where it matters most. |
Quoting @broofa:
Show of hands -- because the check can be easily made to work either way. Do you think that |
Quoting myself:
My vote is that |
If
then shouldn't |
I think you're getting hung up the wording. Both |
Some definitions from ECMA spec Number value primitive value corresponding to a double-precision 64-bit binary format IEEE 754 value. NOTE A Number value is a member of the Number type and is a direct representation of a number. The definition of number conversion is also describe such that http://www.ecma-international.org/publications/files/ECMA-ST/Ecma-262.pdf All of the underscore
|
Libraries like jQuery and Prototype (whose API underscore.js seems to be a bit inspired by) also use the internal
Not so. The |
A NaN values are members of JS Number data type just like (and because) they are members of the IEEE 754 numeric types. The terminology "Not a Number" is a IEEE 754 term and note that they don't also use "number" as the name of a data type so in its original context there was no confusion about the name. BTW, it is IEEE 754 that defines that NaN values are not "equal" to anything else, including themselves. Because NaN is a Number, you can use it in numeric computations. All the built-in numeric values and Math functions have explicit rules for how they operate upon and propagate NaN values. |
@jdalton lol didn't even look what else changed. If the definition of the is check is changing to match the prototype then that works too. |
@arbales and @michaelficarra said it best. NaN is a |
@jdalton I'm not talking about the implementation details of various libraries, but the reason for using a library in the first place. And, I'd say that direct comparisons to the Prototype or jQuery ways are less relevant to Underscore, as we should favor more semantically cogent API decisions. (imho) @keithamus, I think you might have reversed your answer? Since both The big issue for me is the contradiction that |
Unfortunately others, at some point, have shared your opinion and it's caused nothing but problems in their projects. |
@jdalton, but… we're not talking about a function with a native equivalent. I think its a little unfair to equate "Incorrect ES5 Fallbacks," – which I believe were mostly unintentional – with a conscious decision to favor semantic clarity over unclear uniformity. I'm not saying ignore the spec, I'm saying read it with "plain meaning" — there is no spec for the behavior of isNumber. Finally, returning ✌️ |
To clarify: I used the term "numeric value" because writing "the number not-a-number" would have been extremely confusing. Yet, this is precisely what IEEE 754 appears to imply: Languages such as Ruby implement different numeric types ( |
@arbales In the spirit of fairness other libs like MooTools, ExtJS, and YUI use an I would hate for this whole pull request to get flushed because of a debate over the finer points of |
@jdalton very true — I believe ES5 has spec'd both The |
@jdalton: Regarding performance considerations. The isType APIs are used in tight loops. There's real-world cases for that, not the least of which is backbone.js, where every attribute set on a model involves two or more isType() calls (via isEqual()). Updating 100 model objects with 10 attributes may result in 2K-4K isType() invocations which, based on the jsperf data, would translate to additional CPU time of 200-500ms on some mobile devices. More than enough to be noticeable to users and make for some annoying delays in the UI responsiveness. You may argue that underscore should wait until mobile developers are "beating down the door" for performance improvements but is that realistic? Is it possible that rather than complain, they'll simply switch to one of the numerous other mobile JS frameworks available, that put more of an emphasis on performance? |
Ya know, as I mentioned before, if performance is ever an issue you simply cut back on sugar (yes isType is sugar) in the affected code. You don't have to, and shouldn't, sacrifice correctness/consistency in the sugar though. |
Sorry @arbales, I misread your original comment. My view is that the following two methods should behave as such:
The reason being that if I am checking if something is a |
"Cutting back on sugar" is less of an option in code you don't control. How would a developer using backbone remove the sugar in underscore's isEqual(), upon which backbone relies? @keithamus: In what circumstances are you testing for a number, but not want to rule out NaN? (I'm looking for something more concrete than "because I want to know if it's an instance of Number". Why do you want to know if it's a number?) |
Hmm. 'Just remembered the jsperf tests are doing 12x tests per iteration, which means the 200-500ms time I quoted earlier is probably more like 15-40ms... so not such a big deal after all. :) So, yeah, maybe the perf aspect of things isn't that big a deal. |
I think the performance concerns are becoming somewhat excessive. To provide some context, many of Underscore's collection methods already implement numerous nested function calls: Regarding
Finally, it seems worthwhile to note that Underscore and other frameworks are already unlikely to appeal to developers who prioritize performance over readability and/or convenience. |
And another thing
|
@NHQ: Cravat.
|
@SeanMcMillan Touche! Now somebody tell the website cuz: Underscore is a utility-belt library for JavaScript that provides a lot of the functional |
I think Kit's comment was right on. Projects like backbone.js/underscore.js can reduce unnecessary function calls and use bare bones JS in areas where performance is critical. When I forked Prototype.js and began the process of converting it to my own lib I cut back on the number of API calls used in the internals of its methods. This, in part, helped move its overall performance profile away from Prototype's (which is one of the slowest JS libs) to closer to that of faster libs like jQuery. |
I also completely agree with @kitcambridge and @jdalton that, if performance is such a concern, the first place to look is reduction of the average call stack size. |
The current tests allow for too many false positives for my liking. This also changes
_.isNumber(NaN)
fromfalse
totrue
. It really should have beentrue
in the first place.