Live data from Hacker News

Speed Without Wizardry

fitzgeraldnick.com

11–20 of 72 posts

Re: Speed Without Wizardry

#11

Earlier quoted context omitted.

> "It means minimizing use of nested loops, which exponentially increase statement count." Nesting two loops has an n^2 cost and nesting 3 levels deep costs n^3. At no point does it ever cost 2^n or any x^n. It's polynomial, not exponential.

Before I begin just let me say I am not a mathematician. I program so that I don't have to do complex math. I know in reality the frequency of iterations varies considerably but for simplicity of discussion let's remove variability. Say we have a loop with 1000 iterations. That is at minimum 1000 statements in the loop body plus expression overhead from the loop itself. If this loop is nested once with a same sized l…

>that example is exponential of 1000.

no it's not. you simply have the definitions mixed up. exponential slow down or speed up means a^x where x=1000. you are describing polynomial growth, i.e. x^a (where x=1000 and a=3)

Re: Speed Without Wizardry

#12

Earlier quoted context omitted.

> "It means minimizing use of nested loops, which exponentially increase statement count." Nesting two loops has an n^2 cost and nesting 3 levels deep costs n^3. At no point does it ever cost 2^n or any x^n. It's polynomial, not exponential.

Before I begin just let me say I am not a mathematician. I program so that I don't have to do complex math. I know in reality the frequency of iterations varies considerably but for simplicity of discussion let's remove variability. Say we have a loop with 1000 iterations. That is at minimum 1000 statements in the loop body plus expression overhead from the loop itself. If this loop is nested once with a same sized l…

You won't have random extra nested loops appear out of nowhere in your code

Re: Speed Without Wizardry

#13
I really don't understand this article, and the claims really rub me the wrong way.

The main point it makes is, again "He perfectly demonstrates one of the points my “Oxidizing” article was making: with Rust and WebAssembly we have reliable performance without the wizard-level shenanigans that are required to get the same performance in JavaScript."

This doesn't make a lot of sense as a claim.

Why? Because underneath all that rust .... is an optimizing compiler, and it happens the author has decided to stay on the happy path of that. There is also an unhappy path there. Is that happy path wider? Maybe. It's a significantly longer and more complex optimization pipeline just to wasm output, let alone the interpretation of that output. I have doubts it's as "reliable" as the author claims (among other things, WebAssembly is still an experimental target for LLVM). Adding the adjective "reliable" repeatedly does not make it so.

Let's ignore this though, because there are easier claims to pick a bone with.

It also tries to differentiate optimizations between the two in ways that don't make sense to me: "In some cases, JITs can optimize away such allocations, but (once again) that depends on unreliable heuristics, and JIT engines vary in their effectiveness at removing the allocations."

I don't see a guarantee in the rust language spec that these allocations will be optimized away. Maybe i missed it. Pointers welcome.

Instead, i have watched plenty of patches to LLVM go by to try to improve it's heuristics (oh god, there's that evil word they used above!) for removing allocations for rust. They are all heuristic based, they deliberately do not guarantee attempting to remove every allocation (for a variety of reasons). In general, it can be proven this is a statically undecidable problem for a language like rust (and most languages), so i doubt rustc has it down either (though i'm sure it does a great job in general!)

The author also writes the following: "WebAssembly is designed to perform well without relying on heuristic-based optimizations, avoiding the performance cliffs that come if code doesn’t meet those heuristics. It is expected that the compiler emitting the WebAssembly (in this case rustc and LLVM) already has sophisticated optimization infrastructure,"

These two sentences literally do not make sense together. The "sophisticated optimization infrastructure" is also using heuristics to avoid expensive compilation times, pretty much all over the place. LLVM included. Even in basic analysis, where it still depends on quadratic algorithms in basic things.

If you have a block with 99 stores, and ask LLVM's memory dependence analysis about the dependency between the first and the last, you will get a real answer. If you have 100 stores, it will tell you it has no idea.

What happened to reliable?

Why does this matter? For example: Every time rust emits a memcpy (which is not infrequent), if there are more than 99 instructions in between them in the same block, it will not eliminate it, even if it could. Whoops. Thats' a random example. These things are endless. Because compilers make tradeoffs (and because LLVM has some infrastructure that badly needs rewriting/reworking).

These "sophisticated optimization infrastructures" are not different than JITs in their use of heuristics. They often use the same algorithms. The only difference is the time budget allocated to them and how expensive the heuristics let things get.

There may be good reasons to want to write code in rust and good reasons to believe it will perform better, but they certainly are not the things mentioned above.

Maybe what the author really wants to say is "we expect the ahead of time compiler we use is better and more mature than most JITs and can spend more time optimizing". But they don't.

Maybe it would also surprise the author to learn that their are JITs that beat the pants off LLVM AOT for dynamic languages like javascript (they just don't happen to be integrated into web browsers).

But instead, they make ridiculous claims about heuristics and JITs. Pretending the compiler they use doesn't also depend, all over the place, on heuristics and other things is just flat out wrong. At least to me (and i don't really give a crap about what programming language people use), it makes it come off as rampant fanboism. (Which is sad, because i suspect, had it been written less so, it might be actually convincing)

Re: Speed Without Wizardry

#14

Earlier quoted context omitted.

Before I begin just let me say I am not a mathematician. I program so that I don't have to do complex math. I know in reality the frequency of iterations varies considerably but for simplicity of discussion let's remove variability. Say we have a loop with 1000 iterations. That is at minimum 1000 statements in the loop body plus expression overhead from the loop itself. If this loop is nested once with a same sized l…

>that example is exponential of 1000. no it's not. you simply have the definitions mixed up. exponential slow down or speed up means a^x where x=1000. you are describing polynomial growth, i.e. x^a (where x=1000 and a=3)

It is exponential in the number of nested loops, which is what's important for the realization that adding more nested loops is bad.

Re: Speed Without Wizardry

#15

The simple rule I have found for achieving superior performance in high level languages, particularly JavaScript is to simply do less . It isn't that simple though. Doing less really means less code totally at the current compilation target, essentially feeding fewer total instructions to the compiler. This means no frameworks and minimal abstractions. It means having a clear appreciation for the APIs you are writing…

One would have expected that the arithmetic assignments operators would have been faster as

a += 1 would only compute the address of "a" only once and then duplicate it, whereas

a = a + 1 would compute the address of "a" twice. For more complicated examples the first should see an even faster speedup.

So, from my perspective, there is a serious problem here in the optimiser.

Re: Speed Without Wizardry

#16
post #14

Earlier quoted context omitted.

>that example is exponential of 1000. no it's not. you simply have the definitions mixed up. exponential slow down or speed up means a^x where x=1000. you are describing polynomial growth, i.e. x^a (where x=1000 and a=3)

It is exponential in the number of nested loops, which is what's important for the realization that adding more nested loops is bad.

> "It is exponential in the number of nested loops, which is what's important for the realization that adding more nested loops is bad."

Adding more nested loops is bad, but it's not exponential. It's polynomial.

As you nest more and more loops, the big O complexity goes from N to N^2 (quadratic) to N^3 (cubic) to N^4, etc... N^(any number) is polynomial. Exponential would be 2^N or 3^N or any number raised to the N.

See: https://en.wikipedia.org/wiki/Exponential_growth

Re: Speed Without Wizardry

#17

I really don't understand this article, and the claims really rub me the wrong way. The main point it makes is, again "He perfectly demonstrates one of the points my “Oxidizing” article was making: with Rust and WebAssembly we have reliable performance without the wizard-level shenanigans that are required to get the same performance in JavaScript." This doesn't make a lot of sense as a claim. Why? Because underneath…

To address your question about allocations, in Rust, you always know when you are allocating on the heap vs on the stack. The way you write your code guarantees it. Stack allocations are “basically free” compared to the heap because the memory management overhead is negligible or nonexistent. There are also guarantees about when objects you allocate on the heap are freed; there is no garbage collection.

This is in contrast to JavaScript where objects are always allocated on the heap, and are garbage collected.

So Rust gives you a lot more control and flexibility about how memory is managed. This might or might not matter for your use case, of course. There are compiler optimizations and heuristics -on top of that-, to be sure, but you end up with a lot more guarantees about how your code executes, because that’s what the language is designed for.

EDIT: If you want to learn more about memory management in Rust, see https://doc.rust-lang.org/book/first-edition/the-stack-and-t...

Re: Speed Without Wizardry

#18
Funny, I was using the source-map library under Nashorn. The performance was poor enough that I had to switch to embedding V8; I'm not sure whether that was a consequence of Nashorn itself being too slow, or the Javascript optimizations intended for V8/Firefox just completely missing their mark.

Not that the WASM version of the library would've helped, since Nashorn doesn't do WASM at all. But maybe the performance would've been decent if it had.

Re: Speed Without Wizardry

#19

I really don't understand this article, and the claims really rub me the wrong way. The main point it makes is, again "He perfectly demonstrates one of the points my “Oxidizing” article was making: with Rust and WebAssembly we have reliable performance without the wizard-level shenanigans that are required to get the same performance in JavaScript." This doesn't make a lot of sense as a claim. Why? Because underneath…

I think the author's primary point was that the work done by mraleph required _deep_ knowledge of the V8 JIT internals and low-level profiling to get those "3x speedup" results, plus the algorithmic improvements. Meanwhile, the original Rust implementation got the same "3x" results without having to do deep analysis of how the compiler was behaving. It also seems (based on the commentary) that the way JS/JIT engines treat WASM bytecode is likely to require fewer special-cases or heuristics than plain JS.

Sure, the Rust compiler and the LLVM infrastructure are doing a lot of complicated work internally... but the end users of the compiler aren't having to spend time digging through the guts of it to guess what kind of magic sequences are needed to get fairly good performance.

Re: Speed Without Wizardry

#20

Earlier quoted context omitted.

> "It means minimizing use of nested loops, which exponentially increase statement count." Nesting two loops has an n^2 cost and nesting 3 levels deep costs n^3. At no point does it ever cost 2^n or any x^n. It's polynomial, not exponential.

Before I begin just let me say I am not a mathematician. I program so that I don't have to do complex math. I know in reality the frequency of iterations varies considerably but for simplicity of discussion let's remove variability. Say we have a loop with 1000 iterations. That is at minimum 1000 statements in the loop body plus expression overhead from the loop itself. If this loop is nested once with a same sized l…

I wouldn’t call time complexity particularly complex math. Imagine a loop. It does something n times. Within that loop, you do something n times. For each outer looo through n,you do an inner loop through n. Trivially, this is n*n, or n^2, also known as quadratic time. If you nest another loop, it becomes n^3, or cubic time.

Anyway, there might be some confusion of terms, perhaps. Exponential time in the algorithmic complexity sense is any algorithm that takes 2^n operations to complete. If you’re talking about the number of iterations after you loop through n things n times, then that increase itself would not be exponential. But that delta in iterations is unrelated to exponential and polynomial time complexity.

Post reply on HN