Dead On!

I try not to read osnews.com too much anymore, but I still frequent it. I suppose it’s one of those guilty pleasures in life. I was reading this excellent article on Java performance perceptions today. The previous article on Swing vs. SWT by the same author, Andy Roberts, was excellent and I had high hopes for this one. Andy didn’t let me down.

I’m going to take a few snippets and comment on them here. Most of what I’ll be hitting on is directly relevant to the (in progress) BeOS port of Sun’s JRE, and drawn on my experiences in helping out that project.

Even if it were slower, once again, you have to think what value for money you get per-clock cycle with Java. Many of the core classes are thread-safe as standard, for example; and let’s not forget free garbage collection. Also, that bit of code will run fine on all supported platforms without any additional effort. All the OS abstraction is done for you. That’s pretty incredible when you think about what it takes to actually pull off such a large abstraction layer like that.

I can’t put it any more plainly than Andy did here. Java provides a very, very large abstraction layer and it’s not easy to pull off something like that. Thankfully, at least a few level-headed developers out there in the world understand this.

The Multi-tasking Virtual Machine (MVM) looks most promising as it’ll allow multiple Java applications to share the VM, reducing the overall burden on system resources. OK, I know you can do this already on the current JVM, but it’s still a little raw.

Interesting. I hadn’t heard of this new VM yet, so I checked it out. It looks very promising, but brings to mind a few potential obstacles relating to desktop integration. I’ll poke a few holes in the current BeOS port here. Currently, any java program runs with the Team name “java” in the Deskbar. If more than one java program is running, thanks to Desk bar’s default behavior, they get grouped into the same team. So you could have say JEdit and NetBeans running under two different VM’s, but appearing in Deskbar as a single application. Yes, it’s a usability issue. Yes, we’re aware of it. Yes, we have a few options we’re tossing around on resolving the problem. Worst case scenario, we have to submit a patch to Deskbar to handle our case differently. We’ve not gotten to that point yet. Someday I think we will.

The biggest trap Swing programmers fall into is that they don’t understand the threading model that is employed.

I could not agree more. The single AWT event thread (as Andy calls the EDT) is not something that’s specific to Swing. It’s inherited by Swing through Swings own use of the AWT, which is where the threading model originated. Saying Swing is responsible for the single-threaded nature of the AWT is like claiming that Haiku is responsible for defining the BeOS API. R5 defined the API, Haiku is merely using it. AWT defined the threading model, Swing is merely using it. The design decisions of the parent project impact those of subsequent projects, not the other way around. But enough splitting hairs, let’s move on to this single event thread…

I personally don’t see any problem with it. Sure, I honestly prefer the BeOS “every window is a thread” model as I feel it scales better — especially since we’re finally seeing multiple instruction queues and cores in processors, but for abstracting a lowest-common-denominator event and threading model across a diverse set of platforms, it certainly makes sense to do it this way. One of the more common questions I get about the BeOS AWT port is, “What about the single event thread with AWT?” It seems as though most individuals assume this is a large problem or some great obstacle in porting Java to BeOS. It’s not, and my usual response is, “What about it?”

The Java Event thread is exactly that. A Java event thread. It reads the event queue, takes the top event, dispatches it, grabs the next event, dispatches it, etc. User actions upon widgets generate and post events into the queue, which are then processed and dispatched by the event thread. This event thread is completely separate from any native message delivery mechanism. There are hooks that can be implemented if needed to synchronize native and java event handling, but largely this is unnecessary. Only is specific instances on specific platforms does this become a minute issue to work around. With the BeOS AWT we have yet to need any global locks of any kind. That’s not to say there will never be any, that’s just saying that at this point in the game, we haven’t needed any.

But back to event dispatching, how does this work with BeOS? Simple. The BeOS native objects that make up the AWT components generate java Events, then place them into the event queue from their native thread. It’s a quick operation, and at that point, the events are in the AWT event queue and dispatched by the AWT event thread. There’s no problem, there’s no contention if events are being posted by more than one thread at a time. The AWT Event model is simply a non-issue. Think of it like creating your own BLooper as a BMessage destination, and having all the BWindows in your application redirect incoming BMessages to this one single BLooper for processing. There’s no lockup anywhere, there’s no contention, nothing. Just events getting passed to the proper location.

As Andy’s article points out, the problem occurs when people handle processing in the AWT event thread. A major no-no that most developers don’t even realize is a naughty thing to do. Even when they do know about it spawning a thread off to do the work is more work. When lazy programmers come to the position of “do it right or get it done” the right way is often left in the dust with good intentions to fix it later. These shortcuts are rarely documented, and even more rarely fixed in subsequent releases or revisions. Even worse, occasionally programs are written so poorly that events must occur in some form or order, a behavior that is almost never guaranteed by any toolkit. The AWT being no exception there.

Some other performance issues have been down to the underlying graphics framework, Java2D. Because Swing components are all emulated, they are very much at the mercy of Java2D.

So true. Some people don’t seem to realize that -all- drawing on the Graphics object from Java 1.2 and later was deferred to the Java2D subsystem. It’s all Java2D. That’s why you obtain a Graphics2D by casting the Component.getGraphics() object. They’re all Graphics2D objects by default now. Otherwise, you’d be prone to getting ClassCastExceptions anytime you tried that cast. It’s one of the nastier hacks done to the Java platform in my opinion.

… Java2D have been working hard recently to improve several aspects that should enhance this even further. One area is the use of hardware acceleration where possible. This must be a nightmare to implement in a multi-platform fashion, but it’s being done, slowly but surely. I hate to say it, but Microsoft has made this easier for the Windows platform due to its DirectX graphics abstraction layer. Windows users can expect to see this pipeline utilised even more in future releases.

Lots of fodder here.

After I get keyboard events working reliably (the Focus system is horrendously documented), the next few weeks I’ll be working on reimplementing the Java2D portion of the BeOS Java port. I’m hoping to enlist the help of Andrew as I start cleaning it up, rewriting major portions, and fleshing out the implementation. Any comment I have below are reflective of the current implementations limitations, features, and things I plan to carry over into the next revision.

First of all, we don’t have any I repeat, there is no hardware acceleration. Until we can create off-screen buffers, draw to them using the Accelerant API, and blit them back to the frame-buffer at particular locations, don’t expect it. I figure we’ll just have to wait for Haiku on this one.

As for Microsoft having “made this easier” I point to my previous rant on the topic in which I beg to differ. It points out the idiocy of the DirectX & GDI integration in relation to device drivers, and how obscure the error checking has to be in order to for it to work in a sane robust manner. It’s a mess.

There are a few hackish tricks I’m (still) tossing around in my head relying on DirectWindow. These will be attempted, but the default fall-back behavior will be similar to what we have now. Anything advanced like accelerated off-screen buffers will have to wait for an OSBOS API. Sorry. The BeOS Java2D with and without DirectWindow is (going to be) a pretty glorious hack. The real bummer is that DirectWindow will only give us advantages in some specific cases… I have yet to prototype this out, but I suspect it’ll be significantly faster than the existing solution.

There are also a few little tweaks for Mustang that will even help in the “perceived” performance problems. For example, the abolition of the infamous “grey rect” problem. The fix required the use of true double-buffering support for Swing, which is another great boost for the toolkit.

Where to start with this one… The problem is not with the way Swing is double-buffered. Swing uses off-screen Images to draw components, then blits the off-screen image to the on-screen surface. The problem is with the way surface drawing takes place in Java2D. Most platforms draw directly to the surface of the component, which can be obtained as a raster from the windowing system. Not the case in BeOS, further more if we try to issue normal BView drawing methods to these components we quickly run into threading issues. It gets real nasty real quick. The solution: True double-buffering at the OS & Graphics abstraction layer. In the current (and I expect the final) BeOS Java2D implementation, each component that has a surface has an off-screen BBitmap which acts as it’s surface. This surface is drawn over the native component by the native components BView::Draw() method (we override). The current contents of the BBitmap (java surface) are always visible. Java Graphics object invocations result on the BView redrawing it’s modified contents region when the Graphics object is disposed. In this way, all drawing happens off-screen, to the BBitmap and is then blit to the frame-buffer when the native component needs to redraw. It’s true double-buffering, at the Java2D abstraction level, and completely eliminates the “grey rect” symptoms other platforms have because they draw directly to the frame-buffer within the components bounds.

The problem here wasn’t Swing or it’s notion of “double buffered”. It was Java2D’s “let’s draw to the frame-buffer, when that gets invalidated or obstructed we’ll call the Java objects repaint() and have it redraw to the frame-buffer.” The latency imposed as Java calculates clipping regions, redraws children views, and composes an updated buffer to reblit to the native component’s surface while the surface lost it’s previous state and appeared grey, is what caused the grey rect and the perceived lag. However, one of the reasons this works on BeOS is also one of the reasons it probably won’t work on other platforms. We have no (easy) way in BeOS of implementing the bits of Java2D that query a component if it’s currently obstructed. So while on some platforms if a Java window is obstructed, the obstructed parts may not receive paint() updates, on BeOS they will. If those obstructed parts are then exposed, on BeOS they’re already up-to-date. With other platforms, they may not be.

It’s a trade-off. There’s many like this, and as we work our way through implementing the java class library on BeOS, we’re constantly examining these different strategies, looking at what users and developers would expect, and trying to pick the best set of behaviors & trade-offs. In a way, it’s very much the same thing Haiku runs across while implementing the R5 API.

Anyway, those are the comments I have about this latest article about Swing, Java, and performance. I’m no expert, but I think Andy did an excellent job of pointing out the most common reason Swing gets a rap for being slow. Lazy developers who don’t know what threads are. I have a few disagreements on his view with Java2D and Swing regarding how “easy” he makes certain things sound. Other than that, he’s earned my respect as someone who does their research before writing, and presents information in a manner that’s about as unbiased as it gets.

7 Responses to “Dead On!”

  1. Simon Says:

    Hey Bryan,

    Talking of scalability, the idea of each component having a BView linked to an offscreen BBitmap scares me a little, as the appserver will fairly quickly run out of threads (each offscreen BBitmap = offscreen Window = new thread in app server). If that applies to components as small as toolbar buttons, I can see hitting around 100 components would not be that difficult and would probably lead to hard system lockups.

    Mozilla doesn’t use many BViews, but you see the same behaviour on webpages with lots of dropdown boxes (open 10 different bugzilla pages in tabs) - they each have their own hidden BWindow, which soon uses up all the threads in the app-server. I’ve been thinking about a solution to that for a while.

    I can’t really think of any better ways for your need though.

  2. Bryan Says:

    I suppose I should have clarified something.

    We’re using BBitmaps, yes. But they’re not drawable. There is no BView added to the BBitmap. Adding a BView to the BBitmap is when the app_server constructs an offscreen BWindow. Without doing that, you just have a BBitmap raster in memory. That’s what we use. BBitmap is a conveince, and a mighty fine one. We do -not- use BBitmaps with offscreen BWindows. That would get nasty pretty quickly.

    Which brings me to the next topic, 100 components hammering things with their BBitmaps. It’s only heavyweight (AWT) components that have these BBitmaps. Windows, Buttons, Lists, Panels, Canvas, etc. Just about everything but Menus. However, like I said, this only happens on heavyweight components. Swing components are all lightweight, they live inside a parent AWT component, and all Swing components paint to that parent component. For a Swing-based application (such as say, JEdit BeOS Screenshot) each window has one BView, and one BBitmap. Keep in mind, the BBitmaps cannot accept child BViews, so they’re not offscreen windows and don’t take app_server threads. In the screenshot linked above, there are three BWindows on the screen, each has a BView that draws the contents of the BBitmap surface for the window. All the swing components are drawn to that BWidnow’s surface. Heavyweight components become children of the BView that draws the surface Swing gets rendered into, which is why heavyweight components (under Java

  3. Simon Says:

    Ah right thanks. I misread the bit about BView::Draw drawing over the java surface as meaning it drew into the BBitmap.

    So have you written all your own code for drawing lines etc without using the appserver at all? AGG or something?

  4. Hugh Says:

    Great to hear more about the backgrounds and motivations regarding the BeOS-Java-port, also the article was a very interesting read. Will try the antialiasing-flag for sure:)

    BTW you might enjoy this l&f: (looks almost like your handwriting;)
    http://javootoo.l2fprod.com/plaf/napkin/index.php

    Good luck with your new job, maybe you can then finally close the Categorie “Work Sucks” ,-)

    Gregor

    PS: Sadly I’m covered up with Uni-work, but I installed Wordpress on my testserver a while back and really enjoy it - for better overview on the frontpage, you might want to break the article into a teaser and then insert a special line (new in 1.5 I think) that creates a link “More…” which opens the entire article. Can’t remember the syntax, but it should even be on the toolar.

  5. Bryan Says:

    Sun provides a default set of cross-platform native rendering pipes for Java2D. Getting them to work with BBitmap was a trivial issue (BBitmap->Buffer() returns a pointer to the start of the bitmaps raster in memory). The Sun-supplied graphics functions are fast, work well, and support everything that’s needed in Java2D. There are places we can probably speed things up in the future, but it may have to wait for Haiku or some OSBOS API for accelerated graphics.

    So yes, we’re bypassing the app_server for all Java2D drawing. The only time the app_server is used is to draw the surface bitmaps. The problem with this is that transferring bitmaps from the client program to the app_server gets expensive quickly. There’s a few things we’re doing to speed this up for R5, so far it’s been very useable on my laptop, even when it’s clocked to 600Mhz. If some of the things I’m planning to try out end up working, we should be able to eliminate the BBitmap transfer alltogether on machines that support BDirectWindow… I may be spilling the beans here, as I have absolutely no idea if that will work at all. Just a hunch that it would.

  6. arooaroo Says:

    Andy Roberts here. Glad to discover that someone read my article! ;) One point though, I *didn’t* write an article about SWT vs Swing - I wrote an article about Java looks, which briefly mentioned SWT, but focused mainly on improving Swing’s look and feel.

    For those interested in SWT, you can expect an interview SWT lead dev, Steve Northover (from IBM) to appear on OSNews in the near future. The third part in my Java ‘debate’ series will be published early next week, and will concern Java/OSS issues.

    Also, during your blog, my name suddenly switched to ‘Adam’ - which it isn’t! ;)

  7. Bryan Says:

    My sincere apologies, Andy.

    I look forward to this next installment!

    I suppose using Swing “vs” SWT was a bit misleading. I should have said you somewhat compare / contrast them, and point out some rather nice debunkers for the crowd that insists uniformity is the only way.

    I don’t think I should point my finger any further than Apple to show that companies often make “usability guides” and then break them by skinning some apps. *cough*Safari*cough*

    Anyway, I’ll leave that one alone.

Leave a Reply

You must be logged in to post a comment.