If you're going to spend the time to implement an algorithm in C++ at least take the time to make it cache aware. Otherwise it's not really surprising when the performance is the same when you're doing the exact same thing.
Would be curious to see what the performance looks like with a tightly packed scene and static dispatch on a discrete number of primitives instead of using virtual functions.
It seems like the listed OCaml program has a default level of 9 where as the C++ one uses 6?
Times using original listing (minus print stmts):
$ time ./ray-cpp
real 0m3.369s
user 0m3.347s
sys 0m0.010s
$ time ./ray-ml
real 0m4.710s
user 0m4.703s
sys 0m0.003s
Time after changing the 9 in the ocaml program to a six: $ time ./ray-ml
real 0m4.032s
user 0m4.026s
sys 0m0.003s
Seems like C++ has a good edge. Above was ran on a 64 bit machine.Lastly the virtual call again has the chance to cache miss(although probably only once seeing as we've just got a few impls).
In a small example like this there's a good chance that most of the heap allocations are close together but put this approach into production and you can very easily see fragmentation start driving up your cache misses.
(FWIW I'd also store radius*radius on creation, use a BSP/kd-tree and all sorts of other tricks that really help in these types of computations).
These days with C++11's auto, this is improved a lot. It's not full type inference, but eliminates most of the boilerplate, and arguably what's left you really want to keep for documentation purposes.
> The object-oriented representation of a trivial sum type as an inheritance hierarchy of classes is needlessly inefficient.
This use case strikes me as a particularly bad example of sum types: The author wants to represent different kinds of scene members, like spheres and groups, as a sum type. Using a sum type means that anyone wanting to add a new kind of object has to add it to the sum type and handle it everywhere, which will quickly become problematic. Using object-oriented design allows new object types to be introduced without updating everything.
Basically the sum type looks nicer here only because it's a toy example and we're not considering how it will evolve.
That said, I do think sum types are really useful in some use cases, especially in compilers, and I'm sad C++ doesn't have them. I tend to end up defining ASTs using Cap'n Proto as a convenient way to declare a tagged union, even though I have no intent to serialize the structures.
> No need to write constructors for tuples, records and variant types in OCaml.
To be fair, for an all-public struct (as in the example), you don't need constructors in C++ either.
Once you have private members, then of course you need a constructor.
> Native lists in OCaml.
C++11 initializer lists make it basically possible to write list/vector/array classes that "feel native".
> Higher-order functions instead of loops in OCaml.
C++11 lambdas, etc. However, in a lot of cases, loops are easier to read.
Would be nice to see Rust's Option<T> and Result<T,E> ported over to C++ since I find that error handling extremely elegant compared to most C++ approaches.
They're quite nice, especially with a few trivial additions for ergonomics.
The actual algorithmic lines are very similar in size and readability but when it comes to defining your data structures, F#/OCaml clearly wins.
The author of the website(Jon Harrop) is a very good writer and though his books are expensive I'd recommend them to anyone learning OCaml or F#.
Specifically F#/OCaml for scientists is awesome( it comes in variants for both languages) as is F# for Numerics.
Saying nothing about comparing LOC in an imperative/OO language to a functional one, does the brevity actually help a reader's understanding of the code at all? It seems to me that a lot of the comparisons call out descriptions of explicit actions in C++ where OCaml does the same action implicitly.
That seems like a language trade-off more than a feature.
In usual applications however most people don't realize that the development time should also be considered. In that sense a program written in a few lines of Python could actually outperform a super optimized C++ program because it usually requires more time and effort to code the same thing in C++.
> With two cross-overs, there is little difference between the performance of the OCaml and C++ implementations. Note that the C++ was compiled with CPU-specific optimizations whereas the OCaml binary will run on any 486 compatible.
Ok, so the author is suggesting numerical C++ compiler generated SSE2+ code is comparable to OCaml 486, and thus with only very slow stack based x87 FPU available? SSE beats x87 hands down even without vectorization enabled, using a single float/double (scalar) per XMM vector register! That should make a significant difference in any ray-tracer worth 2 cents.
I didn't bother reading the source code, but I think it's very likely he didn't write C++ code like you should for a performance sensitive numerical application.
Although maybe the code for scene graph, bounding box and hit test was bad? FPU performance starts to matter once you actually find the ray intersecting object...
Anyways, usually if you do use C++, you do so because of specific performance requirements. Not to have the shortest or cleanest possible implementation.
Within functions, the OCaml compiler can assign floats to registers, so those would also be unboxed at least for the duration of the function.
[1] https://realworldocaml.org/v1/en/html/memory-representation-...
https://github.com/phresnel/metatrace
Performance 100%!
OCaml is a really clean language though!
The author may have assumptions I'm missing here but the standard way to have a "one of these or one of these" thing is to have two classes inherent from a given abstract base class and use a pointer to the base class.
If one wants the device specifically as a type, one could wrap the base-pointer in a class.
Is there something I'm missing?
Some of the things that make me miss OCaml variants/sum types when I have to use C++ subclassing/dynamic dispatch instead: * very lightweight syntax both for creating them and matching on them (and lightweight representation; variants without arguments are just ints under the hood) * compile-time checks on exhaustiveness in pattern matches (so no unexpected NULL's!)
Note that OCaml also allows open/polymorphic variants for the cases where you want to allow expanding the list of options – here the compiler will only check that e.g. a function that matches on only `A|`B won't be sent a `C, although it can be sent a member of a type that contains only `A's. This is typically used for public interfaces where you want to let your implementation expand later (e.g. your current function accepts `UTF8|`UTF16 but you might later decide to accept `WTF8 as well).
I think this is what the author describes in the sentence that follows the one you quoted. He also calls it "a common (ab)use of object oriented programming."
Between C++14 and some template usage, I'd imagine the LOC could be reduced a lot.
OCaml? Uh... there's F# for realists, Scheme for optimists, and Haskell for pedants. If you're actually writing OCaml web apps in OWebl I'd sure love to know why. Amazon released a native C++ SDK for AWS if you're into pain. If you're using "OPAM" to install the OCaml AWS client... wow.
OCaml is totally a practical language for writing real world applications. As I usually point out in these threads, Red Hat pay me to write OCaml programs[3], that are used by thousands of customers and many more free software users worldwide. The advantages (over other languages, not C++) are: easy linking to C libraries, and builds a native binary with no extra dependencies, speed. Advantages over C++: safety, robustness, compact source code size.
[0] https://opam.ocaml.org/packages/
[1] https://ocaml.org/meetings/ocaml/2014/OCaml2014-Leroy-slides...
Red Hat could pay someone to do exactly what you're doing in many other languages with the exact same results. The only difference would be that the language in question would show up on the StackOverflow list of "most loved" or "most wanted" languages. Rust shows up in 50th place with less than 0.2% share and OCaml doesn't even make that list (TIOBE 9/2015).
Sounds argumentative, but is a genuine question: why have you chosen OCaml for this? What is so remarkable about it for this use case that 50+ more popular languages wouldn't do it?
Obviously there are downsides, so what is the upside?
OWebl, in spite of its shiny website, is not what I would call "popular". You want to look at opium[1] or ocsigen/eliom [2].
Don't know what you have against OPAM, it's the best language-specific dependency manager I know.
1: https://github.com/rgrinberg/opium 2: http://ocsigen.org/eliom/
Personally, I would love to use OCaml for my company's projects, but the lack of true cross platform tooling makes it impossible at the moment.
There are other (good) web frameworks in OCaml (ocsigen, opium).