One thing we C++ programmers know, though, is that direct control over instructions helps performance in small programs but can hurt it in larger programs. E.g. it is really hard to enjoy the performance benefits that moving collectors and JITs can bring in large C++ applications.
It's not so easy. The pattern you'd want to try and match or beat moving collectors is arenas, and they're really not trivial to use in C++ (or Rust). The only low-level language that supports them well is Zig. But even then, evolution of the program can often require large architectural changes.
So yes, in principle it's possible to match and perhaps somewhat exceed Java's performance in C++, even in large programs (just as, in principle, it's possible to match and exceed C++'s performance in Assembly), but we don't care about what's possible in principle; we care about what we can achieve with the budget we have. Or put another way, C++ has better performance/effort than Assembly, and Java has better performance/effort than C++ in large programs (perhaps not in all domains, but in important ones). The JVM was designed to (among other things) specifically make it easier to overcome some known performance problems that large C++ programs experience.
> we care about what we can achieve with the budget we have
100% agree on this. This is the last driver for everything else in professional environments.
Related: I think C++ safety being driven by profiles and not the Safe C++ stuff that was proposed violates the economic assumption in so many ways that it was the wrong choice for C++.
I meant the kind of "perf-to-the-instruction" thst native can generate vs other considerations.