That means you can do tricks that you just can’t do in a “higher” level language. Or you may be able to do but you have to tell the compiler “shut up I know what I’m doing.”
This just isn’t true. Yes, Julia provides you with protections by default, but when the compiler can convince itself they aren’t necessary, they’re silently dropped. Here’s a case where I’ve not told the compiler @inbounds
:
julia> function mysum(A)
s = zero(eltype(A))
for a in A
s += a
end
return s
end
julia> A = Float32[1,2,3];
Now check this with @code_native debuginfo=:none mysum(A)
. It’s not complicated, but to simplify even further here is the loop body:
L48:
vaddss (%rax,%rdx,4), %xmm0, %xmm0
incq %rdx
cmpq %rdx, %rcx
jne L48
This result has no bounds checking, because Julia’s compiler could prove it’s not necessary. The body of that loop is the same body you’d get from a good C compiler.
In C do you have a pointer to a structure but want to treat it as a pointer to an array of floats, no problem. Have a pointer to something but know that 57 bytes ahead is a structure of type Foo, not an issue. C trusts that you know what that pointer points at, just tell it and it will let you do it.
And so will reinterpret(reshape, Float32, A)
. (The reshape
is new in 1.6 and crucial to getting the same performance you’d get from C.)
Threading is just another area where C/C++ doesn’t get in the way with safety checks that may slow you down.
You could also wrap pthreads and use it directly. Last time I played with that, Julia was still in a state where you had to manually make sure all the code you wanted to run inside threads was already compiled—which indeed is not an issue for C, since it can’t run code interactively and compile on the fly. But even there, there really isn’t an in-principle difference between the two, just a pragmatic one.