R is an interpreted programming language - see https://cran.r-project.org/doc/FAQ/R-FAQ.html#What-is-R_003f. Hence you have an interpreter that interprets and executes subsequent lines as the code runs. The libraries in R that have good performance are simply written in C - see for an example the src
folder in the excellent data-tables libraries: https://github.com/Rdatatable/data.table
On the other hand Julia is a compiled language (and in the same dynamic and using multiple dispatch). Julia uses a LLVM compiler system to produce assembly from any piece of code before it gets executed.
Consider for an example this Julia function:
f(x,y) = x < y ? 2x : 5;
When Julia executes it for the first time it gets an assembly code and actually if you want you can see it:
julia> code_native(f, (Int, Int))
.text
; ┌ @ REPL[8]:1 within `f'
pushq %rbp
movq %rsp, %rbp
; │┌ @ int.jl:82 within `<'
cmpq %rdx, %rcx
; │└
jge L17
; │┌ @ int.jl:87 within `*'
addq %rcx, %rcx
; │└
movq %rcx, %rax
popq %rbp
retq
L17:
movl $5, %eax
popq %rbp
retq
nopl (%rax,%rax)
; └
This is a set of instructions directly understood by your CPU - no interpreter is involved. Should you decide to write this function in e.g. C and compile it you could expect very similar assembly lines.
Finally, note that this code is type specific - If you try code_native(f, (Int, Float64))
(so with different types) - you will get a totally different piece of assembly (try yourself!).
Hence the nature of interpreted and compiled languages is very different and R can only be fast when using external libraries written in C (or other compiled language).