Making the obvious code fast (2016)
jackmott.github.io
jackmott.github.io
The reason a language like C makes obvious code fast is because C has very little expressive power. The lack of power strongly encourages the programmer to think imperatively and code at a very low level. Apply this to an extremely trivial problem (mere addition - does it get any simpler?), and of course you end up with fast code.
The lesson doesn't generalize. The lack of expressive power means higher level abstractions are clumsy to write, refactoring is more expensive, and recomposing your program with e.g. a caching layer is much harder.
See https://blog.codinghorror.com/on-managed-code-performance-ag... for example (alas, it appears some of the original source blogs are gone) - Raymond Chen wrote a dictionary in C++, Rico Mariani implemented it in C# and and got great performance with the obvious solution.
Don't get me wrong, I like C - especially on small problems. I don't get the feeling that I'm creating something flexible with C, though, it's very much bespoke and tailored. I get involved in micromanagement of fine details. This is OK for a small problem, but extremely aggravating for a big problem. As soon as I want higher level abstractions, and start building structs of function pointers and whatnot, I just shake my head - I'd rather write a parser and corresponding text generator than manually compile that into C.
C++ has been hobbled by the concept of a sufficiently smart compiler. It's so afraid of introducing abstractions that aren't cost-free, it tries to make them all optional, and the resulting explosion in feature combination means some combinations don't work well together, and then every big C++ project has to decide which set of features they'll adopt, and which ones they won't.
Give me the freedom to think closer to the problem, and even if the abstractions have some costs, and even if the compilers aren't quite sufficiently smart, the chances are I'll come up with algorithm improvements that meet speed and space requirements and let me move on to the next problem much sooner. Agility trumps hyper-optimization.
Just today I rewrote my own streams abstraction, because libc sucks and I want to support my own printf style functions and my own custom streams (for example dynamically memory allocating streams. fopencookie() is not portable). I think that streams are one of the most abstract and most useful abstractions. It went pretty good. Making a few vtables explicitly in a few places is not that terrible.
The point is that with a JIT the runtime is executing as little as possible, with the rest being dynamically optimized machine code. So this isn’t really a fair thing to say…
That’s the keyword here.
I could create a sample program that would be hard to make as fast in C or C++, with the constraint that the code needs to be dynamically loaded at runtime.
In practice, it's easier to modify a more dynamic program and be sure you're not breaking much. Highly tuned C tends to bake in assumptions about memory lifetime, layouts, call graph, etc. The flexibility to modify is what gives you the ability to target higher level improvements in a shorter amount of time.
Given any concrete implementation, after it's been designed in a more dynamic language, you can convert it to something fast in something like C. But starting from C is probably not a great idea. And not everything can be written multiple times economically.
E.g. the .net GC was prototyped in Lisp, and initially converted to C++ with a translator.
They are there... it's just the recent migration at MSDN blogs broke a lot of links. Basically it looks like Rico doesn't blog anymore (no longer at Microsoft) so his blog is archived and links to it work, but Raymond Chen's blog is active and was moved en masse to new links.
The links from Rico Mariani's blog (https://blogs.msdn.microsoft.com/ricom/2005/05/10/performanc...) are here:
Raymond Chen's posts:
https://devblogs.microsoft.com/oldnewthing/20050509-30/?p=35...
https://devblogs.microsoft.com/oldnewthing/20050510-55/?p=35...
https://devblogs.microsoft.com/oldnewthing/20050511-46/?p=35...
https://devblogs.microsoft.com/oldnewthing/20050513-26/?p=35...
https://devblogs.microsoft.com/oldnewthing/20050516-30/?p=35...
https://devblogs.microsoft.com/oldnewthing/20050518-42/?p=35...
https://devblogs.microsoft.com/oldnewthing/20050519-00/?p=35...
So does C++ iterators for that matter but they are not so pleasant to actually write.
Also I believe the browsers have sped up some of the Javascript cases shown there substantially.
rust has no way to pass ffast-math flag (unless that changed recently) which will enable this for C/C++
What a delightfully clear, complete, and readable privacy policy.
Certainly it should be a goal, but I don't think it should be the goal. Higher levels of abstraction aren't typically motivated by performance. The priority is ease of expression and reasoning about code. Of course both are desirable, but when the two are in conflict and a trade off is necessary, designers will lean toward expressive abstractions, not performant abstractions. After all, performance is already available without the abstractions.
But I do agree it is super cool how Rust finds a sweet spot between performance and expressibility. It is one of my favorite languages for that reason. I just don't think it should be a universal goal.
for (int i = 0; i < x.length; i++) {
z[i] = x[i] * y[i];
}
(Above code copied from: http://prestodb.rocks/code/simd/ )But the reason it doesn't work for the article's code is because of the accumulator variable. That is not supported yet. See: https://bugs.java.com/bugdatabase/view_bug.do?bug_id=7192383
However, the difference between using sum() and reduce() are interesting. Will eventually have to JMH to verify if this is still the case and if it matters in our codebase.
As i said recently [1], the Java way is to sacrifice a little performance to allow users to do silly things and still get the right answer.
How you score that in terms of "support" I freely leave up to the reader. Go assembly does integrate into Go more than simply raw assembler would, as it handles some of the runtime concerns, so the "support" is more than just "yeah, we can write some stuff in assembler and link it in, I can do that anywhere", but on the other hand, it is a form of assembler, not something that looks like a function call in the native language or something.
<script>
let arr = new Float64Array(32000000);
let sum = new Float64Array(1);
sum[0] = 0;
let l = arr.length, i, el;
// Initialize with radom values
for (i=0; i<l; i++) arr[i] = Math.random();
// Sum
let st = performance.now();
for (i=0; i<l; i++) sum[0] += arr[i] * arr[i];
let en = performance.now();
console.log(en - st, sum[0]);
</script>
Using a temporary value for arr[i] is definitely slower.Node 8 especially, with turbofan.
[dup *] map sum
And given the definition of sum (the Joy combinator step is like fold), sum == 0 swap [+] step
There could be a (pre-)compilation transform that generated: 0 swap [dup * +] step
From which the proverbial "sufficiently smart compiler" would generate SIMD instructions. (I'm working on that now, in Prolog. At this point the type-inferencer can already tell that this function expects a list of numbers and returns a single number (it's a "Catamorphism"[1].) But I'm nowhere near specializing the output code to SIMD. My target architecture is Prof. Wirth RISC machine for Project Oberon. However, there's a body of research on Prolog compilation that includes work on retargeting machine code generator generators, so there's that.)[1] http://joypy.osdn.io/notebooks/Recursion_Combinators.html
With the advent of Span, stackalloc and the like, there has been something of a "war on allocations" in .NET recently - it's great to see some extra focus on performance!
I conjecture that apart from a few artificial benchmarks, the choice of the different approaches here is totally irrelevant. Just don't use the 10000ms approach, and you won't notice a difference in 99% of the real world applications.
Likewise Google, Azul and IBM have SIMD support on their JIT/AOT compilers.