Letters to the editor may be sent via email to [email protected], or via the postal service to Letters to the Editor, C/C++ Users Journal, 1601 W. 23rd St., Ste 200, Lawrence, KS 66046-2700.
Dear Marc,
Its always a great pleasure going through high quality contents of CUJ. This months articles were also superb! The Java Solutions section is a very good addition.
Regarding the article on "Java Named Semaphores" (Java Solutions, December 2000), there are few points I wanted to talk about:
When a thread executes wait, one of two things happens. If no other thread has executed wait, wait doesnt block, and the thread passes through, gaining access to the critical code. On the other hand, if another thread has already executed wait, processing on the current thread is suspeneded...
Thats actually not the case. wait always blocks, even if no other thread has executed wait. The semantics of wait is not similar to a mutex (as mentioned in the article) but rather similar to Event objects (Windows) or condition variables (Unix).
The sample program below illustrates this point:
public class Test { synchronized void showWait() { try { System.out.println ("calling wait"); wait(); System.out.println ("out of wait"); } catch(InterruptedException e){} } public static void main(String[] args) { Test t=new Test(); t.showWait(); } }
Here the main thread will be blocked forever, until it is notified.
The Mutex class presented is semantically not a mutex. This class requires one thread to call lock while some other thread calls unlock, whereas mutex semantics require that the thread that acquired the lock should free the lock. Hence it is more like an Event object.
Similarly, the article mentions the following regarding notifyAll:
notifyAll...Then if multiple threads are waiting, then one is arbitrarily chosen to discontinue waiting...
notifyAll actually wakes up all the threads (waiting on that object) and not any one. Hence, the Mutex code presented will not work, as it calls notifyAll, which would wake up all threads waiting on this mutex. The code below demonstrates notifyAll:
public class Test { synchronized void showWait() { try { String threadName= Thread.currentThread(). getName(); System.out.println ("calling wait() in thread->" + threadName); wait(); System.out.println ("out of wait in thread->" + threadName); } catch(InterruptedException e){} } synchronized void showNotifyAll() { String threadName= Thread.currentThread(). getName(); System.out.println ("Sleeping for 5 seconds in thread->" + threadName); try{Thread.sleep(5000);} catch(InterruptedException e){} System.out.println ("calling notifyAll from thread->" + threadName); notifyAll(); } public static void main(String[] args) { final Test t=new Test(); new Thread(){ public void run() { t.showWait(); } }.start(); new Thread(){ public void run() { t.showWait(); } }.start(); new Thread(){ public void run() { t.showNotifyAll(); } }.start(); t.showWait(); } }
Sincerly,
Vishal Kochhar
The author has confirmed that his explanation of wait and notifyAll was incorrect, but hopes it does not put a damper on the main point of the article, which was creating named semaphores in Java. Thank you for your comments. mb
Dear Sir or Madam,
As a long-time subscriber to C/C++ Users Journal (longer in fact than C++ has been in the title!), and a long-time fan of the writings of Andrew Koenig and Barbara Moo, I read with great interest their article Using Library Algorithms (excerpted from their new book Accelerated C++: Practical Programming by Example).
This article, their book, and other articles by such notables as Bjarne Stroustrup himself reflect the views of a new school of thought regarding the teaching of programming in C++, effectively reversing the old-school approach by starting out with high-level classes and algorithms to allow the student to write effective and useful programs from the start. This top-down approach eradicates one of the biggest hurdles of the old-school bottom-up approach, which was that the student was forced to absorb quite a lot of detailed information before being able to write useful programs. This often led to considerable frustration on the students part, and I must say that the new approach is indeed better in a number of ways.
However, as Jon Programming Pearls Bentley notes, we also cannot forget performance. And that brings me to the point of this letter, which is to take some small issue with the following statement from an otherwise excellent article. Disregarding the obvious typo, the statement is:
*out++ = *begin++;
is equivalent to the more verbose
*out = *begin; ++out; ++begin;
While correct in the sense that the final values are identical, the first form unavoidably results in the creation of two temporary objects (which are likely just pointers) due to the use of the post-increment operator. The cost may be small, but may also be significant in cases where performance is important or where the iterators involved are objects larger than pointers. See also Scott Meyers More Effective C++, Item 6, for more on this particular topic.
Admittedly, delving into the details of why pre-increment is more efficient than post-increment is definitely counter to the whole philosophy of the new teaching approach, but it seems to me that some way of incorporating efficiency concerns must be found if this new school is to result in altogether better programs.
So, to Andrew Koenig, Barbara Moo, Bjarne Stroustrup, and other worthy proponents of this excellent new approach to learning C++ I address the following question: How can efficiency concerns be appropriately included in this new approach? Here the key word is appropriately since an inappropriate concern for efficiency has almost certainly damaged more programs than any lack of efficiency ever will.
Thanks.
Randy Maddox
Consulting Software Engineer and Author
[email protected]
Andrew Koenig replies:
As long as youre going to mention Jon Bentley, I would like to bring up his two rules for optimization:
1. Dont do it.
2. (for experts only) Dont do it yet.
Performance is important, but so is ease of understanding, and the two often conflict. As you point out, it is important to think about how to resolve this conflict especially in the context of an introductory book such as Accelerated C++.
The general rule that we decided to follow in our book was to think about large-scale efficiency issues, particularly when they might cause algorithms to be quadratic rather than linear, but not to worry too much about small-scale efficiency issues such as the one you raise. There are several reasons for this decision.
First, there is a limit to how much we can expect readers to be willing to master at one time. Therefore, as Bentley suggests, we would rather defer talking about optimization issues until we can assume that our readers have gained more experience.
Second, the kind of small-scale optimizations youre talking about here typically gain only a linear factor in execution time, which means that they will have much less of an effect in practice than the large-scale issues.
Third, our experience is that it is difficult to predict accurately the effect of small-scale optimizations without trying them on relevant implementations and measuring their effect. As an example, I tried measuring execution time on one particular implementation for the following program fragment:
vector<int> v(1000, 42); vector<int>::iterator end = v.end(); for (int i = 0; i != 1000000; ++i) { vector<int>::iterator p = v.begin(); while (p != end) { ++(*p++); } }
If I change the statement
++(*p++);
to
++*p; ++p;
the program runs approximately 8 per cent faster. So you are indeed right that there is a difference at least sometimes.
However, if I turn on compiler optimization, the program runs approximately 88 per cent faster. That is, its execution time decreases by more than a factor of eight! Moreover, the two different forms of the inner loop now run at exactly the same speed. So at least in this case, the compiler appears to be capable of generating good code from either form of the inner loop without help from the programmer.
Efficiency is one of many important ideas that programmers must eventually understand. One of the hard decisions in writing an introductory book is the order in which to cover these important ideas. Throughout Accelerated C++, we have tried to introduce at each point the ideas that would add the most to what the reader would be able to accomplish at that point. We felt that it was important to discuss the
*out++ = *begin++;
usage because it is so common that our readers would surely encounter it elsewhere, and therefore that we would enable our readers to understand programs that they might otherwise find to difficult. On the other hand, we thought that it was still too early to discuss the small-scale efficiency characteristics of this notation, because many readers would not yet be in a position to understand the issue, and because understanding it would not add much to what readers could accomplish at this point in the exposition.
Regards,
Andrew Koenig
Dear CUJ,
I was interested in the article on Debugging under GNU/Linux by Randy Zack (February 2001) since I have been using Linux since 1997. One thing that he states when he is talking about core files is that we didnt learn anything we couldnt have learned by executing the program itself from within gbd. That is true for a program that only takes milliseconds to run, but twice I have found core files indispensable.
The first case is when a satellite data processing program I was developing dumped after running for two hours. I would have to have spent another two hours to get to that point had I restarted using gbd. Instead, the core file had enough information to identify the line number and the variables value that caused the crash and the problem was solved in a matter of minutes.
The second case is when a program I wrote that processed a live data feed crashed. In this case, there was no way of replicating the data that caused the crash and I would not have been able to find the problem by restarting the program with gbd. Instead, by loading the core file into gdb, I was able to see the offending data string. A few minutes later, the problem was fixed and the program was back on line.
The point is, core files can be an extremely useful, if not the only way, of identifying bugs in long running (or always running) code. A core file can immediately go to the program state that caused the error condition, without taking the time to get to that point.
Dear Chuck Allison,
I found your File Processing article in Java Solutions (February 2001) quite educational. However, I couldnt help noticing a few things regarding the C++ version.
1) The return type of function toString is missing in the function declaration. This is illegal in both C and C++ according to the latest standards of the two languages. Looking at the function body, I assume you meant void as the return type.
2) Having corrected (1), I can hardly accept the fact that this is a C++ program. It looks more like C. Although it is legal C++ code, the program contains a number of constructs that are considered poor style and/or error-prone in C++.
3) Return value of -1 from main involves implementation-defined behavior. Both languages specify that to be portable main must return zero or EXIT_SUCCESS for success or EXIT_FAILURE for failure. You need to #include <cstdlib> or <stdlib.h> for the EXIT_XXX macros.
4) You check the return value of fwrite, but ignore the return value of fread. Why? There is as much chance of failure reading from a file as there is writing to it.
Regarding item (2), I omit the details but could provide them if youre interested. Also, I thought of including my idea of what a C++ version should look like. However, depending on the circumstances, decisions like the following would have to be made:
a) Use std::string or char[]?
b) Use return error codes or exceptions?
c) Use enums for constant class values or static members?
Again, if youre interested, I could provide my /*one of many*/ C++ solutions.
Sincerely,
Boris Pulatov
Chuck Allison replies:
Indeed, this is just a C program, and I should have labeled it as such. (The version I post on my website will have the corrections; also, note that the file type is .c, not .cpp.) My point was of course that the Java version is much more verbose. If your C++ is version is even shorter than my C version, then that would be interesting.
I forgot the return type for toString, so it defaults to int, which is valid in C.
Your point (3) is also correct, but again, I was just comparing the size of the programs. Using the <stdlib> macros (which I am very aware of, thank you) would make the one-line program longer, and as it stands, this program will run on all Windows and Unix systems. Portability was not important to the current issue, but you are indeed correct, as also is your comment about making the program more bulletproof by checking fread.
I hope that my point of Java I/O being much more verbose than C (or even C++, which I didnt illustrate) was made by the article, nothing more nor less.
Thanks for writing.
Chuck Allison