Preston L. Bannister { random memes }

2008.08.29

JVM invocation – the Sun/Java folk screw up again.

Filed under: Software — Preston @ 2:29 am

It started out with bugzilla reports for Apache Tomcat. Ran across a series of messages for this bug. This is of interest for me, as a few years back I wrote a JavaService.exe – a program that ran as a proper Win32 service, and followed all the documented Sun/Java behaviors for discovering and invoking a JVM. This meant a commercial product (what paid for my time) could run Tomcat, was perfectly and exactly isolated from generic Tomcat installations, and would do exactly the right thing across Java upgrades. I’d carefully crossed every “t” and dotted every “i”, in the expectation that future versions of Java would be consistent, and everything would work out as was meant.

Except it did not. The Sun folk changed the API. Again. (I ran the test. It failed.)

I do not know how to present this. Generally, I am an advocate for Java. But … the Sun folk do a terrific job on some aspects … sometimes. Now that Sun has open-sourced Java, I could contribute to the weaker bits … but only on my own (limited) time, as my current employer is not explicitly interested in Java. I do not like saying “someone else should do this”, unless I am willing to contribute something.

Gah….

The documented API for invoking a JVM changed from Java 1.4 to 1.5, and (as it turns out) from 1.5 to Java 1.6. No reason for this – the capabilities did not change in any fundamental way – but it changed anyway. The JavaService.exe program I wrote a few years back .. guess what? It started failing for one customer for no obvious reason. Turns out the failure is due to careless Sun programmers that effectively changed the JVM invocation API in Java 1.6, and introduced a new failure mode.

There is an existing documented bug in Sun’s database for this issue, marked as “Closed, Not a Defect” … which is wrong. I could invest time in this, but no guarantee that any work I did would get picked up. Should I bother?

2008.08.19

The stock File Open dialog is wrong.

Filed under: Software — Preston @ 12:23 am

The first time I went through the exercise of writing a GUI design guide was in the early 1980’s (very much pre-Windows). In the years between I have written a moderate number of GUI applications, but most of my work has been non-GUI, so I often spend years not thinking (much) about GUI design.

My most recent task was to add a customized “File” “Save As” dialog to a desktop application. Not too big a deal, but once running I had this nagging mental itch … I knew there was something wrong, but could not first at figure out what was bugging me. Pulled up and compared the File Open and File Save dialogs as used on Windows, Macintosh, and Gnome (Linux). Finally it clicked – they are all wrong.

Take the File Open dialog. Choosing a file to open is a ландшафтselection task – the file to open must already exist.

  1. What is the first most-likely task? Choose a file from a (short) list. The list could be most-recently-used names of saved files. The list may be files in a particular directory. Probably a bit of both.
  2. What is the second most-likely task? Choose a location from a (short) list. This is not a generic browse-anywhere-on-disk gadget. This is an application-dependent list of a small number of places where you might have files to open.
  3. Less likely is the need to browse to an arbitrary location on disk. This is where “progressive disclosure” kicks in, with the expectation that most users will never need or want to see this part of the user interface.

This is entirely different organization than the usual File Open dialog, and the usual dialog is wrong. The first task should be the first thing in the dialog, not buried amid a bunch of controls you are mostly not going to use.

The interesting bit here is that of all the platforms, the Gnome File Open dialog is – not perfect by any means, but the closest to fitting the task. Guess I need to pay more attention to the Gnome folks. :)

This is not about “dumbing down” the user interface for average users. This is about “smarting up” the work done by the programmer. Think about this … if your application has ever saved files, shouldn’t that list of saved files show up in the File Open dialog? Of course, the standard File Open dialogs do not fit this usage. Seems pretty silly that the list of “recently used” files (when supported) are not available as an immediate choice when opening a file.

The File Save dialog is for an entirely different task.

  1. What is the first most-likely task? Entering an name for the new file to be saved.
  2. What is the second most-likely task? Changing the location where the file is to be stored. Again, this is a short list, not anywhere on disk.
  3. Less likely is the need to browse to an arbitrary location on disk. Again, this is the point to apply “progressive disclosure”.

Again, note this is different from the usual File Save dialog, and the usual dialog is wrong.

Again, the Gnome File Save dialog is a notch above pretty much everyone else.

Looking at the new dialogs in Windows Vista, without doubt I find the visual appearance “cool” … but not well designed for the task. Bit disturbing that – as I Microsoft with all their money can afford to hire good user interface design folk. What has gone wrong?

Update: Two added notes.

First, I do not have access to a Macintosh (last Mac I used regularly was a Lisa) so I have to go off screenshots on the web (seem to remember looking at a design guide on Apple’s site). Not ideal – so the OS/X behavior is only a guess. Sure wish Apple had OS/X in a VM for developers, so I could test web applications and web-launched applications to insure compatibility.

Second, the recently-used-files list (when present) is both finite and distinct from the File Open dialog – both of which are silly as default behavior. The history of recently opened files should be accessible from the File Open dialog. Using the same notion, the File Save dialog should “remember” the recently used directories for saved files. You want a single locus of attention in either case.

2008.08.18

Why FileInputStream is slow – continued

Filed under: Software — Preston @ 11:35 pm

As noted earlier reads through FileInputStream were radically slower than I had expected. Hit a roadblock when I could not find the sources for SetByteArrayRegion(). Got the JDK7 sources to build locally, so went looking again – and finally found a mess of macros. A *partial* expansion of the SetByteArrayRegion() definition:

DT_VOID_RETURN_MARK_DECL(SetByteArrayRegion);

JNI_ENTRY(void, jni_SetByteArrayRegion(JNIEnv *env, jbyteArray array, jsize start, jsize len, const ElementType *buf))
  JNIWrapper("SetByteArrayRegion");
  DTRACE_PROBE5(hotspot_jni, SetByteArrayRegion__entry, env, array, start, len, buf);
  DT_VOID_RETURN_MARK(SetByteArrayRegion);
  typeArrayOop dst = typeArrayOop(JNIHandles::resolve_non_null(array));
  if (start < 0 || len < 0 || ((unsigned int)start + (unsigned int)len > (unsigned int)dst->length())) {
    THROW(vmSymbols::java_lang_ArrayIndexOutOfBoundsException());
  } else {
    if (len > 0) {
      int sc = typeArrayKlass::cast(dst->klass())->log2_element_size();
      memcpy((u_char*) dst->byte_at_addr(start),
             (u_char*) buf,
             len < < sc);
    }
  }
JNI_END

I think I know what this code is doing ... but no guarantees. Have to admit to having serious doubts about this code. This sort of massive macro expansion is more a characteristic of C code, not C++ code. Is this really necessary? I cannot tell - without spending a lot more time.

Re-wrote the readBytes() function to something more sensible - a single I/O call with no buffer allocation or copies.

int
readBytes(JNIEnv *env, jobject this, jbyteArray bytes,
          jint off, jint len, jfieldID fid)
{
    int nread, datalen;
    jbyte* pBuffer;
    FD fd;

    if (IS_NULL(bytes)) {
        JNU_ThrowNullPointerException(env, 0);
        return -1;
    }
    datalen = (*env)->GetArrayLength(env, bytes);

    if ((off < 0) || (off > datalen) ||
        (len < 0) || ((off + len) > datalen) || ((off + len) < 0)) {
        JNU_ThrowByName(env, "java/lang/IndexOutOfBoundsException", 0);
        return -1;
    }

    if (len == 0) {
        return 0;
    } 

    fd = GET_FD(this, fid);
    if (fd == -1) {
        JNU_ThrowIOException(env, "Stream Closed");
        return -1;
    }

    pBuffer = (*env)->GetByteArrayElements(env,bytes,0);
    nread = IO_Read(fd, pBuffer+off, len);
    (*env)->ReleaseByteArrayElements(env,bytes,pBuffer,0);
    if (0 < nread) {
        /* The read() is good */
    } else if (nread == JVM_IO_ERR) {
        JNU_ThrowIOExceptionWithLastError(env, "Read error");
    } else if (nread == JVM_IO_INTR) { /* EOF */
        JNU_ThrowByName(env, "java/io/InterruptedIOException", 0);
    } else { /* EOF */
        nread = -1;
    }

    return nread;
}

To measure the effect of the modified FileInputStream, first we need the measure throughput with feed-workers:

preston@mercury:~/workspace/feed-workers$ time ./feed-workers -n 1 -r `which cat` logs/_3.5G > /dev/null
Sun Aug 17 23:30:29 2008
Scanning: logs/_3.5G
Done with: logs/_3.5G
Worker #12263 ended with status: 0
Sun Aug 17 23:32:00 2008
Elapsed (ms): 90235, total (MB): 3394
Scanned 37 MB/s

real	1m30.242s
user	0m0.848s
sys	0m11.437s

preston@mercury:~/workspace/feed-workers$ time ./feed-workers -n 1 -r `which cat` logs/_400MB logs/_400MB logs/_400MB logs/_400MB logs/_400MB logs/_400MB logs/_400MB logs/_400MB logs/_400MB logs/_400MB > /dev/null
Sun Aug 17 23:46:58 2008
Scanning: logs/_400MB
Done with: logs/_400MB
Scanning: logs/_400MB
Done with: logs/_400MB
Scanning: logs/_400MB
Done with: logs/_400MB
Scanning: logs/_400MB
Done with: logs/_400MB
Scanning: logs/_400MB
Done with: logs/_400MB
Scanning: logs/_400MB
Done with: logs/_400MB
Scanning: logs/_400MB
Done with: logs/_400MB
Scanning: logs/_400MB
Done with: logs/_400MB
Scanning: logs/_400MB
Done with: logs/_400MB
Scanning: logs/_400MB
Done with: logs/_400MB
Worker #13303 ended with status: 0
Sun Aug 17 23:47:02 2008
Elapsed (ms): 4337, total (MB): 4243
Scanned 978 MB/s

real	0m4.346s
user	0m0.384s
sys	0m3.932s

For this test machine, the C++ code manages 37MB/s reading from disk, and 978MB/s reading cached file data. (The machine used for the timings in this article is different from prior articles. The differences make sense – this is a new laptop with a faster CPU and slower disk.)

Running the prior Java file reader using FileInputStream against the stock JDK7 build:

preston@mercury:~/workspace/wide-finder-j$ ~/sources/j2sdk-image.base/bin/java -version
openjdk version "1.7.0-internal"
OpenJDK Runtime Environment (build 1.7.0-internal-preston_2008_08_12_18_09-b00)
OpenJDK 64-Bit Server VM (build 14.0-b01, mixed mode)
preston@mercury:~/workspace/wide-finder-j$ ~/sources/j2sdk-image.base/bin/java  -jar upload/wide-finder-j.jar logs/_400MB logs/_400MB
Wide Finder in Java

FILE /home/preston/workspace/wide-finder-j/logs/_400MB

Time for READ - to get file cache consistent
File: /home/preston/workspace/wide-finder-j/logs/_400MB
Size: 444945620
Read: 444945620
Time: 1760
Rate: 241 MB/s

Time for READ - for measure
File: /home/preston/workspace/wide-finder-j/logs/_400MB
Size: 444945620
Read: 444945620
Time: 1737>
Rate: 244 MB/s

FILE /home/preston/workspace/wide-finder-j/logs/_400MB

Time for READ - to get file cache consistent
File: /home/preston/workspace/wide-finder-j/logs/_400MB
Size: 444945620
Read: 444945620
Time: 1750
Rate: 242 MB/s

Time for READ - for measure
File: /home/preston/workspace/wide-finder-j/logs/_400MB
Size: 444945620
Read: 444945620
Time: 1741
Rate: 243 MB/s

Running the same Java code, but using the JDK7 build with the FileInputStream using the modified readBytes():

preston@mercury:~/workspace/wide-finder-j$ ~/sources/j2sdk-image.new/bin/java  -jar upload/wide-finder-j.jar logs/_400MB logs/_400MB
Wide Finder in Java

FILE /home/preston/workspace/wide-finder-j/logs/_400MB

Time for READ - to get file cache consistent
File: /home/preston/workspace/wide-finder-j/logs/_400MB
Size: 444945620
Read: 444945620
Time: 1770
Rate: 239 MB/s

Time for READ - for measure
File: /home/preston/workspace/wide-finder-j/logs/_400MB
Size: 444945620
Read: 444945620
Time: 1734
Rate: 244 MB/s

FILE /home/preston/workspace/wide-finder-j/logs/_400MB

Time for READ - to get file cache consistent
File: /home/preston/workspace/wide-finder-j/logs/_400MB
Size: 444945620
Read: 444945620
Time: 1762
Rate: 240 MB/s

Time for READ - for measure
File: /home/preston/workspace/wide-finder-j/logs/_400MB
Size: 444945620
Read: 444945620
Time: 1734
Rate: 244 MB/s

Right. No difference? Odd – there must be something eating a lot of CPU in the Java/JVM code. I’m not tooled up for profiling the Java/JVM code, so … not sure if I am will get to the bottom of this one.

The point of this exercise is that I believe the FileInputStream read() code should – for large buffers – come very close to the performance of C++ code. Something is knocking down the cached-file-data read rate for Java code to about a fourth of C++ code. This is a good point to apply profiling – and the block of time needed may be more than I am willing to spend.

2008.08.15

Guilty as charged…

Filed under: Humor, Software — Preston @ 12:26 pm

User Interface Design for Programmers – Chapter 8
Human aptitude tends towards the bell curve. Maybe 98% of your customers are smart enough to use a television set. About 70% of them can use Windows. 15% can use Linux. 1% can program. But only 0.1% of them can program in a language like C++. And only 0.01% of them can figure out Microsoft ATL programming. (And all of them, without exception, have beards and glasses.)

The link points to this image:

Guilty as charged. :)

2008.08.10

Desktop design needs a kick in the pants

Filed under: Software — Preston @ 8:57 pm

What is wrong with this picture?

Don’t get it? Try this version…

Everything outside the text box, everything grayed out in the second picture, is overhead. Wasted space. Pixels serving no purpose. Or very little purpose at the moment. In the above example, 18% active, and 82% overhead – not good. Even better? The text box is not fully on the screen. Have to scroll to see it all. The currently most important element is partly off-screen due to too much vertical clutter.

To be clear – I am not picking particularly on the coders of the web application on screen. This design mistake is very, very, very common.

In an odd way the rise of HDTV makes the problem both permanent and worse. Permanent as “widescreen” displays are from this point forward going to be cheaper than other form-factors, due to sales volume. Worse as the “widescreen” format is, well, wide rather than tall. Look at the above pictures (or the screen in front of you) and you will see a pile of conventional design elements that eat away chunks of vertical space, while serving limited purpose.

Due to the price advantage, in a few years HDTV panels are going to predominate in the workplace. We must adjust our design practices to suit the new and most-common form factor.

The screenshot was taken from my 17-inch laptop screen – which is about as big (or bigger) as is practical for a laptop. That’s all there is folks. You can get higher resolution, but cannot downsize fonts or UI elements without making them hard to read/use.

Yes, I know there is always a crop of programmers that think tiny 6-point fonts are “kewl”. Right. Walk past their office a week later, and you will catch them leaning in so they can read the screen with less effort. There is a good reason the world of print-on-paper uses 10 or 12 point fonts (or bigger) for anything they expect you to read. Nothing shouts “clueless” quite so much as a user interface (desktop application, web page, or whatever) with too-small text and interactive design elements.

Which is what I saw rather a lot of when installing and trying alternate window managers (this is on Linux). Too small design elements is too common in that lot. Bit of a disappointment that, as the Windows developers at Microsoft seem unfortunately clueless (if I had a penny for every mis-click on a cascading popup … that would be a lot).

Is Apple the only hope for something better, in the desktop GUI? Don’t know who else will make the effort.

2008.08.05

Google and Programming

Filed under: Software, Web — Preston @ 8:27 am

In an odd way, Google is harmful to programmers.

To pick an example – look for examples of Java application/applet use. What you find is a lot of poor or obsolete information. For a programmer, how to filter out the (rare) good from the (often) bad is not easily obvious. Google is ranking results by … well I don’t really know Google’s algorithm, but measures of popularity are clearly not good measures of quality.

Java is also hurt by the lack of a single well-kept reference site comparable to php.net for PHP users. Lacking a popular common site, searching for Java information take you to quite a variety of places, mostly not good.

The problem exists for any topic with a long history. Java gains from a long period of relevance, but loses from a large amount of visible material on the web offering not-very-good information. Javascript also has a long history, and a large body of examples and hints that are not good.

Quite unintentionally, Google favors newly-invented languages and toolkits. Search for Ruby and web programming topics, and the results you get back contain little obsolete information.

If you want programmers to get back current and relevant material, seems you need to rename your language/toolkit/whatever every few years. :)