Question about Crystal, Compiled Code, and Performance

It’s my understanding that Crystal code is compiled with LLVM into native machine code. If that’s the case why is code written in C and compiled in GCC faster than Crystal code compiled with LLVM? If they’re both native machine code shouldn’t performance be the same?

What are the codes that you are comparing?

1 Like

Just benchmarks from the book Programming Crystal, C always outperformed Golang and Crystal in every example.

I guess we’d have to see the codes, but in general go and Crystal allocate more memory than C, and C is older than Crystal and Go so they might have more optimized algorithms.

1 Like

LLVM has some overhead that C doesn’t have, plain and simple. I would highly recommend you check out kostya/benchmarks, look at the memory used by Crystal, it’s a lot more than C in some cases. That’s a huge potential bottleneck since the computer has to obviously allocate, write, and read that memory to do things. There are cases where Crystal barely underperforms compared to C, in Matmul’s case, a mere 0.06 seconds difference.

At the end of the day, LLVM turned into compiled ASM is NOT the same as C to compiled ASM. If you were to take a C program, compile it to LLVM, and then compile it to machine code, you’ll probably see similar speed issues, but of course, the LLVM emitted by Crystal is going to be fundamentally different than LLVM emitted by C code, so it’s a little hard IMO to directly compare the two like they are supposed to be the same. Crystal is going to have applications where is meets C speeds/memory profile, or just barely under performs it, and there is even times where C might have a larger memory profile than Crystal.

It all depends on the optimizations and methodologies used by either language.

Although take what I say with a grain of salt, I’m no master of LLVM.

EDIT: made a mistake, meant to say compiled to native machine code instead of C code

I’m not sure that’s entirely correct. LLVM can compile C (this is clang) and it generates code that matches, and sometimes outperforms, the performance of gcc.

The thing here is that Crystal has a GC, arrays are a reference to a pointer (while in C they are just pointers), in C everything’s a struct and in many cases you don’t allocate heap memory for them, in C strings can be mutable so you can reuse memory, etc.

I know about clang, what I meant was that you can’t write a piece of code in Crystal and emit LLVM and expect it will be exactly the same as an identical piece of LLVM emitted C code. If I’m not mistaken, the two piece of code will have some big differences, like GC, std classes, etc.

For example, I wrote two programs, one in Crystal, and one in C, both of them do about the same thing, print the string "HELLO!\n"

test.cr

print "HELLO!\n"

test.c

#include <stdio.h>

int main()
{
    printf("HELLO!\n");
    return 0;
}

I then made each emit LLVM-IR using the following commands

crystal-vs-c$ crystal build test.cr --emit llvm-ir -o test-cr.ll
crystal-vs-c$ clang -emit-llvm -c test.c -S -o test-c.ll

The difference between the two is night and day, the C code ends up being a succinct 25 lines of LLVM-IR where the Crystal code comes out to around 70000 lines of LLVM-IR. I tried emitting LLVM without the prelude but, it won’t work since it can’t find print without it.

GC/reference overhead are both good points though, I didn’t think of that.

Again not a master of LLVM so maybe I’m missing something else but, I would love to learn more about LLVM internals from someone who works with it all the time. :slight_smile:

The overhead in this example comes mostly from Crystal’s stdlib runtime, not LLVM.

The following Crystal code resembles the C implementation more closely:

require "lib_c"
require "c/stdio"

LibC.printf pointerof("HELLO!\n".@c)

Compiled with --prelude=empty --no-debug this emits 33 lines LLVM IR and works pretty similar as the C example with only minimal overhead.

Obviously, you wouldn’t want to write a larger program like this. And for writing any serious application, you will need some kind of libraries and enhancements to a minimal runtime. Crystal’s stdlib just provides a lot of features you’ll probably need anyway, right from the start.

2 Likes

Whoa, didn’t know you could require C’s stdio like that in Crystal!

Yeah, with just the compiler, no stdlib you can essentially write C code with Crystal syntax.

3 Likes

could we reduce the amount of the stdlib data that gets sent to LLVM? Would that be a long term goal? For example if we load JSON - github.com/crystal-lang/crystal of the stdlib, we might not also need to load Random - github.com/crystal-lang/crystal if this isn’t being considered already, could we track this as a long term goal to minimize CPU Cycles and RAM use by a Crystal Program?

I’m not sure what you mean. The compiler already skips code that is never called. If your program doesn’t use random, it won’t be compiled into the executable.

3 Likes

Was referring to this, is the entire standard library not included? If so then already resolved.

The entire stdlib is available by default, but only the parts that are actually used will be compiled into the binary.