In general I would say there's no good single book or resource that describes everything comprehensively. There's a lot of resources, though, but mostly scattered in various places.
The BEAM Book [1] is a good, though unfinished resource talking in general about the implementation - the memory model and the interpreter.
If you're interested in some very low-level details of the runtime, the internal documentation [2] also holds a lot of interesting details.
There are also some additional details on internals at Spawned Shelter [3].
I would say it's mostly that nobody optimized it (perhaps yet) for that environment. There's generally nothing inherently slow in the VM boot sequence - it's just that this was not a priority, so it's slow. There are some attributes suggesting it could be a good fit for that environment, for example, the VM is generally very small - you can get a full system in ~20MB - and that again wasn't something that was heavily optimized.
That's probably not true in the recent versions - in OTP 20 the zlib integration was reworked and is now based on NIFs (similar to Java's JNI) instead of port drivers.
Given that NumPy is implemented in C and only with bindings to Python, you can do exactly the same on the BEAM and indeed it exists (though not widely used): https://github.com/versilov/matrex
I can't stress how important that is. We have about 1.5k tests in our Phoenix app and they almost all hit the database - they run in 30 seconds. I compare this to a Rails app where 1k tests run 8 minutes. Having a fast test suite makes development so much easier and makes you rely more on tests.
Absinthe is completely separate from that project. I think the whole community effort is around Absinthe at the moment and it's shaping into a great library. There's also a PragProg book coming out in Autumn "Craft GraphQL APIs in Elixir with Absinthe" [1].
Yes. That's not changed. All NIFs (regular or dirty ones) are executed directly in the context of the VM. A safe option would be a port - a regular program where you communicate through stdin/stdout. Ports allow representing such program (running in a separate OS process) as something equivalent to a native Erlang process.
There's also work on supporting writing NIFs in Rust, which gives some degree of additional safety. The relevant project would be: https://github.com/hansihe/rustler
Dirty schedules are about functions implemented in C - called NIFs (Native Implemented Functions) - a sort of FFI you'd find in most languages.
Because of preemptive nature of Erlang, doing lengthy work in those functions can destabilise the system (the "magic value" is said to be around 1ms). That is because C functions can't be preempted in the middle of execution like Erlang ones can.
Using dirty schedulers lifts this time limitation, but gives a higher constant overhead when calling a function on a dirty scheduler, since it means switching OS thread. This tradeoff, however, is perfectly acceptable for a lot of cases.
And yes, Erlang has good & highly concurrent HTTP libraries implemented in Erlang.
Erlang has a per-process GC - each Erlang process has it's own heap and it's own garbage collector. There's no VM-wide GC. This usually means the GC pauses apply only to a small part of your system, while the other parts are running completely normally. There's a good article that describes it in depth: https://www.erlang-solutions.com/blog/erlang-19-0-garbage-co...