- macros: fancy syntax and "magic" can be resolved at compile-time. Less work to do on runtime.
- templates: they get handled at compile-time, too, resulting in functions with blobs of binaries. This matters a lot, since a specific template-binary exists only once throughout the application and gets re-used whenever needed. This way you get near instant templating, instead of the usual string processing on every request.
- dispatching code like the routing that must be done on every request is like rails a DSL, but actually a macro, that gets compiled down to basic pattern matches, which is extremely fast.
- the concurrency model is extremely lightweight, handling more requests and/or doing more stuff per request is much more efficient hardware-wise. Indirect performance gain.
Just a few points that have a very noticeably impact, there are probably more. It boils down to the erlang vm's concurrency and pattern matching performance, plus elixir's compile-time macros and the fact that they treat strings as immutable binaries.