Live data from Hacker News

How we made a Ruby method 200x faster

campsite.com

21–30 of 48 posts

Re: How we made a Ruby method 200x faster

#21

This should have been obvious before the fact to anyone who understands how CSS selectors work in browsers. As in, they are matched right-to-left, which implies that a selector like ”p a” first selects all the nodes, and for each of them, it then traverses up the DOM tree until it encounters a node (selector matches) or the root node (selector doesn’t match). That said, the traversing shouldn’t happen for plain tag s…

> This should have been obvious before the fact to anyone who understands how CSS selectors work in browsers.

For sure, but that's somewhat reductive. This is not exactly common knowledge. Certainly not something you would expect any given engineer to have immediately jump to mind.

Re: How we made a Ruby method 200x faster

#22

The waterfall of end statements in Ruby reminds me of pascal. Seems verbose.

Interesting. I consider myself a rubyist and never considered this. Perhaps because the rest of the language is so concise that this little verbosity never really bothered me

Re: How we made a Ruby method 200x faster

#23

This should have been obvious before the fact to anyone who understands how CSS selectors work in browsers. As in, they are matched right-to-left, which implies that a selector like ”p a” first selects all the nodes, and for each of them, it then traverses up the DOM tree until it encounters a node (selector matches) or the root node (selector doesn’t match). That said, the traversing shouldn’t happen for plain tag s…

Is that really how it works in browsers and other rendering engines?

Intuition suggests to me that it wouldn’t start with CSS and then find all the matching DOM nodes. I would expect it started at each DOM node and then found the CSS rules which might apply.

So “I’m adding an A to the tree; what are all the CSS rules with or A or * at the rightmost token; which of that set applies to my current A; apply the rules in that sub set”. Going depth first into the DOM like this should result in skipping redundant CSS, and (as my imagination draws it) reduce DOM traversals.

Re: How we made a Ruby method 200x faster

#25
post #19

Earlier quoted context omitted.

I think it's pretty easy to see why people would dislike it, with each on their own line and indented, it's very easy to track what ends where. With this version, not so much, if you're e.g. five nests deep and then see three end statements on one line.

I think this is fine - I don't see why having the `end`s on separate lines would make it easier to understand: if ... if ... if ... if ... if ... x = 1 end end end y = 2 end end

When I see such code I chukle... Really? I always try to make my code as flat as possible, either using next or break (or split to function and use return). Thats why I sometimes miss goto. But case can emulate it pretty fine.

Re: How we made a Ruby method 200x faster

#26

This should have been obvious before the fact to anyone who understands how CSS selectors work in browsers. As in, they are matched right-to-left, which implies that a selector like ”p a” first selects all the nodes, and for each of them, it then traverses up the DOM tree until it encounters a node (selector matches) or the root node (selector doesn’t match). That said, the traversing shouldn’t happen for plain tag s…

Is that really how it works in browsers and other rendering engines? Intuition suggests to me that it wouldn’t start with CSS and then find all the matching DOM nodes. I would expect it started at each DOM node and then found the CSS rules which might apply. So “I’m adding an A to the tree; what are all the CSS rules with or A or * at the rightmost token; which of that set applies to my current A; apply the rules in…

In browsers, DOM parsing starts before (all) CSS is loaded and parsed. Also, the sizes of elements in the flow are (by default) dictated by the text content, so it really does not make sense to try to paint a page in a root-to-leaf order.

Re: How we made a Ruby method 200x faster

#27
I've flamegraph-debugged JS code from time to time, and it usually feels a lot more of a craft and "educated guesses" than the vast majority of programming things I do. I usually only get down to it when there's an actual perf problem so YMMV, but I'm curious, do I do JS flamegraph debugging wrong, or is it something like this for everyone?

- 20% of the times you get lucky and find a very easy win that speeds up things 90%+. Similar to this post, usually when a single method/call takes a huge chunk of the work.

- 50% of the times you grind at it and can get 30-50% speed up. I usually try many things, and only some of them do make a difference.

- 30% of the time absolutely no luck! Many small calls where each is unavoidable, no repeated code, etc.

Re: How we made a Ruby method 200x faster

#28

I've flamegraph-debugged JS code from time to time, and it usually feels a lot more of a craft and "educated guesses" than the vast majority of programming things I do. I usually only get down to it when there's an actual perf problem so YMMV, but I'm curious, do I do JS flamegraph debugging wrong, or is it something like this for everyone? - 20% of the times you get lucky and find a very easy win that speeds up thin…

I do low level systems programming, so pretty different from JS-land, but I feel the techniques you should apply when doing optimization generally apply at any level/language.

0) algorithmic improvement. Obvious shit like do a quick sort instead of bubble sort (assuming N > 64, or whatever), not doing unnecessary work in a hot loop, etc

1) reduce memory footprint. The slowest part of your program is almost always just waiting for memory, unless you're doing something that's heavily CPU bound. Web applications are probably always memory bound. Reducing the amount of memory the function you're optimizing operates on reduces DCache misses, which are expensive.

2) Do batch operations. Once I've got something to a point where it's not completely braindead (which, honestly, is where I stop most of the time), I look to start batching things. Usually look to do 8 or 16 at a time in the hopes the compiler/runtime can make some use of SIMD. Use STATIC LOOPS ie (for 0..8) so the compiler can unroll the loop. That's extremely important.

3) probably unavailable (unless you want to/can drop into WASM), but the next step is usually SIMD. This is a rabbit hole, but if you want/need another ~8x perf improvement, this is how to get it

4) once all that's done, it's probably close to optimal in terms of cycles per element (unless I did something boneheaded, which is common). Last step is to multithread it if it needs even more juice. This can range from trivial to completely impossible depending on the algorithm. In JS land, you need to make sure you operate on SharedAreayBufferrs when doing multithreading for performance, because web workers copy the input/output values by default.

Anywhoo.. maybe that helps.

When I try to optimize something lightly, it's not uncommon for me to get 10x improvement fairly easily. When I optimize something to within an inch of it's life, I can sometimes get three or even four orders of magnitude faster.

Re: How we made a Ruby method 200x faster

#29
I don't understand "how we made X in Ruby/Python Y% faster" posts. It is of course possible to optimize functions in any language, and often worthwhile to do, but if you're going to spend a lot of engineering resources on it, then can I introduce you to my friends C++ and Rust?
Post reply on HN