IIRC GNU's /bin/true also has massive performance advantage. I am not able to find it presently, but an old post tried to reverse engineer it, and turns out it does caching and whatnot to get something like 12 GBps throughput.
And yet you might still be right: If an empty file gets parsed and executed by the shell[1], the shell's initialization code might make things much slower than a small C program.
[1] Which is a valid assumption, as it's often the default (not sure if it's POSIX[2]), e.g. this gets clearly executed by my shell despite not having a shebang or anything else:
The real reason why things are fast in popular shells is because they implement most of coreutils as builtins. The /bin/true being empty file would prevent it from working when called with exec for example, so that's why we don't use empty files anymore.
From quick scan of v7 exec() implementation it seems that it has to be case as getxfile() would return ENOEXEC for files smaller than sizeof(u_exdata) (which is a.out header as C struct).