ARMv7 vs. x86-64: Pathfinding benchmark of C++, D, Go, Nim, Ocaml, and more
github.com
github.com
(defun get-longest-path (nodes node-id visited)
(declare (optimize (speed 3) (space 0) (debug 0) (safety 0)
(compilation-speed 0)
#+lispworks (fixnum-safety 0))
(type fixnum node-id)
(type (vector node) nodes)
(type (vector atom) visited))
(setf (aref visited node-id) t)
(Let ((max (loop for neighbour of-type route across (node-neighbours (aref nodes node-id))
unless (aref visited (route-dest neighbour))
maximize (the fixnum
(+ (the fixnum (route-cost neighbour))
(the fixnum (get-longest-path nodes (route-dest neighbour) visited)))))))
(declare (fixnum max))
(setf (aref visited node-id) nil)
max))
Above Common Lisp version improves the runtime from 8.5 to 3.6 seconds in SBCL and from 30 seconds to 2 seconds in LispWorks 64bit. Computer: i7 Mac mini.*Edit: and, it's done.
https://gist.github.com/lispm/6066e1eeadf943910c47
You might want to adapt it...
Edit: I tested the C++ version on my 5-year-old i7, with an even older compiler (just had to modify the code to not use C++11 features), and with the max optimisation level, it produces a result of 1465ms - which is pretty damn amazing, considering that this is a 16-year-old compiler generating 32-bit code and the most recent CPU it had knowledge of was the Pentium Pro (P6)! I'm convinced that an Asm version could be <1s though, so there's still plenty of room for improvement.
I wonder how much of the difference we're seeing between various languages is the quality of the code their compilers generate for various backends and how much is due to the different languages benefiting more from architectural differences between the two chips.
I imagine that languages that generate code with more indirection are going to excersize the prefetcher and branch predictor of the core they're running on much more than languages that generate code with simpler control flows. Both the A9 and the Nehalem cores are out of order but the Nehalem has a much, much more sophisticated set of facilities for that. I predict that if you were to re-run the benchmarks on an iPhone 5S you'd see much less of a difference between the various ARM times. And if you were to run it on a cheap Android phone with A7 or A53 cores you'd see a much larger difference.
The timings presented are also confusingly presented for my taste.
int getLongestPath<16>(std::vector<node, std::allocator<node> > const&, int, std::bitset<16>)
Which is fine, but seems a slightly limited platform / language comparison!http://caml.inria.fr/mantis/view.php?id=5433
The new backend is supposed to be considerably faster on floating point code. This code looks integer only, and I don't have relative performance of old/new backend for integer code.
As a wider question: Who cares much about ARMv7? ARMv8 is a completely different beast, requiring a different backend, with much better raw performance (on the same terms as x86-64). That's where languages should be concentrating their current efforts.
I've been working on fixing bugs in the ARMv8 backend in the OCaml compiler - it's now stable, and performs very well (I can't go into exact performance details for contract reasons).
http://caml.inria.fr/mantis/view.php?id=6489
http://caml.inria.fr/mantis/view.php?id=6284
http://caml.inria.fr/mantis/view.php?id=6283
(and more ...)
If Android will do - Cortex-A53 smartphones like the Huawei Ascend Y550 cost 120€ nowdays in Europe (about 4x the price of a Raspberry Pi).
- Far too little memory (2GB vs 16/32GB+)
- Slow flash vs SATA disks
- The "zoo" of u-boot/proprietary kernel crap, instead of UEFI, ACPI and standard upstream kernels
- Nonsense like locked bootloaders (Nexus 9 disables HYP mode in the bootloader!!!)
IORefs involve locking. They are bad performance-wise. An algorithm like this should be done either fully functionaly without any mutation at all or in ST.
Poking through GHC's runtime source, I see a writer barrier but no locking -- did I miss something?
And Nim doesn't look bad in the other statistics (on an x86_64 Intel Core2Quad Q9300):
Lang Time [ms] Memory [KB] Compile Time [ms] Compressed Code [B]
Nim 1400 1460 893 486
C++ 1478 2717 774 728
D 1518 2388 1614 669
Rust 1623 2632 6735 934
Java 1874 24428 812 778
OCaml 2384 4496 125 782
Go 3116 1664 596 618
Haskell 3329 5268 3002 1091
LuaJit 3857 2368 - 519
Lisp 8219 15876 1043 1007
Racket 8503 130284 24793 741
Code size with gzip -9 < nim.nim | wc -c. Removed unused functions in Haskell.I was curious how fast Javascript would run this, so I ported the lua example over, it ran in 2200msec on my mac laptop. I'm not sure it's a great comparison benchmark however, because there are some obvious optimizations you can make. I added a simple cache to avoid recalculating travel costs for leaves and it now runs in 150msec.
var fs = require('fs');
var nodes = [], visited = [];
var visitCache = {};
function readPlaces() {
var lines = fs.readFileSync('agraph','utf8').split('\n');
var numnodes = lines[0]|0;
for(var i=0;i<numnodes;i++) {
nodes[i] = [];
visited[i] = false;
}
for(i=1;i<lines.length;i++) {
var line = lines[i].split(' ');
nodes[line[0]|0].push({dst: line[1]|0, cost: line[2]|0});
}
}
function getLongestPath(nodes, nodeid, visited) {
visited[nodeid] = true;
var neighbours = nodes[nodeid];
var max = 0
for(var i=0;i<neighbours.length;i++) {
if (!visited[neighbours[i].dst]) {
var dist = neighbours[i].cost + getLongestPath(nodes, neighbours[i].dst, visited);
if (dist > max) {
max = dist
}
}
}
visited[nodeid] = false;
return max;
}
function getLongestPathCached(nodes, nodeid, visited, depth) {
var idx;
visited |= 1 << nodeid;
if (depth == nodes.length - 7) {
idx = (visited * 32) + nodeid;
if (visitCache[idx]) {
return visitCache[idx];
}
}
var neighbours = nodes[nodeid];
var max = 0
for(var i=0;i<neighbours.length;i++) {
if (!(visited & (1 << neighbours[i].dst))) {
var dist = neighbours[i].cost + getLongestPathCached(nodes, neighbours[i].dst, visited, depth+1);
if (dist > max) {
max = dist
}
}
}
if (idx) {
visitCache[idx] = max;
}
if (typeof visited != 'number') {
visited[nodeid] = false;
}
return max;
}
readPlaces();
var start = Date.now();
var length;
if (nodes.length < 32) {
length = getLongestPathCached(nodes, 0, 0, 0);
} else {
length = getLongestPath(nodes, 0, visited);
}
console.log(length+' LANGUAGE Javascript: '+(Date.now() - start)); function getLongestPathCached(nodes, nodeid, visited, depth) {
var idx;
visited |= 1 << nodeid;
idx = (visited * 32) + nodeid;
if (visitCache[idx]) {
return visitCache[idx];
}
var neighbours = nodes[nodeid];
var max = 0
for(var i=0;i<neighbours.length;i++) {
if (!(visited & (1 << neighbours[i].dst))) {
var dist = neighbours[i].cost + getLongestPathCached(nodes, neighbours[i].dst, visited, depth+1);
if (dist > max) {
max = dist
}
}
}
visitCache[idx] = max;
return max;
}Key takeaway for me is that statically typed languages that are compiled to native code are still 2-3x faster than the fastest JITs. On both platforms.
https://github.com/logicchains/LPATHBench/blob/master/jv.jav...
Instead of a vector of node classes, there's a
static final int[][] nodes;
Which is used in a similar manner to a vector of node classes, but due to containing primitives (ints) is unboxed.http://benchmarksgame.alioth.debian.org/u64q/performance.php...
If you take a Racket program and change the language to Typed Racket, you'll get flooded with compiler errors. Or at least that's been my experience.
>Racket is not Scheme anymore
I'll update the post to note this, thanks.
That's because when using `#lang typed/racket` you literally change the language you're writing and that includes the syntax of some very fundamental forms (like define or struct).
You can mix typed and untyped code freely as long as they are separated by module boundaries. Something like this:
#lang racket/base
(module with-types typed/racket
(provide a)
(: a (-> String Integer))
(define (a x) 0))
(module without-types racket
(require (submod ".." with-types))
(provide a))
But it's definitely not as convenient as Erlang's Dialyzer, TypeScript, Dylan or similar success/gradual/occurrence/soft-typing solutions for sure.removed the box improve performance from 10s to a 9s.
#lang typed/racket
(struct: route ([dest : Integer] [cost : Integer]) #:transparent)
(struct: node ([neighbours : (Listof route)]) #:transparent)
(: str->int (String -> Integer))
(define (str->int str)
(define n (string->number str))
(if n (numerator (inexact->exact (real-part n))) 0))
(: read-places (-> (Vectorof node)))
(define (read-places)
(define lines
(file->lines "agraph"))
(define num-lines (str->int (car lines)))
(define nodes (build-vector num-lines (lambda (n) (node `()))))
(let loop ([i : Integer 0])
(define nums (string-split (list-ref (cdr lines) i)))
(define len (length nums))
(when (and (> len 2) (> (length lines) (+ i 2)))
(let ([node-id (str->int (list-ref nums 0))]
[neighbour (str->int (list-ref nums 1))]
[cost (str->int (list-ref nums 2))])
(define new-node (node
(append (node-neighbours (vector-ref nodes node-id))
(list (route neighbour cost)))))
(vector-set! nodes node-id new-node)
(loop (+ i 1)))))
nodes)
(: get-longest-path ((Vectorof node) Integer (Vectorof Boolean) -> Integer))
(define (get-longest-path nodes node-id visited)
(vector-set! visited node-id #t)
(define sum
(foldr
(lambda ([neighbour : route] [max : Integer])
(if (not (vector-ref visited (route-dest neighbour)))
(let ([dist (+ (route-cost neighbour) (get-longest-path nodes (route-dest neighbour) visited))])
(if (> dist max)
dist
max))
max))
0
(node-neighbours (vector-ref nodes node-id))))
(vector-set! visited node-id #f)
sum)
(define nodes (read-places))
(define visited : (Vectorof Boolean) (build-vector (vector-length nodes) (lambda (n) #f)))
(define start (current-inexact-milliseconds))
(define len (get-longest-path nodes 0 visited))
(define duration (- (current-inexact-milliseconds) start))
(printf "~a LANGUAGE Racket ~a\n" len (inexact->exact (floor duration)))Point being, if you want to benchmark language backends, it doesn't make a lot of sense to cross chips without making mulch-dimensional benchmark (i.e adding in clock or power vectors).
After rereading closely, the intent of the article is saying "50% slowdown for backend X on x86, 70% slowdown for backend X on ARM." The subtle difference is that you are keeping comparisons of slowdown in the family and only making analogy to the other arch by slowdown percentage.
It's still susceptible to ISA implementation (for instance an in order Atom might fare a lot worse for typical backends), but mildly interesting.
However, I would have liked to see Julia and Javascript benchmarks in those results. I've heard great things about Julia, and knowing just how incredibly far we've brought the Javascript VMs over the past decade, it wouldn't surprise me to see Javascript fairly high on the list.
Just to clarify, it performed as well as the Oracle JVM on x86. Its poor performance on ARM is just due to its lack of JIT compilation.
>However, I would have liked to see Julia and Javascript benchmarks in those results.
I'm happy to include Javascript or Julia implementations if someone supplies them. I wasn't comfortable with Julia enough to write one myself.
> Just to clarify, it performed as well as the Oracle JVM on x86. Its poor performance on ARM is just due to its lack of JIT compilation.
Understood. Admittedly, I'm a system administrator first, developer second. However, my experience has shown that Sun/Oracle JVM usually out performs OpenJDK. Even in development, on the Java teams I've had to support, OpenJDK is never preferred or wanted.
Further, RHEL ships the OpenJDK compiled with the GNU compiler for Java (GCJ), as well as GNU's classpath. From what I've seen supporting these Java teams, and not being a Java developer, it's unstable and slow.
So, in this specific instance, OpenJDK may have performed as well as the Sun/Oracle JVM on x86-64, but in the broader scope, it doesn't seem to hold up. Just my experiences though. Take it with a grain of salt.
I would argue it's actually risky.
Almost every Java developer uses the Oracle version because (a) it is what is recommended for OSX which is a popular development platform and (b) the bundled tooling is much better than OpenJDK. Hence you shouldn't mix/match JVMs just in case you hit implementation differences.
Your experience seems to be filtered through your misconceptions. It is the same code base.
I am not a Java developer. I am a system administrator.
http://openjdk.java.net/projects/code-tools/jmh/
Also -XX:-PrintCompilation -XX:-PrintGC
https://wikis.oracle.com/display/HotSpotInternals/MicroBench...
What is the difference between FSharp and F#?
I'm willing to trade a little performance and do a little extra error checking if it means I can just do:
GOARCH=arm go build
and then copy the binary over.The Arch Linux package repository proved really awesome here: for the vast majority of things (everything on x86) I could just `pacman -S myLang`. The hardest to install was SBCL on ARM, as I needed another Common Lisp to compile it, so had to get ClozureCL and use that.
That was just a momentary bit of weirdness, vec[i] has since returned.
EDIT: Further down, the author acknowledges that this has been fixed. That's what I get for commenting before scrolling the whole way down!
g++ cpp.cpp -std=c++11 -Wall -O2 -march=native -o cpp
I was originally using -O3 but then someone found that -O2 is actually faster in this case.Another things that I'm not sure you can even do anymore is disable bounds checking by adding "-gcflags -B" to the compile.
Not a good habit to get into though.
Q1: Why do you need an adjustable array?
Q2: Why do you need a structure with a single slot (node)?
If you want to benchmark a piece of code, please write a nice version and then optimize it. How can I reason about a benchmark result if the code is not understandable?
The snarky tone comes from the fact that people often post "benchmarks" including languages they can't program in resulting in a misrepresentation.
E.g. Lisp jumped from the bottom to the center (as expected) of the benchmarks after somebody donated a sane implementation.
https://github.com/logicchains/LPATHBench/blob/master/lisp.l...
* SBCL can produce impressive x86 code
* It's ARM branch is pretty new, so it but 30% isn't too bad.
I find this a bit odd. Microsoft should be able to build a better compiler. I thought F# was getting a lot of traction.
I had a look at the F# code and it's not all that efficient (there are a number of intermediate data structures being created that don't need to be). I'll have a go at optimizing the code to see if I can improve the performance.
It was running on Mono, not the Microsoft CLR.
Besides, C wasn't tested. C would win if it were, I'm pretty sure.
* They've alienated many Firefox users thanks to many bad UI changes. They've continued to do this even after the users have strenuously objected to these unwanted changes. Firefox's share of the market has thus dropped from 35% to probably sub-10% these days.
* Many of the remaining Firefox users still point out that Firefox is slower and more bloated than other browsers. Although Mozilla often rejects or ignores these complaints, my years of software development experience have taught me that when many users say there's a problem, there very likely is one, even if we the developers can't reproduce it.
* Firefox for Android hasn't been picked up by many users.
* Firefox isn't an option on iOS. (Although I guess we can't fully blame Mozilla for this.)
* Firefox OS is floundering. The devices available so far have fared very poorly in reviews. Some of these reviews are among the harshest I've ever seen for any software or hardware product.
* Thunderbird is on life support.
* Rust is still pre-1.0, and will be like this for several more months, at the very least.
* Servo depends on Rust, so it being a viable option is still years away.
* There was that whole Eich debacle. It was pathetic, no matter how you look at it.
* Bugzilla is long forgotten these days.
* Despite absolutely massive funding from Google and now Yahoo, Mozilla hasn't managed to put out any other product that people actually want to use.
When I look at that track record, it's just one failure or disaster after another. It's not top-notch at all. So I'm not surprised that they have trouble using C and C++. They seem to be having severe trouble with pretty much everything they do!
1) Regardless of whether anyone thinks Firefox's UI changes have been for the better, it's not possible to alienate Firefox users via UI changes because Firefox still offers the most customizable UI of any browser you've heard of. What are users going to say? "Damn you Mozilla, you made your browser look just like Chrome! I hate that so much, I'm switching to Chrome!"
2) Many of the remaining Firefox users point out that they have switched back to Firefox because Chrome has become bloated and slow. (Personally I think that everyone making this argument, on all sides, merely fails to appreciate what sort of benefit it brings to a browser to have a totally fresh user profile.) In overall benchmarks of memory usage and browser engine/Javascript engine speed, neither Chrome nor Firefox is significantly better by any significant margin.
3) Firefox for Android has between 50 and 100 million downloads on Google Play, and has a higher user rating than Chrome for Android (4.4 to 4.2).
4) Mozilla has announced earlier this month that they'll be shipping a Firefox for iOS, but given the crippling of third-party browsers on iOS I doubt Firefox will be any less hobbled than Chrome for iOS, and will certainly be worse than Safari.
5) I have no sales stats on Firefox OS, but given that they're still persisting in setting up new carrier partnerships I'd say they're better off than at least the Ubuntu phone. I'll probably never need a Firefox OS phone, but honestly if it weren't for the audacity of Mozilla trying to penetrate the OS market (and hence trying to end their reliance on the willing participation of third-party platforms to host their browser (which isn't so "willing" these days with the advent of locked-down platforms like iOS and WinRT (is that still even a thing?))), then I'd have already written off Firefox as dead in the water.
6) Thunderbird was never a moneymaker nor key to Mozilla's strategy, especially after the meteoric rise of web-based email clients.
7) Rust is the most interesting systems language to emerge in years, and its influence will be felt on every future systems programming language to come (though I am certainly biased here).
8) Servo is Mozilla's other project, aside from Firefox OS, that is so unbelievably audacious that I can't help but cheer them on. I have spoken with its developers and they're all astounded with the performance they're seeing, though they're holding off on releasing concrete numbers until the feature set is comparable with more complete browsers. Having seen the Servo devs in action, I can assure you they are on top of their game.
All this said, your original point was that you don't think that Mozilla has a top-notch team of C++ developers. And here's the thing: all of this is irrelevant to whether or not Mozilla's C++ developers are top-notch. I bet John Carmack's team at Id software was as top-notch a team of C++ developers as will ever be assembled, and yet Rage was still a commercial failure. To know whether or not the team is good, you have to look at their code and you have to look at their process.
In other words, personally, you are incorrect. FF no longer "still offers the most customizable UI of any browser you've heard of". (Simple enough: FF removed options that PM kept. Hence, FF is not more customizable than PM.)
And considering where firefox was five years they have done a remarkable job at trimming it down, in particular in memory usage.
I don't understand why this myth persists, despite the fact that Fortran has been beating C at several benchmarks for as long as C has existed.
int getLongestPath(ArrayList<node> nodes, int nodeID, boolean[] visited){
visited[nodeID] = true;
int dist, max=0;
for(route neighbour: nodes.get(nodeID).neighbours){
if (!visited[neighbour.dest]){
dist = neighbour.cost + getLongestPath(nodes, neighbour.dest, visited);
if (dist > max){
max = dist;
}
}
}
visited[nodeID] = false;
return max;
}
The ArrayList of nodes could be changed to an array, but I don't imagine that'd be much faster (I've always heard that ArrayLists are just as fast as arrays, except for primitives).*Edit: someone found a massive improvement, by replacing the node class with arrays, to simulate unboxing.
ArrayList<route> neighbours = nodes.get(nodeID).neighbours;
for(int i=0;i<neighbours.size();i++){
route neighbour = neighbours.get(i);
Also add -Xbatch as a parameter which gives another few percentage points.Original comment is on proggit. Went from 1600ms to 900ms.
http://www.reddit.com/r/programming/comments/2pvf68/armv7_vs...
You should include both your old java code with the new one. Call your version "enterprise java" and the other "optimized java". If anything, that could be useful to people coding in java and having performance issue ( otherwise they'll have to look for it in git history, which they'd have no reason to).
I've added a note regarding the change, but I'm off to bed in a moment (AEST timezone). I'll separate the Java versions tomorrow.
lpath-clang3.5-O2 8981 LANGUAGE C++ 763
lpath-gcc4.7-O2 8981 LANGUAGE C++ 769
lpath-gcc4.8-O2 8981 LANGUAGE C++ 746
lpath-icpc14-O2 8981 LANGUAGE C++ 750
lpath-icpc15-O2 8981 LANGUAGE C++ 735
lpath-clang3.5-O3 8981 LANGUAGE C++ 734
lpath-gcc4.7-O3 8981 LANGUAGE C++ 943
lpath-gcc4.8-O3 8981 LANGUAGE C++ 946
lpath-icpc14-O3 8981 LANGUAGE C++ 664
lpath-icpc15-O3 8981 LANGUAGE C++ 655
The last column is the time reported by the program in milliseconds. What this shows is that the same source compiled with Intel's icpc 14 or 15 -O3 is about 50% faster than the same source compiled with g++ 4.7 or 4.8 and -O3, and about 20% faster against g++ -O2. The point isn't that Intel's compiler is so much better, but that this degree of variation is normal for a benchmark like this. There are times when Clang or GCC will come out ahead by the same margin. The lesson is that you aren't benchmarking source code, you are benchmarking a particular compiler with particular options running on a particular processor.The article is wonderfully specific about what was used, but one should be very careful extrapolating to different combinations. In addition to the compiler differences, note for example that although I did my tests on a processor running less than 1.5x faster, I got runtimes that were almost 2-3x faster. Most likely, this is because the Haswell processor I tested on is more efficient than the several generation old Westmere that the author used. There's nothing right or wrong about either choice, but the degree of difference is why it's always important to specify.
I glanced briefly at the code with 'perf record -F10000', and my quick conclusion was the the Intel version was running faster because it was making better use of the branchless cmov's than the other compilers, and thus has 10,000,000 fewer branch prediction errors. At 20 cycles per miss, this accounts for over half the difference between icpc and the others. The use of the new fast variable shift instructions (shlx) and the once-again fast bit test (bt) instruction is probably the rest of the difference.
The difference between g++ -O2 and -O3 seems to be that the -O3 version is doing almost everything off the stack rather than in registers. It's bad enough that this is probably a performance bug rather than intended behavior.
Measurement is highly specific -- the time taken for this benchmark task, by this toy program, with this programming language implementation, with these options, on this computer, with these workloads.
Same toy program, same computer, same workload -- but much slower.
Measurement is not prophesy.
I'm missing something -- where does it say which version of JDK was used?