There is definitely an efficiency argument between let and let*. But the main reason why we have let is historic, due to the relationship with lambda.
let is easier, simpler and more efficient to implement in a code-walking Lisp interpreter. This is particularly true if there is some half decent data structure for environments, rather than just an assoc list.
Suppose that the interpreter implements environments as a chain of objects. So for isntance (let (a b) (let (c d) (let (e f)))) will add three environment nodes to the environment chain. Each of these new nodes contains two bindings (in an individual list, or hash table or whatever).
When we interpret the let form, we can evaluate all of the initializing expressions in the incoming environment chain. We can create a single environment node for all of the new bindings, in a single operation, and populate the bindings with the values.
When we interpret the let* form, we cannot do this. For each successive binding mentioned in the let*, we have to call make-environment, populate it, and add it to the environment chain, so that we then interpret the next initialization form in the extended environment.
This leads to a degenerate run-time environment structure. Whereas (let (a b c d ...)) produces one environment object with a nice hash table in it, (let* (a b c d ...)) produces an inefficient chain that requires O(n) traversal to find a binding.
We can erase the difference between the interpreter performance of let and let*, but only by dragging the performance of let down to let*. If we represent the environment chain as a naive assoc list, then this issue doesn't matter; all variable lookups are a linear search. In fact let* is then easier to implement: evaluate each init expression, and push a new binding onto the current environment.
Now, enter compilation into the picture. A Lisp compiler can use a devilish trick to implement let* by just making a few tweaks to the compilation strategy for let. To compile let*, we can allocate a single environment for all the bindings (a move which would result in incorrect scoping in the interpreter). We leave that environment empty, and add it to the compile-time environment chain. We thus compile the init expressions in the scope of that new environment. As we iterate over the init expressions to compile them, we add each corresponding variable to that environment one by one, so that the compilation of subsequent init expressions will have that variable in scope.
let* is a simple hack that becomes obvious when you have a Lisp compiler that handles let.
A Lisp compiler easily produces efficient representations of environments, regardless of scoping rules, which is not necessarily true of an interpreter.
Since interpreters came first, that explains why let is parallel. At least partially. The other reason is that let was realized as a syntactic sugar over lambda. But lambda (originally) oes not have initializing expressions at all in its own scope; it just specifies variables. The lambda expression produces a run-time object, such that the binding of the values to the parameters happens at run time, when the function is invoked. The evaluation of the argument expressions is in a totally different scope.
Now in the immediately-called lambda, this is still true: the scope of the expressions is entirely outside of the lambda:
((lambda (a b) (+ a b)) 1 2)
The expressions 1 and 2 have nothing to do with the lambda; they are not enclosed in it.
So it is obvious that if we want a let sugar notation which corresponds to the above, we must carefully preserve this property:
(let ((a 1) (b 2))
(+ a b))
If we want this let to be the same as the previous lambda, we must make it look as if a and b are function parameters, and 1 and 2 are argument expressions.
If you're a researcher working with a language that has lambda and no let, longing for a nicer way to write immediately-called lambdas, it is unlikely that you will invent let* binding semantics. You will invent something that has a clear translation strategy to the existing construct you are using all over your code, so that you can refactor the code to use it without surprises.
Note that the modern lambda in dialects like Common Lisp does have embedded expressions in it: namely optional and keyword arguments!
(lambda (a &optional (b x) (c y) ...))
These default value expressions x and y are evaluated in the surrounding lexical scope, whenever the argument is missing, every time time the function is called. And, so, what scoping discipline do these expression use? Why, serial, not parallel!
[1]> (defun foo (x &optional (y (1+ x)) (z (1+ y)))
(list x y z))
FOO
[2]> (foo 10)
(10 11 12)
So, things went full circle. In the beginning, there was LAMBDA. LAMBDA begat LET. LET begat LET*, and LET* begat newer LAMBDA with sequential binding for optional argument init-forms. :)
The result is that translating a modern immediately-called lambda into let is quite complicated. For instance:
(funcall (lambda (x y &optional (z x) (w (1+ z))) a b c)
can compile into:
(let ((x a) (y b)) ;; we put the fixed params into a let
(let* ((z c)) ;; z arg present, so refer to c, not x
(w (1+ z))) ;; w arg absent, use (1+ z)
...))