9

It has been a few months since I started my position as an entry level software developer. Now that I am past some learning curves (e.g. the language, jargon, syntax of VB and C#) I'm starting to focus on more esoteric topics, as to write better software.

A simple question I presented to a fellow coworker was responded with "I'm focusing on the wrong things." While I respect this coworker I do disagree that this is a "wrong thing" to focus upon.

Here was the code (in VB) and followed by the question.

Note: The Function GenerateAlert() returns an integer.

Dim alertID as Integer = GenerateAlert()
_errorDictionary.Add(argErrorID, NewErrorInfo(Now(), alertID))    

vs...

 _errorDictionary.Add(argErrorID, New ErrorInfo(Now(), GenerateAlert()))

I originally wrote the latter and rewrote it with the "Dim alertID" so that someone else might find it easier to read. But here was my concern and question:

Should one write this with the Dim AlertID, it would in fact take up more memory; finite but more, and should this method be called many times could it lead to an issue? How will .NET handle this object AlertID. Outside of .NET should one manually dispose of the object after use (near the end of the sub).

I want to ensure I become a knowledgeable programmer that does not just rely upon garbage collection. Am I over thinking this? Am I focusing on the wrong things?

6
  • 1
    I could easily make the case that he is 100% since the first version is more readable. I'd wager the compiler may even take care of what you are concerned with. Even if it didn't, you are prematurely optimizing.
    – rlperez
    Commented Jul 10, 2012 at 17:13
  • 6
    I'm not at all sure it would really use more memory with an anonymous integer vs. a named integer. In any case, this really is premature optimization. If you need to worry about efficiency at this level (I'm almost sure you don't) you might need C++ instead of C#. It's good to understand performance issues and what happens under the hood, but this one is a small tree in a big forest.
    – psr
    Commented Jul 10, 2012 at 17:22
  • 5
    The named vs anonymous integer wouldn't use more memory, especially since an anonymous integer is just a named integer that YOU didn't name (the compiler still has to name it). At most, the named integer would have a different scope so it might live longer. The anonymous integer would live only as long as the method needed it, the named one would live as long as its parent needed it. Commented Jul 10, 2012 at 17:35
  • Let's see... If Integer is a class, it will be allocated on the heap. The local variable (on the stack most likely) will hold a reference to it. The reference will be passed to the object errorDictionary. If the runtime is doing reference counting (or such), then when there are no more references, it (the object) will be deallocated from the heap. Anything on the stack is automatically "deallocated" once the method exits. If it's a primitive, it will (most likely) end up on the stack.
    – Paul
    Commented Jul 10, 2012 at 20:24
  • Your colleague was right: the issue raised by your question shouldn't have been about optimization, but about readability.
    – haylem
    Commented Jul 10, 2012 at 21:52

7 Answers 7

24

"Premature optimization is the root of all evil (or at least most of it) in programming." - Donald Knuth

When it comes to your first pass, just write your code so that it's correct and clean. If it is later determined that your code is performance-critical (there are tools to determine this called profilers), it can be re-written. If your code is not determined to be performance-critical, readability is far more important.

Is it worth digging into these topics of performance and optimization? Absolutely, but not on your company's dollar if it's unnecessary.

9
  • 1
    On who else's dollar should it be? Your employer benefits from the increase in your skills rather more than you do.
    – Marcin
    Commented Jul 10, 2012 at 19:01
  • Subjects that don't directly contribute to your current task? You should pursue these things on your own time. If I sat down and researched every CompSci item that piqued my curiosity over the course of the day, I'd get nothing done. That's what my evenings are for. Commented Jul 10, 2012 at 19:10
  • 2
    Weird. Some of us have a personal life, and as I say, the employer benefits primarily from the research. The key is to not actually spend all day on it.
    – Marcin
    Commented Jul 10, 2012 at 19:11
  • 6
    Good for you. It doesn't really make it a universal rule, though. Plus, if you discourage your workers from learning at work, all you've done is discourage them from learning, and encouraged them to find another employer who actually pays for staff development.
    – Marcin
    Commented Jul 10, 2012 at 19:35
  • 2
    I understand the opinions noted in the above comments; I would like note that I asked during my lunch break. :). Again, thank you all for your input here and throughout the Stack Exchange site; it is invaluable!
    – user39581
    Commented Jul 10, 2012 at 20:22
5

For your average .NET program, yes, it's overthinking. There may be times where you'll want to get down to the nuts and bolts of exactly what's going on inside of .NET but this relatively rare.

One of the difficult transitions I had was switching from using C and MASM to programming in classic VB back in the 90's. I was used to optimizing everything for size and speed. I had to let go of this thinking for the most part and let VB do it's thing in order to be effective.

5

As my co-worker always used to say:

  1. Make it work
  2. Fix all bugs make it work flawless
  3. Make it S.O.L.I.D.
  4. Apply optimization if it is performing slow

In another words, always keep in mind KISS (keep it simple stupid). Because over-engineering, over-thinking some code logic may be an issue to change logic next time. However, keeping code clean and simple is always good practice.

However, by time and experience you would know better which code smells and would need optimization pretty soon.

3

Should one write this with the Dim AlertID

Readability is important. In your example though, I'm not sure that you're really making things that more readable. GenerateAlert() has a good name and it's not adding a lot of noise. There are probably better uses of your time.

it would in fact take up more memory;

I suspect it doesn't. That's a relatively straight-forward optimization for the compiler to make.

should this method be called many times could it lead to an issue?

Using a local variable as an intermediary makes no impact on the garbage collector. If GenerateAlert() new's up memory, then it will matter. But that will matter regardless of the local variable or not.

How will .NET handle this object AlertID.

AlertID isn't an object. The result of GenerateAlert() is the object. AlertID is the variable, which if it's a local variable is simply space associated with the method to keep track of things.

Outside of .NET should one manually dispose of the object after use

This is a dicier question that depends on the context involved and the ownership semantics of the instance provided by GenerateAlert(). In general, whatever created the instance should delete it. Your program would likely look significantly different if it were designed with manual memory management in mind.

I want to ensure I become a knowledgeable programmer that does not just relay upon garbage collection. Am I over thinking this? Am I focusing on the wrong things?

A good programmer uses the tools available to them, including the garbage collector. It's better to overthink things than to live oblivious. You might be focusing on the wrong things, but since we're here, you might as well learn about it.

1

Make it work, make it clean, make it SOLID, THEN make it work as fast as it needs to work.

That should be the normal order of things. Your very first priority is to make something that will pass the acceptance tests which shake out of the requirements. This is your first priority because it is your client's first priority; meeting the functional requirements within the development deadlines. The next priority is to write clean, readable code that is easy to understand and can thus be maintained by your posterity without any WTFs when that becomes necessary (it's almost never a question of "if"; you or someone after you WILL have to go back in and change/fix something). The third priority is to make the code adhere to the SOLID methodology (or GRASP if you prefer), which puts code into modular, reusable, replaceable chunks that again aids maintenance (not only can they understand what you did and why, but there are clean lines along which I can surgically remove and replace pieces of code). The last priority is performance; if code is important enough that it has to conform to performance specs, it's almost certainly important enough to be made correct, clean and SOLID first.

Echoing Christopher (and Donald Knuth), "premature optimization is the root of all evil". In addition, the kind of optimizations you are considering are both minor (a reference to your new object will be created on the stack whether you give it a name in source code or not) and of a type that may not cause any difference in compiled IL. Variable names aren't carried forward into the IL, so since you're declaring the variable right before its first (and probably only) use, I would bet some beer money that the IL is identical between your two examples. So, your coworker is 100% right; you are looking in the wrong place if you're looking at named variable vs inline instantiation for something to optimize.

Micro-optimizations in .NET are almost never worth it (I'm talking about 99.99% of cases). In C/C++, maybe, IF you know what you're doing. When working in a .NET environment, you already are far enough away from the metal of the hardware that there is significant overhead in code execution. So, given that you are already in an environment that indicates you've given up on blistering speed and are instead looking to write "correct" code, if something in a .NET environment really isn't working fast enough, either its complexity is too high, or you should be considering parallelizing it. Here are some basic pointers to follow for optimization; I guarantee you your productivity in optimization (speed gained for time spent) will skyrocket:

  • Changing the function shape matters more than changing the coefficients - WRT Big-Oh complexity, you can reduce the number of steps that must be executed in an N2 algorithm by half, and you still have a quadratic-complexity algorithm even though it executes in half the time it used to. If that's the lower bound of complexity for this type of problem, so be it, but if there's an NlogN, linear or logarithmic solution to the same problem, you will gain more by switching algorithms to reduce complexity than by optimizing the one you have.
  • Just because you can't see the complexity doesn't mean it isn't costing you - Many of the most elegant one-liners in the word perform terribly (for example, the Regex prime checker is an exponential-complexity function, while efficient prime evaluation involving dividing the number by all prime numbers less than its square root is on the order of O(Nlog(sqrt(N))). Linq is a great library because it simplifies code, but unlike a SQL engine, the .Net compiler won't try to find the most efficient way of executing your query. You have to know what will happen when you use a method, and thus why a method might be faster if placed earlier (or later) in the chain, while producing the same results.
  • OTOH, there's almost always a tradeoff between source complexity and runtime complexity - SelectionSort is very easy to implement; you could probably do it in 10LOC or less. MergeSort is a bit more complex, Quicksort more so, and RadixSort even more so. But, as the algorithms increase in coding complexity (and thus "up-front" development time), they decrease in runtime complexity; MergeSort and QuickSort are NlogN, and RadixSort is generally considered linear (technically it's NlogM where M is the largest number in N).
  • Break fast - If there is a check that can be made inexpensively that is significantly likely to be true and means that you can move on, make that check first. If your algorithm, for instance, only cares about numbers that end in 1, 2, or 3, the most likely case (given completely random data) is a number that ends in some other digit, so test that the number does NOT end in 1, 2, or 3, before making any checks to see whether the number ends in 1, 2, or 3. If a piece of logic requires A&B, and P(A)=0.9 while P(B)=0.1, then check B first, unless if !A then !B (like if(myObject != null && myObject.someProperty == 1)), or B takes more than 9 times longer than A to evaluate (if(myObject != null && some10SecondMethodReturningBool())).
  • Don't ask any question that you already know the answer to - If you have a series of "fall-through" conditions, and one or more of those conditions are dependent upon a simpler condition that must also be checked, never check both of these independently. For example, if you have a check that requires A, and a check that requires A && B, you should check A, and if true you should check B. If !A, then !A&&B, so don't even bother.
  • The more times you do something, the more you should pay attention to how it's done - This is a common theme in development, on many levels; in a general development sense, "if a common task is time-consuming or fiddly, keep doing it until you're both frustrated and knowledgeable enough to come up with a better way". In code terms, the more times an inefficient algorithm is run, the more you'll gain in overall performance by optimizing it. There are profiling tools that can take a binary assembly and its debug symbols and show you, after running through some use cases, what lines of code were run the most. Those lines, and the lines that run those lines, are what you should pay the most attention to, because any increase in efficiency you achieve there will be multiplied.
  • A more complex algorithm looks like a less complex algorithm if you throw enough hardware at it. There are some times where you just have to realize that your algorithm is approaching the technical limits of the system (or the part of it) that you're running it on; from that point if it needs to be faster, you'll gain more by simply running it on better hardware. This also applies to parallelization; an N2-complexity algorithm, when run on N cores, looks linear. So, if you're sure you've hit the lower complexity bound for the type of algorithm you are writing, look for ways to "divide and conquer".
  • It's fast when it's fast enough - Unless you're hand-packing assembly to target a particular chip, there's always something to be gained. However, unless you WANT to be hand-packing assembly, you must always keep in mind what the client would call "good enough". Again, "premature optimization is the root of all evil"; when your client calls it fast enough, you're done until he doesn't think it's fast enough anymore.
0

The only time to worry about optimization early on is when you know you're dealing with something that's either huge or something you know will execute a huge number of times.

The definition of "huge" obviously varies based on what your target systems are like.

0

I would prefer the two line version simply because it is easier to step through with a debugger. A line with several embedded calls makes it more difficult.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.