In a thoughtful comment to my post analyzing Apple’s refusal to allow non-native apps–the ones built with Flash, .NET, any type of cross-compiler, etc.–on its devices, Andrew Martin argued:
I would argue that knowing a “lower-level” high-level language like C would help a developer write more efficient programs (regardless of how efficiently the compilation occurs). Of course this isn’t necessarily true in all cases
It’s an interesting point and, since it didn’t fit the discussion on the page, I moved it over here.
The argument that knowing a way to program at a lower level of abstraction helps you write more efficient programs is as old as programming. Assembly-language programmers said that to C programmers, C programmers said it to C++ programmers, Java programmers now say it to Ruby programmers, etc.
Efficiency is a complex metric with as many constituent parts as there are resources one cares about in a given situation: time, space (RAM, virtual memory, disk space), I/O, OS-level resources (processes, threads, handles, semaphores, ports, channels, …), etc. To understand the issues, one has to look at what aspects of a programming language affect efficiency (however this is defined). Some key factors are the built-in constructs, data structures, frameworks. For example, C++ templates allow for compile-time optimization that is practically impossible to replicate using C. An extreme example of this is template meta-programming, which I’ve had some great results with. The same is true of some Objective-C capabilities such as dynamic binding and message passing, which are not practically replicable in C. Another, and very different, example would be Flash which is very good at vector graphics, animation, etc. It doesn’t just have built in libraries that support this–its programming model, which includes things such as frames, and runtime environment, down to its bytecode architecture if I’m not mistaken, have special, optimized constructs that support these capabilities. Matching that using, say, Java would require excellent graphics/animation libraries and, even then, the fact that the JVM architecture is not optimized for vector graphics & animation may be a significant disadvantage.
Another factor, and reason why it is becoming less and less true that knowing a lower-level language helps a programmer write more efficient programs, is the quality of optimization, be that static (compile-time) or dynamic (run-time). People have a very limited ability to perform the type of optimizations software can. We don’t have the information management power to do static analysis (code & data flows as well as instruction/data layout) well and we tend not to have the time and skills to do dynamic analysis (profiling), especially when one considers the added complexities of such things as varying workloads, parallelism and predictive execution. Worse, our psychology drives us to premature optimization.
It is legitimate to ask what does the last point have to do with lower vs. higher-level languages since both are benefiting from better compilers. The answer lies in the division of responsibilities. In a lower-level language like C it is often much more difficult for a compiler to make big assumptions about the impact of code execution on data structures. In a higher-level language, the compiler and/or runtime environment have more responsibilities but that also allows them to do more because they, and not the developer, are in control and that gives them the flexibility to move data structures around, to parallelize without fear of race conditions and unsafe data structure access, to not recycle memory until necessary, etc. For example, a dynamically-optimizing garbage collector may beat C memory management, even if the C program is compiled using a dynamically optimizing compiler.
In the future, the main way to write more efficient programs that don’t just work through massive but relatively dumb parallelism will be to use programming models that maximize the expression of intent with the minimum of implementation-specific constraints, allowing the hardware and software around developers’ code to maximally help.
Let me know what you think in the comments or on Twitter @simeons.
“In a higher-level language, the compiler and/or runtime environment have more responsibilities but that also allows them to do more because they, and not the developer, are in control”
Excellent point, I had not thought of this before! The traditional argument that higher-level languages have more overhead (and are thus less efficient) fails to account for this fact that the compiler can, in certain situations, be smarter than the programmer by seeing intricate patterns. I suppose this really depends on the size and complexity of the application, but as applications continue to evolve and become more complex, I could see more and more of this type of work being “passed off” to the compiler.
I guess it also depends on the scope of the optimization. Having a poor understanding of memory management and efficient algorithms and thus writing poor-quality code will still produce an inefficient application – no matter how sophisticated the compiler. Do higher-level (more accessible) languages put programmers in the drivers set before they’re as experienced as necessary? I’m not sure. But by forcing the programmer to think about and manage these things himself/herself, C-type languages can produce more efficient “big-picture” code.
This type of analysis, by definition, needs to look at alternatives while making the same assumptions about developer skills.
I learned programming on Apple ][ clones. It was a very constrained system which forced its developers to be smart. I learned all kinds of tricks that made me a better developer. Did I do it while hacking in Assembly? No, I started programming in BASIC.
The experience a developer gains has a lot less to do with the programming language they are using and more to do with the problems they are solving.
I’m not sure what you mean by “big-picture” code. Lower level languages make it harder to think about the big picture because there are too many details to take care of. The classic example here is garbage collection. Even Apple introduced it in Objective-C 2.0. Historically, the biggest and most consistent contributions of languages with explicit memory management have been crashes and memory leaks.
I think your experience is a perfect illustration of my point. Being on a constrained system or using lower level languages helps you learn tricks and think about things you would normally not even consider. I’m not saying that only good developers only program in lower level languages, but that having had that experience makes you a better programmer regardless of which platform
My experience has been that you are right: often the built in capabilities and modern dynamic optimizations of high level languages can exceed what programmers can do in practice with lower level tools. But…
Too often, predictability is lost. A few years ago my team built pretty much the same high performance system (an XML parsing system) in both C and Java. We hand tuned each separately. Mostly the C was somewhat faster, but that isn’t the point. The real difference was that when the performance of the C was surprising, it took a few minutes to look at the generated code and see what the compiler was doing. With Java, it often took days or weeks. Was some simple code being inlined or not? Only after the JIT had awhile to run a few times, or early? Where was the generated code anyway — just finding it was very difficult and very platform-dependent. I could tell very similar stories of PL/I optimizations from 30 years ago.
I’m not against modern high-level languages, but do prepare for some serious headache if you can’t figure out >why< they aren't performing as you expect
Good point, Noah. One needs smarter profilers for the less predictable languages.