I am happy about the 'demise of low level programmer'. Why? Because for me programming is more about algorithms/data structures than circuits, microprocessors and caches. I am happy that I didn't have to write mouse handlers, keyboard handlers - because have I started doing that, I'd either got bored before managing to do something useful, or changed my passion towards low-level stuff before writing my first quicksort. I am also happy that I can write something without understanding all mechanismes behind it - that I can build some site with django not even knowing how http works, or play with some graphics having no idea about graphic cards. Sure, all of this stuff is interesting - but it's just too much! Low level guys did their job well, so now people who don't like machines so much can build cool stuff on top of that.
I don't want to praise ignorance - probably I will read some of the stuff he linked under the article - I just think it's ok that people can do something useful with computer not knowing all the details about it. It's worth knowing though - but just as you can have no idea about physics of sound and how piano works and write nice songs, you can build nice stuff with high level tools not knowing low level.
> Low level guys did their job well, so now people who don't like machines so much can build cool stuff on top of that.
Did, and are doing, and, optimistically, will continue to do. The job is not done, in fact, it will never be over.
I think that what is happening is that the low-level programmers are becoming outnumbered by you high-level programmers. That's OK, and, in fact, that's how it should be.
I agree with you on most counts, but when you traverse your 2d array one way versus the other and are left scratching your head as to why one of them is slower, it's usually because something is going on in there that you don't know about, that the abstraction didn't make clear.
We need those low level programmers. We also need high level programmers to know a little about this stuff. However, we don't need everyone to be a low level programmer, and that's why the current state is an improvement over the past.
Knowning the proper way to traverse an array is basic knowledge that can be explained in 10 minutes. Seriously, cache theory is not complicated and even the highest-level programmer ought to know about it.
That's a huge step from knowing the number of instructions that fit in a trace cache of a CPU or when loop unrolling becomes optimal. Cache theory and understanding how IEE 754 are basics and are fairly universal. It's completely different from programming a fast subroutine in assembly optimized for a specific processor generation.
Yes, this is exactly the point of abstraction layers, and abstraction layers are precisely why I love computer science and software engineering so much. With abstraction layers, we can create, maintain, and use systems that are inconceivably complex.
In my experience, it's useful, even when writing high-level applications, to be aware of the relative cost of low-level operations. The "Numbers Everyone Should Know" slide from this deck is a reasonable starting point: http://research.google.com/people/jeff/stanford-295-talk.pdf To generalize a bit, the small numbers at the top of that chart are mostly a concern for people doing systems programming, but as you progress down the list you'll find operations costly enough to have a noticeable impact on application programs. E.g., if you build a typical web application in a high-level language, your user won't be able to tell if you add a hundred prediction-resistant conditional branch instructions or a hundred L1 cache misses per page view; but if you add a hundred network round trips per page view they'll observe a measurable slowdown. Similarly, if you're making a game and you want to display graphics at 60 frames per second, you can do quite a large amount of computation per frame, but you can't read a file from disk on every frame.
Abstractions are awesome. But when you tune for performance, the abstractions break down.
This is not inherent, as abstractions could have fully specified the performance characteristics of their primitives. However, most don't, so to get great performance out of your tools, you typically have to see directly through the abstraction layers.
When you write a quicksort algorithm, it is pretty important that you know about cache lines and the relative costs, or your quicksort will not be nearly as good as the one that does take that into consideration.
Of course, what counts as "low level" depends on your perspective. There are plenty of programmers today who don't spend much time working on data structures or fundamental algorithms (e.g., quicksort). Most commonly used languages have a decent sort routine in the standard library(or at least one that isn't horrible). Unless it's so slow that it forms a bottleneck, there's no reason to treat it as anything other than a black box. The same is true of data structures. Today's languages come equipped with tons of collection types (queues, vectors, hash tables, sets...), interfaces to SQL (and noSQL) databases and key-value stores, and so on. The days when all you had were arrays with integer indices are past.
There are lots of very good and detailed examples in this article, and some very nice links for those interested in the actual low-level stuff.
But I am not sure where a lot of things he notes were ever "taught" in universities or formally to those of us who were in the trenches. Much of the hard-core low-level stuff was learned either on your own, or when you got into an environment where it was necessary to know.
There are some additional skills that are falling by the wayside as well. One is how to optimize instructions on a type 650 rotating drum computer. Or more realistically, how to program co-routines in an interrupt-driven environment. Or how to write a floating-point division routine if you are working on a 286 target that does not have a 287. Or what order to write items to a disk and where to place them to minimize arm movement in a real-time data-driven system.
I admit that some of these are actually less necessary now due to technology being replaced, or machines being ridiculously faster.
But I was around when those technologies were necessary and useful (well, not the 650, but almost). We built a real-time system in assembler that had to be seriously well-optomized, and we had to modify the operating system to remove hazardous a latency that was fogging our 2ms data sampling rate.
But you know, simultaneously with that were COBOL programmers who had to be schooled in the idea that writing backups to tape was a good idea.
So there always has been a gap--this article suggests that this is something new.
Having said all that, this is an excellent article, as it is a challenge to programmers who would be better, and has a forest of valuable links to learn more. (This site now bookmarked.)
I graduated 10 years ago, in Computer Engineering, and much of this low level stuff was covered in core classes, such as Microprocessors & Interfacing, Operating Systems, and Computer Architecture. If one only wanted to study "higher level concepts", or couldn't pass, you switched to computer science...
Part of the reason why there are so few low level programmers is that it's quite complicated to actually find information on it:
* Lots of college courses, especially of the Java school variety simply do not cover anything remotely near low level code.
* For people looking to learn themselves, there are far fewer books and websites with resources than for other subjects. If I walk into my local book shop, there are books on Java, C++, C#, PHP (lots of PHP), Flash and Python. Then a few language agnostic books like Code Complete and Design Patterns (Stuff like this is lacking in quantity, but at least it's there). None on anything low level. There isn't even a x86 assembly for dummies type book there. The internet has free books for other languages too (Dive Into Python, Why's Poignant Guide to Ruby, etc..) and plenty of free resources on good functional programming or good OOP. For assembly, all I keep running into is a wikibook, which I'm rather skeptical about as they tend to pretty poor in my experience.
And then, once you've gone through the initial learning phase, how are you going to get any experience? For 95%+ of projects, it's a case of YAGNI. There aren't any interesting open source projects coded in assembly today. Maybe a few Linux drivers, but that's something that would need a lot of domain knowledge on top of low level knowledge to understand.
All you need is an assembler (a language with a built-in assembler helps with the learning curve) and the processor manual (ideally the processor manual's assembler syntax will match your assembler's syntax; this is not the case for e.g. the defaults in GNU as and Intel x86 or x64). Build a timing loop (timing instructions like rdtsc are right there in the manual) and get to profiling instructions. Read up, and find out about the hidden performance models behind the various instructions (and rework your timing loop to reset these things where possible); read up on caches, branch prediction, register renaming etc., then play around, try to reproduce the positive and negative sides of these optimizations and develop intuitions etc.
A timing loop will tell you how fast it runs on your particular configuration, but not how fast you can expect it to run in the general case. For that, you need to get very intimate with the processor manuals, especially optimization guides. Also, a good profiler, like Intels VTune, is great for getting low level performance data such as cache misses.
Also, doing what you suggest on a number of combinations of hardware would be useful, so you can compare various processor architectures.
> Lots of college courses, especially of the Java school variety simply do not cover anything remotely near low level code.
That's probably true for CS courses, but a lot of courses in my computer engineering undergrad years touched low-level stuff. The most relevant are the real-time systems, microprocessor architecture, and configurable processors courses. We had to unroll loops, reorder instructions according to data dependencies, and otherwise optimize assembly routines on paper during exams (based on a description of the architecture and the number of cycles for each instruction). Having learned that it's rather easy to pick up a book on whatever processor interests you and start writing.
While in general I agree with the fundamental issue in this post, I can't help but think each time that people, when they write lists like this, write down the things they themselves know very well. And then call those 'indispensable for all programmers out there'. Yet every single list is different, because everybody encounters different specific details in their careers. I guess the only conclusion is that, in the end, these specific details aren't that important after all. But I do understand that it's hard to admit that - I spend years to get to the relatively detailed level of understanding of the win32 api that I have, yet it's mostly useless now already, and will only get more useless with time. The irrational part of me is bothered by that too.
He says They don’t seem to grasp that one must understand the native environment you’re working in before going ahead and writing a program to run within it.
I thought, or maybe you don't grasp that one need not understand it first, or in many cases, at all.
That is the whole point of abstractions after all.
If I had to write my entire operating system from scratch everytime I wanted to get something done I'd find a different profession.
However, I don't think that's the point being reached. Somewhere out there exists a magical corpus of knowledge that every programmer should be able to memorize from heart. And then there's the world we live in now where you have a corpus of knowledge you do know and can build upon. Somewhere in between is a practical subset of knowledge that should be common but some feel isn't being represented well. The debate rages about what that suitable subset is.
What I think most people who bring this argument up fail to realize is that the full corpus of knowledge that embodies all of programming is far too large for a single programmer to understand. Good abstractions can be trusted to hide the unnecessary details. Great abstractions shouldn't mean piss-poor performance (and should infact provide just the opposite). They also get out of your way (or you avoid using them) when working in the problem domain where your "low-level" knowledge is more useful for the optimizations you can provide to your code.
I'm just not as OCD as some programmers. Yet I can still get good work done. I don't think I could do it without useful abstractions.
I wouldn't want to be taught in school about low-level optimizations, I am sometimes, and generally, they teach you stuff that is 10 to 20 years old.
I think many low-level optimization tricks each generation of programmers need to find for themselves, the technology changes so much that old tricks are not as useful anymore.
Memory wasn't as slow (compared to processors) as they are today etc. Many people still count FLOPS, while memory accesses is probably the most relevant metric today, especially on GPUs.
My former college switched many years ago to teaching low level concepts on the PLEB, a ARM/Linux computer developed in-house. I would argue they were 10 years ahead of the game, with that platform now being widely commercially deployed.
In fact, the developer of that platform and his professor went on to a startup that provides low-level software components for millions of android phones.
"The rumors of my demise have been greatly exaggerated" - Okay, I took some liberty with the quote.
I see people lamenting that their favorite low-level programming techniques aren't being taught these days.
That's because the field has expanded so much! Entry-level programmers learn entry-level things. If they're good programmers, they're going to eventually teach these things to themselves... If they need them. Chances are they won't need them, though.
There are still plenty of programmers doing low-level work, and there's more all the time. There's just so many more high-level programmers that you don't notice them.
What about the Arduino community, and mobile phone programmers, and embedded systems in general?
I mean, yes, web & desktop coders don't really need to know about the details, but there are people who write operating systems, compilers, microcontrollers... Those are very low-level areas.
Considering nvidia is planing on releasing a quad core 1.5Ghz phone processor around the end of the year, I would say no phone programmers don't really need such details. Arduino community specifically, sure the arduino sucks hardware wise. Embedded systems in general professionals yes, hobbyist no. Professionals yes because shaving per unit price to the bone can make dev time worth it. Hobbyist can buy a pretty beefy microcontroller so that they don't really need to worry about it.
That chip will still require drivers, and you'll still need a compiler for it, and there will be an OS running on it. The use of the 1.5GHz processor is to use high-level constructs to generate beautiful, powerful applications.
There are phone app programmers, and there are phone programmers. If the phone programmers wasted the power in that processor on the core APIs, the energy and expense would be wasted.
That chip would likely still be an ARM+GPU architecture, and would still be without a floating point unit (except CUDA/OpenCL code). So code could still run like a dog and burn precious battery power even faster.
Programming Pearls is excellent, but it has very little in common with Hacker's Delight, except that both are excellent books about programming. HD is specifically about the kinds of low-level tricks we're talking about here, while PP generally is not.
At the uni in Oslo (Norway) there are plenty of low level programming courses. I've taken a few C (no plus plus) course with plenty of bit shifting. There is a course with a lot of assembly, you get to write a converter for UTF8 in assembly working with a lot of shifting on the registers. Then there is a course where you develop an OS and one where you work with multi-core architectures.
And I also feel that that guy in the article should have grey hair. It would give more weight to his argument.
We're [low-level programmers] not dead, we're pining for the fjords. Low level programming is a dark art, and there are naturally plenty of people celebrating how fantastic it is that they don't _have_ to know this stuff. Don't celebrate your ignorance; you may be 2-4 orders of magnitude off after your performance is sucked away by all those layers of abstraction.
My take on it is that "low-level stuff" isn't going away any time soon, and that there will always be a equilibrium reached between those who understand it and are fluent with it (and, sometimes, are going to be able to write code that is 5-100x faster) and those who don't.
It's orthogonal to an understanding of algorithms. No amount of bit-bashing can fix really poor choices of algorithms (N^2 vs NlogN, say, on a big input). That being said, there are a lot of tasks where everyone is going to land on the same linear or logN basic algorithm and the main difference is going to be the algorithm that misses cache frequently and is stuffed with branch mispredicts and pipeline stalls vs. some algorithm that avoids all these problems and runs 10x faster. Sometimes you've just got to go do something to every bit of data and there's no classic algorithmic trick.
For example, I helped a former academic colleague write the 'worlds fastest floating point minimum' routine using SSE and lots of unrolling/software pipelining and I think he got about 8-10x (he had a library for most of the common operations like vector add, mul, etc but it didn't do 'min'). No amount of rampant algorithmic cleverness would avoid the need to look at each data element once when trying to calculate the min of a vector and if you're doing WORSE than linear, you've got real problems.
The major point that I've discovered from doing this stuff for years (on considerably more complex cases than the example above) is that programming efficiently for a modern architecture is qualitatively different than designing algorithms for the abstract '1 operation counts as 1 operation' machine in your average algorithms textbook. Oddly, quite a bit of improvement in my scalar, non-parallel programming came about after having used CUDA fairly intensively - if you're writing code for a Core 2 Duo or beyond, you're already parallel programming even if you're designing code that's single-threaded. Understanding how to rethink your algorithm to have data-parallelism (not to mention using SSE) is just plain conceptually different and more akin to parallel programming than scalar programming.
Knowing when to do this is important; I like bashing out a quick Python script as much as the next guy, or perhaps some pretty random C++ STL code that's probably an order of magnitude from where it should be (not because the STL is bad but because, say, I've been bone-lazy with design). So all those 'I have 10 layers of abstraction above this level" nincompoops shouldn't get too smug; we (low-level guys) can go there too - just because we know how to optimize C/asm loops to within an inch of their lives doesn't mean that we're going to compulsively do it with every last one.
Also worthy of note - the right thing to do changes frequently; some of the resources (especially on branch prediction) listed are already out of date. Don't bring P4 knowledge to a Sandy Bridge fight. Some bit twiddling hacks are great, others (especially ones that assume multiply is ultra-expensive) are obsolete on recent x86. The concepts still keep their validity a lot more than, say, all that newfangled crud that you youngsters fill your heads with (LAMP stacks, etc.) :-)
No amount of bit-bashing can fix really poor choices of algorithms (N^2 vs NlogN, say, on a big input).
Its also worth noting that sometimes an algorithm with higher O() complexity could actually perform better. Eg, a brute-force linear search may be faster than a binary search if the elements can be efficiently cache prefetched, or if the entire dataset fits into cache.
The major point that I've discovered from doing this stuff for years (on considerably more complex cases than the example above) is that programming efficiently for a modern architecture is qualitatively different ...
I think this is a great point. Low-level programming has always required an in-depth understanding of the underlying hardware and that hardware is changing as fast as ever today. It may be true that a large percentage of development can live happily in a land of high-level abstractions, but there will always be a need for low-level optimization and that space is still rapidly changing in very interesting ways.
I'm having an extremely hard time finding a remote dev job, as I have years of C/C++ knowledge and industry experience. If I worked in webdev, I probably wouldn't say the same.
RAD's only ten programmers and that number stays more or less constant. The way everyone is hired is unique and inevitably somewhat odd. But it usually involves knowing Jeff or Jeff knowing of you for a long time and eventually he makes an offer when it makes sense.
How appropriately timed for the Assembly demo compo weekend. But I don't think these things are special: what's special is that some people just adapt and immerse fully into the environment while others try to keep their thinking together and abstract away the machine.
We have bit-twiddling hacks because we have machines that do bit-twiddling. If we had had different kind of programming environment, these low-level hackers would've been creating different tricks of trade. Nobody probably ever taught them anywhere formally, except for programmer peers to each other.
It's not the low-level stuff per se, it's the people who go for the low level no matter what stuff there is because they have to do it to get the job done.
You have not truly done low-level programming until you start worrying about gate widths and wire delays. (I'm so happy that I don't have to worry about these things anymore.)
I think google hires a lot of low-level systems programmers, as should anyone running thousands of datacenters. After all, the power and network savings you can get from bit-twiddling, when multiplied by the number of computers in these networks, is worth quite a lot in dollars.
Even there it really depends on the kind of game (and target platform). I would say that for a lot of indie games, worrying about low-level programming shouldn't be high on your list. In fact many indie-game developers are exactly the kinds of people who do love to worry about such things, when perhaps their time would be better spent making sure anybody wants to play their game, and/or finding a way to market it--- most indie games don't fail because of bad performance.
An exception might be if the game's core gameplay mechanic is fundamentally based around doing some trick on a console or handheld device that can't be done without careful optimization.
I was the last class in my CS/CompEng college to go through the program learning C as a core language for teaching. I had to tutor operating systems to classes a year after me that had learn mostly java. There was a definite loss of understanding of what was going on under the hood. Blank stares on questions on calling conventions and where variables are stored, let alone virtual memory.
I still wonder how those students can write efficient higher level code if they don't at least have a general understand what happens in lower levels.
A good example of this is choosing a web framework in python. In a previous job I had co-workers that wanted to combine an async framework with a synchronous DB API, in a single thread, and expected high performance.
When I got my first job I had to deal with BASIC in ROM and 6502 assembly. Aztec C on the Apple II was very slow and it wasn't possible to tap the faster routines in ROM because it messed up page zero completely.
Still, I can't complain. I loved the 6502 and the routines built into the Apple II+ ROM were incredibly efficient.
I think what's changing from a business perspective is what kills software projects. What keeps a project manager up at night?
1980: Being too slow to run or not being able to an amount of memory we'd consider tiny is what kills projects. Large programs are relatively uncommon because the default languages (C and assembler) are so low-level that writing them is pretty much impossible. Programs exist to solve defined problems, not to be million-line ecosystems. In this world, being a decent programmer means you have to know about things like the performance trade-offs of pre-increment vs. post-increment.
2011: It's rare that a program, unless it's using O(n^2) sorting algorithms on large sets, is actually too slow to be useful. What kills software projects is illegible code. Paying a performance penalty for readability is generally a good idea, and highly-optimized but illegible code has fallen out of favor. This also means that fewer people are learning how to write such code, because people encounter less of it to read.
Technical debt is something that sinks projects, but it's by no means the only thing. This keeps engineers up at night, not project managers. A project manager worth his salt is worried about selling the wrong thing. Plenty of systems are messy, impossible to update, and wildly successful. I've worked on some of them. But few - if any - systems get away with solving a problem nobody has.
You hint that the goalposts have shifted for engineers, and that's a good point. Moore's Law gave us the room for a much stronger culture that worries about abstractions and meta-things like maintainability and cleanliness. But more importantly, technical restrictions have been lifted that previously prevented users from getting they software they need.
I agree with you, except for the dates. It seems to me that worrying about programmer productivity over performance is a phase that we're coming out of. Mobile devices, with less memory and slower CPUs & GPUs than the desktop PCs we're used to are the hardware platform du jour. Also, more people are having to worry about scalability which goes hand in hand with performance: it's not just about smart algorithm choices, kids...
Do you have any supporting references? Legible code is a minimum requirement.
It is like saying "code which dies every full moon is killing software projects." Sure, such code would kill most software projects -- but it is common today?
(My educated guess would be that slow development speed is the real mass murderer of projects.)
Well yeah. What the hell are we doing as programmers punching tape for a Turing machine? That's robot work!
Our abstractions are still leaky, but they're getting better. Soon enough they'll be good enough that you mostly don't have to think about what's happening in the physical box at all— just like you shouldn't have to think about different kinds of wood when you're laying out a housing development.
I'd say that depends on what you mean by "ultimate". The Turing machine is the most fundamental abstraction in CS, sure. But it's not that much of an abstraction of my laptop. My laptop has state, it has instructions, each instruction takes it from one state to another. It doesn't have infinite resources, but for most of my purposes it might as well.
So since my metaphor was obviously unclear, the point I'm getting at is why would I want to be "punching tape" (writing assembly or C instructions) for a "Turing machine" (a physical computer with unlimited resources that executes one — okay, two — instructions at a time) when I could instead tell a robot (a high-level programming language) what I want it to do, and let the robot figure out the individual instructions to make that happen?
And just for future reference, "You know that #{thing I assume you don't know}, right?" is a really douchey way to pretend to try to educate somebody.
Sorry I came across as douchey. That's what I get for trying to write a terse reply from my phone. It just felt like an incomplete analogy when you were talking about abstractions and it felt like you knew a TM was an abstraction. Like trying to call everything a car.
I would say that TMs are one of the most fundamental abstractions in CS - but as you note, for an abstraction they are actually quite concrete. I would perhaps argue that the lambda calculus is a slightly more fundamental abstraction - even of course they are equivalent in "power" to TMs.
I see where you're coming from with that, although personally I mostly think of them as different approaches of considering the same thing. A Turing machine asks the question of what a computer can be, while lambda calculus asks the question of what a computer program can be. You could also think of the TM as an abstraction of the imperative paradigm, and LC as as abstraction of the functional paradigm. We know they're theoretically equivalent, but they're different ways of conceptualizing it, and probably trying to figure out which is more basic is not a good use of our time :P
I don't want to praise ignorance - probably I will read some of the stuff he linked under the article - I just think it's ok that people can do something useful with computer not knowing all the details about it. It's worth knowing though - but just as you can have no idea about physics of sound and how piano works and write nice songs, you can build nice stuff with high level tools not knowing low level.