20

Everyone says that I should make my code modular, but isn't it less efficient if I use more method calls rather than fewer, but larger, methods? What is the difference in Java, C, or C++ for that matter?

I get that it is easier to edit, read and understand, especially in a group. So is the computation time loss insignificant compared to the code tidiness benefits?

Mat
  • 2,066
  • 2
  • 26
  • 31
fatsokol
  • 309
  • 2
  • 4
  • 2
    The question is how long it will take for the processing time you save pass the time spent on more-difficult maintenance. The answer to that depends entirely on your application. – Blrfl Jul 29 '13 at 11:42
  • 2
    Many good questions generate some degree of opinion based on expert experience, but answers to this question will tend to be almost entirely based on opinions, rather than facts, references, or specific expertise. – gnat Jul 29 '13 at 11:45
  • 10
    It's also worth pointing out that the computation penalty for a function or method call is so miniscule that even in a very large program with lots of functions and method calls, making those calls *doesn't even rank on the chart.* – greyfade Jul 29 '13 at 16:58
  • 1
    @greyfade: That is true for the direct jumps but additional indirect predicted jump might cost for example ~3% of total running time of program (just a number from program I've recently checked - it might not have been representative though). Depending on your area you might or might not consider it significant but it did register on the chart (and it of course is at least partially orthogonal to modularity). – Maciej Piechotka Jul 29 '13 at 17:43
  • 4
    Premature optimization is the root of all evil. Linear code is slightly faster than modular code. Modular code is vastly faster than spaghetti code. If you aim at linear code without a very (VERY) thorough project of the whole thing, you'll end up with spaghetti code, I guarantee that. – SF. Jul 29 '13 at 17:45
  • To all commenters above: a very useful rule-of-thumb is the ratio of "CPU overhead per invoke" divided by "useful CPU computation per invoke". (Replace "CPU" by whatever metric, and "invoke" by whatever spooky interactions at a distance.) Keep this ratio in check (0.1 percent to 1 percent) and then you can focus entirely on code usability / maintainability / serviceability / 1337ability . – rwong Oct 28 '14 at 23:18

7 Answers7

47

Yes, it is irrelevant.

Computers are tireless, near-perfect execution engines working at speeds totally un-comparable to brains. While there is a measurable amount of time that a function call adds to the execution time of a program, this is as nothing compared to the additional time needed by the brain of the next person involved with the code when they have to disentangle the unreadable routine to even begin to understand how to work with it. You can try the calculation out for a joke - assume that your code has to be maintained only once, and it only adds half an hour to the time needed for someone to come to terms with the code. Take your processor clock speed and calculate: how many times would the code have to run to even dream of offsetting that?

In short, taking pity on the CPU is completely, utterly misguided 99.99% of the time. For the rare remaining cases, use profilers. Do not assume that you can spot those cases - you can't.

Kilian Foth
  • 107,706
  • 45
  • 295
  • 310
  • 2
    While I agree that most of the time it is premature optimization, I think that your argument with the time is bad. Time is relative in different situations and you can't simply calculate like you did. – Honza Brabec Jul 29 '13 at 12:14
  • 28
    +1 just for the expression "taking pity on the CPU", because it so nicely accents the wrongheadedness in premature optimization. – Michael Borgwardt Jul 29 '13 at 13:15
  • 4
    Highly related: [How fast are computers in everyday terms?](http://www.blueraja.com/blog/368/how-fast-are-computers-in-everyday-terms) Going out of your way to avoid a few function calls for "speed" is like going out of your way to save someone one minute total over the course of their **entire life**. In short: it's not even worth the time it takes to consider. – BlueRaja - Danny Pflughoeft Jul 29 '13 at 16:41
  • 7
    +1 for so eloquently selling what many are missing, but you forgot to add the most important weight to the balance that makes pitying the CPU even more grave: Disregarding the lost money and time spent on that one time maintenance; if it took 1 second, there is still a far more insidious sink there. **Bug risk**. The more confounding and difficult it is for that later maintainer to change your code, the more significant risk of him implementing bugs in it, which may cause insurmountably large inevitable costs from risk to users and any bug causes follow-on maintenance (which may cause bugs...) – Jimmy Hoffa Jul 29 '13 at 17:00
  • An old teacher of mine used to say "If you're thinking whether you're prematurely optimizing or not, stop right here. If this was the right choice, you'd knew it." – Ven Jul 30 '13 at 23:14
23

It depends.

In the glacially-slow world that is Web programming, where everything happens at human speeds, method-heavy programming, where the cost of the method call is comparable to or exceeds the cost of the processing done by the method, probably doesn't matter.

In the world of embedded systems programming and interrupt handlers for high-rate interrupts, it most certainly does matter. In that environment, the usual models of "memory access is cheap" and "the processor is infinitely fast" break down. I have seen what happens when a mainframe object-oriented programmer writes his first high-rate interrupt handler. It wasn't pretty.

Several years ago, I was doing nonrecursive 8-way connectivity blob coloring on real-time FLIR imagery, on what was at that time a decent processor. The first attempt used a subroutine call, and the subroutine call overhead ate the processor alive. (4 calls PER PIXEL x 64K pixels per frame x 30 frames per second = you figure it out). The second attempt changed the subroutine into a C macro, with no loss of readability, and everything was roses.

You have to look HARD at what you are doing and at the environment in which you will be doing it.

John R. Strohm
  • 18,043
  • 5
  • 46
  • 56
  • Remember that a majority of modern programming problems are best dealt with lots of object-oriented, method-happy code. You'll know when you're in an embedded systems environment. – Kevin Jul 29 '13 at 22:05
  • (+1, btw. My comment is for readers, not at the answerer.) – Kevin Jul 29 '13 at 22:06
  • 4
    +1, but with a caveat: it's usually the case that an optimising compiler will often do a better job of inlining, loop unrolling, and doing scalar and vector optimisations than a person. The programmer still needs to know the language, the compiler and the machine very well to take advantage of this, so it's not magic. – detly Jul 29 '13 at 23:09
  • 2
    That's true but you optimized your code **after** you wrote the logic and profiled it to find the problematic part in your algorithm. You did not start to code for performance but for readability. And macros will help you to keep your code readable. – Uwe Plonus Jul 30 '13 at 07:00
  • @Kevin: You'd think new programmers would understand that embedded is different. In the real world, however, they frequently don't. – John R. Strohm Jul 30 '13 at 13:02
  • 2
    Even in embedded systems programming, start by writing clear correct code and only start optimizing after that _if necessary_ and _as guided by profiling_. Otherwise it's too easy to put lots of work in that has no real effect on performance/code-size and which just makes the source hard to understand. – Donal Fellows Jul 30 '13 at 14:41
  • 1
    @detly: Yes and no. Modern compilers can IN GENERAL do a better job WITHIN THE LIMITS IMPOSED BY THE LANGUAGE. The human programmer knows whether the limits imposed by the language are applicable in the particular case at hand. For example, Cn and C++ compilers are required by the language standards to allow the programmer to do certain very pathological thing, and generate code that works anyway. The programmer may know he isn't crazy or stupid or adventurous enough to do those things, and make optimizations that would otherwise be unsafe, because he knows they are safe in this case. – John R. Strohm Aug 03 '13 at 02:35
12

First of all: Programs in a higher language is for being read by humans not by machines.

So write the programs so that you understand them. Don't think about performance (if you seriously have perfomance problems then profile your application and enhance the performance where it is needed).

Even if it is true that calling a method or function takes some overhead this does not matter. Today compilers should be able to compile your code into efficient machine language so that the generated code is efficient for the target architecture. Use the optimization switches of your compiler to get the efficient code.

Uwe Plonus
  • 1,310
  • 9
  • 16
  • 5
    I would say that we should write programs in such a way that **others** will understand them. – Bartlomiej Lewandowski Jul 29 '13 at 17:07
  • The first person that has to read a program is the developer himself. That's the reason why I wrote *you*. If other persons can read the program also it is nice but (in my eyes) not necessary (in the first place). If you work in a team then other persons should also understand the program but my intention was that person have to read a program, not computers. – Uwe Plonus Jul 30 '13 at 06:56
5

Typically when you would otherwise have a large function and you split it into a lot of smaller ones, these smaller ones will be inlined because the only downside of inlining (repeating the same instructions too much) is not relevant in this case. That means your code will act as if you had written one large function.

If they are not inlined for some reason and this becomes a performance problem, then you should consider manual inlining. Not all applications are networked CRUD forms with huge intrinsic latencies.

Esailija
  • 5,364
  • 1
  • 19
  • 16
2

There is probably no computation cost. Usually the compilers/JITs for past 10-20 years or so deals with the function inlining perfectly fine. For the C/C++ usually it is limited to 'inlinable' functions (i.e. the definition of function is available for compiler during compilation - that is it is in header of the same file) but current techniques of LTO overcome this.

If you should spent time on optimization depends on the area you are working on. If you deal with 'normal' application that spent most of the time waiting on input - probably you should not worry about optimizations unless application 'feels' slow.

Even in such cases you should concentrate on many things before doing micro-optimization:

  • Where the problems are? Usually people have hard time to find the hotspots as we read the source code differently. We have different ratio of operation time and we perform them sequentially while modern processors don't.
  • Is doing some calculation needed every time? For example if you change a single parameter out of thousands you might want to compute just a part affected instead of whole model.
  • Do you use optimal algorithm? Change from O(n) to O(log n) might have much larger impact then anything you could achieve by micro-optimization.
  • Do you use proper structures? Say you are using a List when you need HashSet so you have O(n) lookups when you could have O(1).
  • Do you use parallelism efficiently? Currently even mobile phones can have 4 cores or more it might be tempting to use threads. However they are not a silver bullet as they have cost of synchronization (not mentioning that if problem is memory bound they make no sense anyway).

Even if you do decide that you need to perform micro-optimization (which practically means that your software is used in HPC, embedded or just used by very large number of people - otherwise the additional cost of maintenance overcome the computer time costs) you need to identify the hotspots (kernels) you want to accelerate. But then you probably should:

  1. Know exactly the platform you are working on
  2. Know exactly the compiler you are working on and what optimization it is capable of doing and how to write idiomatic code to enable those optimization
  3. Think about memory access patterns and how much can you fit into cache (exact size of which you know from point 1).
  4. Then if you are compute bound think about reorganizing computation to save the calculation

As a final remark. Usually the only problem you have with method calls is the indirect jumps (virtual methods) which were not predicted by branch predictor (unfortunately indirect jump is the hard case for it). However:

  • Java have a JIT which in many cases can predict the type of class and therefore a target of jump beforehand and therefore in hotspots you should not have many problems.
  • C++ compilers often perform a program analysis and at least in some cases can predict the target at compile time.
  • In both cases when the target have been predicted the inlining should work. If the compiler couldn't perform the inlining chances are we couldn't too.
Maciej Piechotka
  • 2,465
  • 2
  • 18
  • 19
0

My answer probably won't expand too much on the existing answers, but I feel like my two cents may be helpful.

First off; yes, for modularity, you usually give up some level of execution time. Writing everything in assembly code is going to give you the best speed. That said...

You know YouTube? Likely either the most high-bandwidth site in existence, or second to Netflix? They write a large portion of their code in Python, which is a highly modular language not quite built for top-notch performance.

The thing is, when something is going wrong, and users are complaining about videos loading slowly, there's not many scenarios where that slowness would ultimately be attributed to the slow execution speed of Python. However, Python's quick re-compilation, and its modular ability to try new things without type-checking will probably allow the engineers to debug what's going wrong pretty quickly ("Wow. Our new intern wrote a loop that does a new SQL sub-query for EVERY result.") or ("Oh, Firefox has deprecated that old caching header format; and they made a Python library to set up the new one easily")

In that sense, even in terms of execution time, a modular language can be thought of as faster because once you find what your bottlenecks are, it will likely become easier to reorganize your code to get it working in the best way. So many engineers will tell you that the heavy performance hits weren't where they thought they'd be (and in fact the things they DID optimize were hardly needed; or, didn't even work the way they'd expect!)

Katana314
  • 862
  • 1
  • 5
  • 18
0

Yes and no. As other have noted program for readability first, then for efficiency. However, there are standard practices which are both readable and efficiency. Most code gets run rather infrequently, and you won't gain much advantage from optimizing it anyway.

Java can inline smaller function calls, so there is little reason to avoid writing the functions. Optimizers tend to work better with simpler easier to read code. There are studies that show shortcuts which should theoretically run faster, actually take longer. The JIT compiler is likely to work better the code is smaller and frequently run pieces can be identified and optimized. I haven't tried it but I would expect one large function which is relatively rarely called not to be compiled.

This likely doesn't apply to Java, but one study found larger functions actually ran slower due to requiring a different memory reference model. This was hardware and optimizer specific. For smaller modules instructions which worked within a memory page were used. These were faster and smaller than the instructions required when the function did not fit withing a page.

There are cases where it is worth while to optimize code, but generally you need to profile the code to determine where that is. I find it is often not the code I expected.

BillThor
  • 6,232
  • 17
  • 17