I just finished my compilers course. One of the topics discussed was ways to make compilers more efficient. For example: tail recursion, inlining procedures, strength reduction, removing dead code, constant propagation. Considering that the main reason why C is more efficient than Python is because it doesn't have dynamic types and a garbage collector. These disadvantages can be removed during the optimization phase when the actual machine code is generated. So why, at the end of the day, is a language like C is more efficient than Python?
Why don't all languages have the same efficiency?
compiler;efficiency
Machine code doesn't magically make type checking irrelevant. The thing with many varieties of machine code is that they have no inherent understanding of types. But that doesn't mean you can't build a type system onto them by using your own conventions.As a trivial example, you could decide that every value is 16 bits. The first 8 bits represents the type, and the second 8 bits represents the actual value. Now you've got something you can check at runtime to verify you're not adding a horse to your latitude. Here's c=b+c in pseudo-assembly:  enter               // function entry  loadw [ax], bx      // load 16 bits at [ax] into bx  loadw [ax+2], cx    // load 16 bits at [ax+2] into cx  cmp   bl, cl        // compare the low bytes of bx and cx  jne   ERROR         // if ^ is not equal, jump to ERROR  addb  bh, ch        // add the high bytes of bx and cx, store in ch  storw cx, [ax+2]    // store cx back in memory  ret                 // return to callerERROR:  // Handle error, print warning, throw exception, etcThat's an example of the instructions a dynamically-typed language may compile down to (in this case, an actually pretty fragile language; consider that even if b and c are the same type, addition might be an outright nonsensical operation to perform on them. e.g GUID+GUID=huh?). Here's what a statically-typed language may do:  enter               // function entry  loadb [ax], bh      // load 8 bits at [ax] into bh  loadb [ax+1], ch    // load 8 bits at [ax+1] into ch  addb  bh, ch        // add bh and ch, store in ch  storb ch, [ax+1]    // store ch back in memory  ret                 // return to callerNotice that there's no cmp, jne, or error handling. Why? Because a statically-typed language can prove, without running the program, that an invalid pair of types will never enter that section of code. Therefore, it can safely elide the checking code. And since it doesn't check the extra type metadata, it can leave that out, too, hence why it's only loading and storing 8 bit bytes instead of 16 bit words.Similarly, machine code doesn't magically clean up your garbage for you. If you're using a garbage collector, it doesn't just disappear when compiling, it gets translated to the target language along with the rest of your program. But do note that a garbage collector is not necessarily less efficient than alternatives. malloc() is not necessarily deterministic.