Let's start at the beginning: mixed C and C++ code is fairly common. So you're in a big club to start with. We have huge C codebases in the wild. But for obvious reasons many programmers refuse to write at least new stuff in C, having access to C++ in the same compiler, new modules start to be written that way -- at first just leaving the existing parts alone.
Then eventually some existing files get recompiled as C++, and some bridges can be deleted... But it may take really long time.
You are ahead somewhat, your full system is now C++, just most of it is written "C-style". And you see mix of styles a problem, what you should not: C++ is a multi-paradigm language supporting many styles, and allow them to co-exist for good. Actually that is the main strength, that you are not forced to a single style. One that would be suboptimal here and there, with some luck not everywhere.
Re-working the codebase is a good idea, IF it is broken. Or if it is in the way of development. But if it works (in the original sense of word), please follow the most basic engineering principle: if it ain't broke, don't fix it. Leave the cold parts alone, put your effort where it counts. On the parts that are bad, dangerous -- or in new features, and just refactor parts to make them a bed.
If you seek general things to address, here's what worth evicting from a C codebase:
- all the str* functions and char[] -- replace them with a string class
- if you use sprintf, create a version that returns a string with the result, or puts it in the string, and replace usage. (If you never bothered with streams do yourself a favor and just skip them, unless you like them; gcc provides perfect type safety out of the box for checking formats, just add the proper attribute.
- most malloc and free -- NOT to with new and delete, but vector, list, map and other collectons.
- the rest of memory management (after the previous two points it must be pretty rare, cover with smart pointers or implement your special collections
- replace all other resource usage (FILE*, mutex, lock, etc) to use RAII wrappers or classes
When you're done with that you approach the point where the codebase can be reasonably exception-safe, so you can drop return-code football using exceptions and rare try/catch in high-level functions only.
Beyond that just write new code in some healthy C++, and if some classes are born that are good replacement in existing code, pick them up.
I didn't mentions syntax-related stuff, obviously use refs instead of pointers in all new code, but replacing old C parts just for that change is no good value. Casts you must address, eliminate all you can, and use C++ variants in wrapper functions for the remainder. And very importantly, add const wherever applicable. These interleave with the earlier bullets. And consolidate your macros, and replace what you can make into enum, inline function or template.
I suggest reading Sutter/Alexandrescu's C++ Coding Standards if not yet done and follow them closely.
I was then asked how many strings this program would generate, assuming garbage collection does not happen. My thoughts for n=3 was (7)
Strings 1 (""
) and 2 ("a"
) are the constants in the program, these are not created as part of things but are 'interned' because they are constants the compiler knows about. Read more about this at String interning on Wikipedia.
This also removes strings 5 and 7 from the count as they are the same "a"
as String #2. This leaves strings #3, #4, and #6. The answer is "3 strings are created for n = 3" using your code.
The count of n2 is obviously wrong because at n=3, this would be 9 and even by your worst case answer, that was only 7. If your non-interned strings was correct, the answer should have been 2n + 1.
So, the question of how should you do this?
Since the String is immutable, you want a mutable thing - something you can change without creating new objects. That is the StringBuilder.
The first thing to look at is the constructors. In this case we know how long the string will be, and there is a constructor StringBuilder(int capacity)
which means we allocate exactly as much as we need.
Next, "a"
doesn't need to be a String, but rather it can be a character 'a'
. This has some minor performance boosting when calling append(String)
vs append(char)
- with the append(String)
, the method needs to find out how long the String is and do some work on that. On the other hand, char
is always exactly one character long.
The code differences can be seen at StringBuilder.append(String) vs StringBuilder.append(char). Its not something to be too concerned with, but if you're trying to impress the employer it is best to use the best possible practices.
So, how does this look when you put it together?
public String foo(int n) {
StringBuilder sb = new StringBuilder(n);
for (int i = 0; i < n; i++) {
sb.append('a');
}
return sb.toString();
}
One StringBuilder and one String have been created. No extra strings needed to be interned.
Write some other simple programs in Eclipse. Install pmd and run it on the code you write. Note what it complains about and fix those things. It would have found the modification of a String with + in a loop, and if you changed that to StringBuilder, it would have maybe found the initial capacity, but it would certainly catch the difference between .append("a")
and .append('a')
Best Answer
There are two 'forces' here, in tension: Performance vs. Readability.
Let's tackle the third problem first though, long lines:
The best way to implement this and keep readibility, is to use string concatenation:
The String-constant concatenation will happen at compile time, and will have no effect on performance at all. The lines are readable, and you can just move on.
Now, about the:
vs.
The second option is significantly faster. I will suggest about 2X as fast.... why?
Because 90% (with a wide margin of error) of the work is not related to dumping the characters to the output, but is overhead needed to secure the output to write to it.
Synchronization
System.out
is aPrintStream
. All Java implementations that I know of, internally synchronize the PrintStream: See the code on GrepCode!.What does this mean for your code?
It means that each time you call
System.out.println(...)
you are synchronizing your memory model, you are checking and waiting for a lock. Any other threads calling System.out will also be locked.In single-threaded applications the impact of
System.out.println()
is often limited by the IO performance of your system, how fast can you write out to file. In multithreaded applications, the locking can be more of an issue than the IO.Flushing
Each println is flushed. This causes the buffers to be cleared and triggers a Console-level write to the buffers. The amount of effort done here is implementation dependant, but, it is generally understood that the performance of the flush is only in small part related to the size of the buffer being flushed. There is a significant overhead related to the flush, where memory buffers are marked as dirty, the Virtual machine is performing IO, and so on. Incurring that overhead once, instead of twice, is an obvious optimization.
Some numbers
I put together the following little test:
The code is relatively simple, it repeatedly prints either a short, or a long string to output. The long String has multiple newlines in it. It measures how long it takes to print 1000 iterations of each.
If I run it at the unix (Linux) command-prompt, and redirect the
STDOUT
to/dev/null
, and print the actual results toSTDERR
, I can do the following:The output (in errlog) looks like:
What does this mean? Let me repeat the last 'stanza':
It means that, for all intents and purposes, even though the 'long' line is about 5-times longer, and contains multiple newlines, it takes just about as long to output as the short line.
The number of characters-per-second for the long run is 5 times as much, and the elapsed time is about the same.....
In other words, your performance scales relative to the number of printlns you have, not what they print.
Update: What happens if you redirect to a file, instead of to /dev/null?
It is a whole lot slower, but the proportions are about the same....