Changes to implementation can absolutely have big effects on performance within a single language. CPython gets faster with most new version as the interpreter and the standard library improve, and you can step out CPython into Jython, or IronPython, or JITted PyPy, and see very different performance characteristics while remaining recognizably the same language. We might even conceive of an OCaml-like dual interpreter/compiler that can do both.
But there are differences in language semantics which mere implementation details can’t bridge.
A language whose semantics include loose or dynamic typing will place restrictions on the implementation that will reduce performance. A language whose semantics relies on dynamic dispatch without the ability to monomorphize will see unavoidable overhead. A language which abstracts away memory allocation takes a lot of optimization tools away from the program author, etc.
Changes to core language semantics like these would be extremely disruptive even in a whole-version migration breaking backwards compatibility. Adding required static typing to a dynamic language or manual memory management to a previously GC’ed language would be such a large difference that you almost certainly can’t call it the same language before and after.
You can of course write extensions in faster languages, but at that point you’re deliberately stepping outside your language’s established semantics because you’ve acknowledged that they’re too restrictive or insufficiently powerful to achieve your goals.
So I don’t agree that slow languages don’t exist. By virtue of their core semantics and the abstractions they aim to provide to the program author, all programming languages limit the performance they can deliver even with an ideal implementation.
A language whose semantics relies on dynamic dispatch without the ability to monomorphize will see unavoidable overhead.
I’m not sure there are languages that really can’t be monomorphized. In principle someone could implement such a dynamic language with a Self-style JIT that dynamically monomorphizes, so the code can be optimized to perform as well as static dispatch. But I suppose the JIT itself is unavoidable overhead :-)
Also, a dynamic language’s design can make it easier to monomorphize, by making it clear when the metalevel is being mutated (things like class definitions) and when those mutations take effect. But that kind of design is relatively rare: CLOS is designed that way, and I think Java too (tho Java relies more on dynamic reoptimization than CLOS).
A language which abstracts away memory allocation takes a lot of optimization tools away from the program author, etc.
Yeah, but I’d say memory layout is more important than allocation: if a program can control when subobjects are embedded in other objects and in arrays, and isn’t required to refer to them indirectly, then it’s easier to get better memory performance. This is one of the big advantages of Golang relative to Java. And project Valhalla, adding value types to Java, has taken many years of careful work to avoid being disruptive.
Data structure layout at a high level (how objects relate to each other, as opposed to the internal layouts of objects) is in almost all languages controlled by the programmer. Language semantics usually don’t allow data layouts to be changed by optimizers because it’s prevented by features like object identity and mutability and aliasing. So I think data layout is the primary thing that determines whether a language is “fast” in the sense of being able to get the most out of the hardware (assuming it isn’t held back by a slow implementation).
It’s totally possible to write benchmarks or inspection testing that would require heavy (e.g. whole-program) optimization. You’re assuming that all Haskell will always exhibit GHC’s limitations.
So no, you cannot guarantee people will pay a runtime cost for something that is testable and preventable before runtime.
But ~danso’s example is great because it’s megamorphic depending on the runtime value of n, so there isn’t a reasonable way to monomorphize it even for a compiler like jhc.
I understand it’s a difficult case, but if it is pathological, wouldn’t we be using a outlier to generalize from, it we pretended that this program needs optimization? If these programs are very rare. I suppose it all rests on what “code like this” means…
I don’t think this is correct.
Changes to implementation can absolutely have big effects on performance within a single language. CPython gets faster with most new version as the interpreter and the standard library improve, and you can step out CPython into Jython, or IronPython, or JITted PyPy, and see very different performance characteristics while remaining recognizably the same language. We might even conceive of an OCaml-like dual interpreter/compiler that can do both.
But there are differences in language semantics which mere implementation details can’t bridge.
A language whose semantics include loose or dynamic typing will place restrictions on the implementation that will reduce performance. A language whose semantics relies on dynamic dispatch without the ability to monomorphize will see unavoidable overhead. A language which abstracts away memory allocation takes a lot of optimization tools away from the program author, etc.
Changes to core language semantics like these would be extremely disruptive even in a whole-version migration breaking backwards compatibility. Adding required static typing to a dynamic language or manual memory management to a previously GC’ed language would be such a large difference that you almost certainly can’t call it the same language before and after.
You can of course write extensions in faster languages, but at that point you’re deliberately stepping outside your language’s established semantics because you’ve acknowledged that they’re too restrictive or insufficiently powerful to achieve your goals.
So I don’t agree that slow languages don’t exist. By virtue of their core semantics and the abstractions they aim to provide to the program author, all programming languages limit the performance they can deliver even with an ideal implementation.
score 4 · canonical
I’m not sure there are languages that really can’t be monomorphized. In principle someone could implement such a dynamic language with a Self-style JIT that dynamically monomorphizes, so the code can be optimized to perform as well as static dispatch. But I suppose the JIT itself is unavoidable overhead :-)
Also, a dynamic language’s design can make it easier to monomorphize, by making it clear when the metalevel is being mutated (things like class definitions) and when those mutations take effect. But that kind of design is relatively rare: CLOS is designed that way, and I think Java too (tho Java relies more on dynamic reoptimization than CLOS).
Yeah, but I’d say memory layout is more important than allocation: if a program can control when subobjects are embedded in other objects and in arrays, and isn’t required to refer to them indirectly, then it’s easier to get better memory performance. This is one of the big advantages of Golang relative to Java. And project Valhalla, adding value types to Java, has taken many years of careful work to avoid being disruptive.
Data structure layout at a high level (how objects relate to each other, as opposed to the internal layouts of objects) is in almost all languages controlled by the programmer. Language semantics usually don’t allow data layouts to be changed by optimizers because it’s prevented by features like object identity and mutability and aliasing. So I think data layout is the primary thing that determines whether a language is “fast” in the sense of being able to get the most out of the hardware (assuming it isn’t held back by a slow implementation).
score 3 · canonical
I don’t believe e.g. Haskell typeclass instances can always be monomorphised:
pathological :: (Show t) => (Integer, t) -> String pathological = \case (0,x) -> show x (n,x) -> pathological (n-1,(x,x))Anybody who writes code like this is going to pay a cost in runtime performance, one way or another.
score 2 · canonical
It’s totally possible to write benchmarks or inspection testing that would require heavy (e.g. whole-program) optimization. You’re assuming that all Haskell will always exhibit GHC’s limitations.
So no, you cannot guarantee people will pay a runtime cost for something that is testable and preventable before runtime.
score 1 · canonical
But ~danso’s example is great because it’s megamorphic depending on the runtime value of n, so there isn’t a reasonable way to monomorphize it even for a compiler like jhc.
score 1 · canonical
I understand it’s a difficult case, but if it is pathological, wouldn’t we be using a outlier to generalize from, it we pretended that this program needs optimization? If these programs are very rare. I suppose it all rests on what “code like this” means…
score 1 · canonical