> 1. Some of the algorithms I use have the potential
> to be parallelized. I am hoping that as the number
> of cores in PCs increase, at some point Clojure's
> performance will beat C++'s due to Clojure's
> superior utilization of multiple cores. (Any ideas
> on how many cores are needed for this to become
> true?)
It all depends on your algorithms and your code. Clojure has lots of
support (data structures and algorithms) for concurrent programming,
but you have to choose and combine them yourself to get efficient
parallelization.
> 2. The JVM is continually being improved. Hopefully
> in a year or two, the performance of HotSpot will be
> closer to that of C++. (But maybe this is just
> wishful thinking.)
Again this depends a lot on what you are doing. Hotspot is already
doing an impressive job on some applications, but remains bad at
others. Optimization of a JIT compiler is quite different from
optimization by hand: the compiler knows nothing about your algorithm
at large that could help it to speed up execution, but on the other
hand it knows where optimization is really useful and it can create
specialized code versions that no sane person would ever attempt to
write by hand.
> 3. Maybe I can implement certain performance critical
> components in C++ via the JNI. (But I get the impression
> that JNI itself isn't particularly efficient. Also, the more
> I pull over into the C++ side, the fewer advantages to
> using Clojure.)
I also wish there were a better interface between the Java world and
the C world than JNI. Perhaps there is? I'd happily trade some safety
for performance.
One source of hope for better interfaces is new virtual machines. One
candidate is VMKit (http://vmkit.llvm.org/), an implementation of the
JVM (and .Net as well) on top of LLVM. Combine this with the gcc
version that produces LLVM code, and it should be possible to get
Java-C interoperability with much less friction than through the JNI.
On the other hand, it will be a long time before VMKit matches the
performance of HotSpot.
> If all else fails, maybe I could use Clojure as a prototyping
> language. Then when I get it right, I code up the actual
> programs in C++. But probably a lot would get lost in
> the translation from Clojure -> C++ so would it be worth
> it?
You could also consider writing Clojure code that generates C++ code.
Or C, or why not directly LLVM.
> I'd love to be convinced that Clojure is a viable choice,
> but I need to be a realist too. So what do people think?
I am in a very similar situation if I ever want to use Clojure in my
professional environment. For the moment I have decided that Clojure
is fun enough to explore even if I never will use it professionally,
so it's not an immediate concern. Time will tell what can be done
realistically.
Konrad.
> I know that there are other functional approaches where
> the compiler automatically finds ways to parallelize. Is there
> much scope for this in Clojure? Or is it all pretty much
> manually added concurrency?
I am not aware of any auto-parallelizing compiler that is actually
useful in practice. All useable parallelization schemes rely at least
on hints from the programmer, and most leave the whole specification
of parallelism to the programmer.
One might say that data-parallel systems are auto-parallelizing, but
they aren't really: the programmer has to define the data that is
being distributed, which counts for me as a parallelization hint.
Clojure does nothing at all, it just provides an infrastructure for
pure functional programming that makes concurrency and parallel
computing easier to implement than most other languages.
> And this leads to another question I have about Lisp-style
> languages in general. I get the impression that in Lisp
> much of the "compiler" is Lisp code itself. Ie, layer and
> layer of macros build up towards the language that then
> actually gets "used". Does this layer upon layer of macros
> lead to inefficiencies in itself? Or is the opposite true?
Macros are handled at compile time, so they add no run-time overhead.
There remains the question of how good the compiler can optimize the
expressions resulting from macro expansion. But the same question
applies to any other compiler, as most of them first transform the
input language into some simple intermediate language and then
optimize at that level. The difference with Lisp is just that the
simple intermediate language is a subset of the full language.
> That seems to match what I've read. On average I get the
> impression that it is about 2 times as slow as efficient C++ code
> and uses maybe 10 times as much memory.
I wouldn't trust any "average"!
> What I'm wondering is whether there is still quite a bit of
> "fat to be trimmed" with HotSpot, or whether it's approaching
> the limits of what is achievable.
JIT technology being young and in rapid development, it would be
risky to do any prediction there.
>> You could also consider writing Clojure code that generates C++ code.
>> Or C, or why not directly LLVM.
>
> Thanks. Worth considering. Though I don't really know
> whether this is a practical option or not. Maybe it is.
There are efficient real-life libraries that use this approach. The
best known is probably FFTW, which consists of optimized C code
generated by functional OCaml code. I have no doubt that Clojure
could take the role of OCaml there. Of course, writing such an
optimizing library requires a significant effort.
Konrad.
>> Macros are handled at compile time, so they add no run-time overhead.
>
> But isn't "compile time" sometimes _at_ runtime? Ie, if
> a macro is dependent on runtime information, then isn't
> it passed to "the compiler" which is included as part of
> the runtime executable, turned into bytecode, and then
> executed itself. That's how I thought it worked anyway.
A macro cannot depend on runtime information. A macro is a function
that is called at compile time, its argument is an expression (as
written by the programmer, or as returned by another macro), and its
result is a modified expression. There is no way a macro could access
runtime information. It is a program that works on program code, not
on runtime data.
Konrad.
This is primarily what I was going on. I realize no
benchmarking approach is going to be perfect, but
this attempt doesn't seem too bad. Is there any
reason to be particularly sceptical about results
found here?
> Then what do people mean when they say that lisp blurs
> the distinction between compile-time and run-time?
I don't know. I can only guess that they may be referring to the fact
that many Lisp dialects have both an interpreter and a compiler. Or
to the fact that macros (executed at compile time) can call functions
that are also available at runtime. Or to the fact that in
interactive sessions in a purely compiled Lisp (such as Clojure),
every expression gets compiled on the fly just before being
executed. Or again to the fact that you have the function eval, which
calls the compiler at runtime.
Anyway, in Clojure there is a clear distinction between compiling an
expression, i.e. producing equivalent JVM bytecodes, and executing
it, i.e. running the JVM bytecode.
> I thought macros could get executed at runtime as
> follows. Suppose I have a domain-specific-language
> implemented in a lisp, using macros. Suppose at
> runtime some of this domain-specific-code gets
> generated somehow (code is data at this point).
> An "eval" is executed on this domain-specific-code,
> which causes the macros (used to implement the
> domain-specific-language) to transform the input
> code at runtime.
>
> Is this wrong?
You can do that but it is rarely done. DSLs are typically implemented
as ordinary functions and macros without calling eval. Eval is there
for very specific needs, such as implementing interactive
interpreters. And even in those specific applications, I wouldn't
expect the CPU time requirements of eval to be important.
> I also thought that when a "clojure application" is
> bundled up as java bytecode, this "executable"
> actually includes the clojure compiler. Why would
> this be included if compilation (including macros)
> is never performed at runtime?
As long as you want to be able to call eval, you need the compiler to
be around. I guess most Clojure applications could be bundled up
without including the compiler, but perhaps this is too much effort
to be worth pursuing.
Konrad.
Not sure if it makes for this algorithm, but there are faster (and
less safe) versions of several functions you're using. Check the
output of (find-doc "unchecked")
While I think your loop-local 'c' will be a primitive int, I'm not
entirely sure -- might be worth trying (int (unchecked-add x
line-offset)). Same thing on line-offset, not sure it's a primitive.
--Chouser
> 1- If you think that HotSpot/Java is slower than C++ by any
> interesting amount, I'd love to see the test case. Being the
I have to admit that I haven't done many tests, but for those I did I
found little to no difference, provided the tests have sufficiently
long run times.
However, what remains a problem for with the Java/HotSpot approach to
performance is the absence of a clear (even though necessarily
approximate) performance model. When writing C or C++ code, I have
some idea of the approximate cost of everything. Of course there are
differences between machines and compilers, but those rarely cause
drastic differences in performance, rarely more than a factor of 2.
On the JVM, I don't know what HotSpot can optimize and what it can't,
and I can't predict the impact of object allocation and garbage
collection. I suppose JVM experts can do better than me, but where
can I learn about all that?
Konrad.