14 comments

[ 3.4 ms ] story [ 29.9 ms ] thread
«Leaving performance considerations aside, what possible difference does it make to the developer whether the CLR’s jitter happens to allocate memory for a particular local variable by adding some integer to the pointer that we call “the stack pointer” or adding the same integer to the pointer that we call “the top of the GC heap”?»

I'm all for avoiding premature optimizations, and giving priority to short and comprehensible code over faster code. But "leaving performance considerations aside" is like discussing dictatorship and "leaving human rights issues aside".

Good programmers have good hunches about performance, better programmers pick the shortest code anyway, the best programmers measure and then maybe optimize.

The best programmers also know to think about performance when designing a system--if you completely abide by the strategy of "don't prematurely optimize", you end up with a system which wasn't designed with performance in mind, and often is very difficult to optimize later because of this. Sometimes the entire structure of the program has to be redesigned because the original method just was not suitable for performance.

A great programmer is able to think about performance at every point in designing something without obsessing about it to the point where it decreases reliability, readability, or extensibility.

I think you are correct, but your scenario is a good example of something else: Prototyping and "planning to throw one away" (Fred Brooks / The Mythical Man Month).

Having written one lousy implementation in X months does not mean that the good implementation, when starting over, will take X more months.

I have never seen a functional specification reaching more than 50% accuracy on details (most are not even close). When implementing you always discover more about the problem.

What stops you from starting over tends to be that the customers have integrated with your system according to published specs, or large roll-outs have been made already.

I think you are correct, but your scenario is a good example of something else: Prototyping and "planning to throw one away" (Fred Brooks / The Mythical Man Month).

Of course, but one is not always implementing a totally new system that justifies "throwing one away". Often one is implementing something whose design is well-known enough that one should expect to be able to do it right the first time.

Having written one lousy implementation in X months does not mean that the good implementation, when starting over, will take X more months.

Sure, but it still takes Y more totally unnecessary months, even if Y is less than X.

Isn't the time for measurement usually after the code has been mostly written? Not prematurely optimizing means getting to the measurement point as soon as you can, then using actual profiling numbers to focus your attention on what needs to be changed.
No, because then you may be locked into design choices that limit what you can optimize. If performance is important, experiment with the general idea of a design, and if its performance is acceptable, follow through.

If you wait until everything is mostly written, you risk finding out that a fundamental design decision is causing your performance problems.

I guess the tricky part here is defining the term "mostly written". My mistake for introducing it (and of course the tautological 'don't prematurely optimize').

Agreed, you should measure as soon as you can and it's best to be building and measuring, not guessing where your bottlenecks are up front.

(comment deleted)
The stack is a great huge implementation detail, and doesn't necessarily work the way you want it to.

With porting an operating system across processors or when performing significant changes and upgrades to an operating system and its interfaces, the details of the data and call stack and the thread stacks and register-passing and register spillage and all the other related ugliness can be (and often is) exceedingly platform-specific.

Adding applications dependencies on these details means your code can be somewhere between faster and less portable, or faster and non-portable.

Or your applications can pin the vendor in a corner by depending on a design statement that the vendor might now regret having made. Whether that might provide or prevent a thread stack, or changes to the call frames, or stack randomization or otherwise.

I'm working in several areas where the vendors spend far, far, far too much time describing the internal details of their implementations. Which is bad on several levels. It can pin the vendor to the design; into choosing compatibility or choosing to break applications. And it tends to obscure the information presented to application programmers with details that are less than relevant.

There's more here than strictly performance; there's also compatibility and maintainability, and stability and sustainability, and extensibility.

Absent specific reasons and whenever in doubt, the decision should be to present an opaque interface. "The stack is an implementation detail."

An interesting point, but using the stack is not always transparent. You can ignore how it works for the most part, but eventually you're going to notice differences in performance or get a stack overflow exception.
Yes, you could enumerate all its characteristics and such to try and give the programmer an idea of how it will behave. Or you can just say "it's allocated on the stack" and much of that will be obvious. Seems like quite a good shorthand to me if nothing else.
I've recently come to appreciate a mathematical approach, which I see as declaring constraints. The power of this (for me) is that you can separate constraints in any way you like, and combine them however you like. This is important when you need to simplify a problem into parts, in order to be able to understand it one bit at a time (it's still an art to know how to divide up the problem, but maths gives you options).

It's very appealing (and obvious) step to then execute these declared constraints - that is, to code directly in terms of the mathematics.

But here's the problem: the maths doesn't tell you the answer; it just tells you its constraints. The answer you want is somewhere in that space. Of course, it's possible to write a language that will always give you an from within that space. The most well-known way for this issue to show up is in efficiency: the solution given by the language does fall within the constraints, but it takes too long. If we had a way to declare the efficiency as a constraint, this might change... but apparently that's a hard one. Other constraints are things like: usability, understandability (to coders of average ability, average education and average deadlines), interoperability with standards (which are never ideal, but which exist and which work), portability, modularity and many others that I haven't encountered or imagined.

Partly the difficulty is in specifying a soft, human constraint in formal terms; the other part is solving for that constraint, which appears to require strong AI or bump up against the halting problem - or else we could just declare the constraint: "fastest possible solution".

I've recently started taking the exact same constraint approach. I find it helps to give me a much more concise mental model of the problem I'm trying to solve. I guess this is because of the declarative nature of the constraints, and the fact that aren't trying to keep all the implementation details in your head. Instead you just ensure that whiles you are programming, you respect the constraints at all times. I find it particularly useful when the problem requires maintaining lots of different data structures that interact with each other.
Yes. I actually find it really difficult to stop thinking about the implementation (and the usability) of what I'm creating. These are good things to be concerned about, but not when the problem is already too complex.

On another aspect of it: the last couple of days, I've found it very effective to think of a model as just the nouns, not verbs; data structures, not algorithms (as in the Brooks quote). Guess this is implicit in the idea of a constraint, but it's helping.

The other thing is that the model doesn't have to be precisely correct. It only needs to be correct in the important ways (if I don't already know what the important aspects are, a complicated model probably won't enlighten me). This gives me conceptual framework to think in, rather than to be an authoritative definitive description, perfectly correct in every minor details (they can be corrected early or late, if they really are minor). I find it helps to give me a vantage point, to see further. My mind is very fertile with finding solutions - provided I can see where I am.