Swing performance query

M

Martin Gregorie

I've found a swing performance issue I don't understand. I'd appreciate
an explanation for what I'm getting and especially any suggestion for
fixing it.

I have written a simple Java terminal emulator, JTerm, which uses a
JTextArea to implement its screen. Structure is simple: its a normal
model/view/display structured application whose main thread handles
keyboard input and transmission to the remote system. A worker thread is
used to capture and display incoming data from the remote system.

When I first wrote the emulator, I used an extended non-terminating
Thread with this logic in its run() method:

while (again)
{
try
{
sleep(waitTime);
model.scanForData();
}
catch (InterruptedException ex)
{
waitTime = MINWAIT;
}

waitTime *= 2;
waitTime = (waitTime > MAXWAIT ? MAXWAIT : waitTime);
}

Every time a key is typed the thread is interrupted to make sure it
starts polling fast. The model's scanForData() method polls for data
from the remote system and puts it into the display via the
JTextArea.replaceRange() method. This worked smoothly, except that any
time I typed at a reasonable rate the JTerm bombed out with a failure to
get a writelock on the default document underlying JTextArea.

Yesterday I re-implemented the thread loop logic as an ActionListener
that's fired by a javax.swing.Timer. The variable loop timing is
achieved by setting the Timer's initial delay to MINWAIT and restarting
it whenever a key is typed. I moved the delay doubling logic into the
model.scanForData() method. This has cured the writelock failures by
executing the model.scanForData() method as part of the Swing
event-dispatching thread, *but* the perceived system performance has
deteriorated hugely.

It looks as though the screen update is now slowing the scan loop down
for long enough that huge volumes of data (anything up to 900 bytes) are
arriving while the screen is being updated despite the loop timer being
set for typically 8 or 16 mS. As the serial line is 9600 baud something
must be adding a second to the scan cycle in the terminal emulator:
nothing else in the entire setup has changed.

Here's the whole system in summary:

JTerm, the Java terminal emulator, is an application I wrote to provide
worst-case performance testing for my SerialPort package. JTerm sends
all keystrokes to the remote system as they are typed and only updates
its display with date received from the remote system, i.e. its a full
duplex terminal emulator with a 24 x 80 display area.

SerialPort runs entirely in user mode. It extends Java by providing
access to serial ports via an interface class and does not require any
changes to the supporting operating system. It's available on
SourceForge if you want to look at it though the current download still
contains the flaky version of JTerm. The new JTerm version will be put
up within 24 hours.

Here's a block diagram:

JTerm - the Java terminal emulator logic and gui
SerialPort - the interface class
|
| - a TCP/IP sockets connection
|
spd - a high performance server written in C
:
: - 9600 baud serial connection
:
remote system - a 25 MHz 68020 system running OS-9 v2.4.
JTerm logs in and runs commands via its shell.

spd buffers input and output serial port streams separately.
Asynchronous i/o is used to move bytes between the buffers and the
associated serial port. Blocks of data are transferred between the
application and buffers over the socket connection, so both i/o streams
are optimized for maximum throughput and minimum overhead.

JTerm and spd are on separate Linux systems on my house LAN. Both are
slow: a P300 and a K6/266 respectively. Single characters make the round
trip in under 4 mS. With the loop delay set to MINWAIT (4 mS) one
character can be sent and four received within the timeout interval.
When I'm sending a command to OS-9 I can type at my normal speed and not
have to wait after typing a key before it appears on screen.

The performance problem only occurs if something I type displays a
significant amount of data.

Any and all suggestions for improving the JTerm emulator will be
gratefully received.
 
T

Thomas Hawtin

Martin said:
while (again)
{
try
{
sleep(waitTime);
model.scanForData();
}
catch (InterruptedException ex)
{
waitTime = MINWAIT;
}

waitTime *= 2;
waitTime = (waitTime > MAXWAIT ? MAXWAIT : waitTime);
}

Thread.interrupt is like poking with a sharp stick. Object.notify is
more like a gentle nudge. You are likely to cause damage with the pointy
stick. I/O can be interrupted, for instance, which is probably not what
you want.
JTextArea.replaceRange() method. This worked smoothly, except that any
time I typed at a reasonable rate the JTerm bombed out with a failure to
get a writelock on the default document underlying JTextArea.

Some of the Swing text threading code is plain wrong. I don't remember
the exact details, but using Thread.interrupt may tickle some of the bugs.
Yesterday I re-implemented the thread loop logic as an ActionListener
that's fired by a javax.swing.Timer. The variable loop timing is
achieved by setting the Timer's initial delay to MINWAIT and restarting
it whenever a key is typed. I moved the delay doubling logic into the
model.scanForData() method. This has cured the writelock failures by
executing the model.scanForData() method as part of the Swing
event-dispatching thread, *but* the perceived system performance has
deteriorated hugely.

Are you pulling all the data out in one go?

java.util.Timer would be better suited, if you want it to run
concurrently with the GUI.
It looks as though the screen update is now slowing the scan loop down
for long enough that huge volumes of data (anything up to 900 bytes) are
arriving while the screen is being updated despite the loop timer being
set for typically 8 or 16 mS. As the serial line is 9600 baud something
must be adding a second to the scan cycle in the terminal emulator:
nothing else in the entire setup has changed.

It might be worth trying to measure the delays.
Here's a block diagram:

JTerm - the Java terminal emulator logic and gui
SerialPort - the interface class
|
| - a TCP/IP sockets connection

If you are communicating over sockets, why not use a thread for reading,
rather than attempting to poll?

Tom Hawtin
 
J

Jean-Marie Gaillourdet

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi Martin,

did you measure the delay between single data packets that arrive? If
many packets arrive in short delays it might be worth to consider
merging of those event. It might be fast enough for the user to update
the display every 20ms or so. Just guessing.

Regards,
Jean-Marie

Martin said:
I've found a swing performance issue I don't understand. I'd appreciate
an explanation for what I'm getting and especially any suggestion for
fixing it.

I have written a simple Java terminal emulator, JTerm, which uses a
JTextArea to implement its screen. Structure is simple: its a normal
model/view/display structured application whose main thread handles
keyboard input and transmission to the remote system. A worker thread is
used to capture and display incoming data from the remote system.

When I first wrote the emulator, I used an extended non-terminating
Thread with this logic in its run() method:

while (again)
{
try
{
sleep(waitTime);
model.scanForData();
}
catch (InterruptedException ex)
{
waitTime = MINWAIT;
}

waitTime *= 2;
waitTime = (waitTime > MAXWAIT ? MAXWAIT : waitTime);
}

Every time a key is typed the thread is interrupted to make sure it
starts polling fast. The model's scanForData() method polls for data
from the remote system and puts it into the display via the
JTextArea.replaceRange() method. This worked smoothly, except that any
time I typed at a reasonable rate the JTerm bombed out with a failure to
get a writelock on the default document underlying JTextArea.

Yesterday I re-implemented the thread loop logic as an ActionListener
that's fired by a javax.swing.Timer. The variable loop timing is
achieved by setting the Timer's initial delay to MINWAIT and restarting
it whenever a key is typed. I moved the delay doubling logic into the
model.scanForData() method. This has cured the writelock failures by
executing the model.scanForData() method as part of the Swing
event-dispatching thread, *but* the perceived system performance has
deteriorated hugely.

It looks as though the screen update is now slowing the scan loop down
for long enough that huge volumes of data (anything up to 900 bytes) are
arriving while the screen is being updated despite the loop timer being
set for typically 8 or 16 mS. As the serial line is 9600 baud something
must be adding a second to the scan cycle in the terminal emulator:
nothing else in the entire setup has changed.

Here's the whole system in summary:

JTerm, the Java terminal emulator, is an application I wrote to provide
worst-case performance testing for my SerialPort package. JTerm sends
all keystrokes to the remote system as they are typed and only updates
its display with date received from the remote system, i.e. its a full
duplex terminal emulator with a 24 x 80 display area.

SerialPort runs entirely in user mode. It extends Java by providing
access to serial ports via an interface class and does not require any
changes to the supporting operating system. It's available on
SourceForge if you want to look at it though the current download still
contains the flaky version of JTerm. The new JTerm version will be put
up within 24 hours.

Here's a block diagram:

JTerm - the Java terminal emulator logic and gui
SerialPort - the interface class
|
| - a TCP/IP sockets connection
|
spd - a high performance server written in C
:
: - 9600 baud serial connection
:
remote system - a 25 MHz 68020 system running OS-9 v2.4.
JTerm logs in and runs commands via its shell.

spd buffers input and output serial port streams separately.
Asynchronous i/o is used to move bytes between the buffers and the
associated serial port. Blocks of data are transferred between the
application and buffers over the socket connection, so both i/o streams
are optimized for maximum throughput and minimum overhead.

JTerm and spd are on separate Linux systems on my house LAN. Both are
slow: a P300 and a K6/266 respectively. Single characters make the round
trip in under 4 mS. With the loop delay set to MINWAIT (4 mS) one
character can be sent and four received within the timeout interval.
When I'm sending a command to OS-9 I can type at my normal speed and not
have to wait after typing a key before it appears on screen.

The performance problem only occurs if something I type displays a
significant amount of data.

Any and all suggestions for improving the JTerm emulator will be
gratefully received.

-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.1 (GNU/Linux)
Comment: Using GnuPG with Thunderbird - http://enigmail.mozdev.org

iD8DBQFEFSlsNIUNP/I5YOgRApxDAKCxYu9U4Yda+l8J4RShXI7rV6zNmgCggSl5
MCsOAMRYCczsfc2eKZcD4fs=
=ILIl
-----END PGP SIGNATURE-----
 
M

Martin Gregorie

Thomas said:
Thread.interrupt is like poking with a sharp stick. Object.notify is
more like a gentle nudge. You are likely to cause damage with the pointy
stick. I/O can be interrupted, for instance, which is probably not what
you want.
Point taken, though I only saw it doing what I expected. I didn't see
any problems other than the Document writelock failures. However, I
suppose it could have been the interrupt that was causing the Document
writelock to fail.
Some of the Swing text threading code is plain wrong. I don't remember
the exact details, but using Thread.interrupt may tickle some of the bugs.
Agreed - and the rather confusing JTextArea documentation hint at that.
At one point it says that JTextArea is thread safe and then a little
later is says you'd better make sure the updates are run by the Swing
event dispatching thread or you'll get problems.
Are you pulling all the data out in one go?
Data trickles into the server's receive buffer from the serial port. The
application can (and does in this case) issue a query to see how much is
there. If data has arrived it issues a read request for what's arrived
and then puts it in the JTextArea.

Before you ask, the server doesn't have locking or synchronization
issues because its based around a single poll() call for all TCP/IP and
serial i/o and is not threaded.
java.util.Timer would be better suited, if you want it to run
concurrently with the GUI.
I used javax.swing.Timer because it runs
ActionListener.actionPerformed() code in the Swing event dispatching
thread. There was a definite warning against using java.util.Timer for
just this reason.
It might be worth trying to measure the delays.
When a keystroke (such as the CR at the end of a command that will
immediately display a lot of lines of data) is handled, the timer delay
is set to 4 mS. At the end of this the server is queried and returns up
to 4 bytes - a byte at 9600 baud (8 data bits + 1 stop bit) is a tad
under 1 ms, so that looks right even allowing for transit time for the
TCP/IP message pair. However, there's obviously time being spent putting
those bytes into the display, at the end of which the timer causes
another 4 mS delay. After this second delay the server typically has
several tens of bytes waiting and these are fetched with the next read
request.

I can turn on a shed-load of tracing and so I'm very confident that this
is what's going on. In particular, at full tracing detail the trace log
shows every keystroke, command/response pair sent to the server, update
to the JTextArea, and timer delay setting.

I'll measure the clock time taken for the JTextArea update after each
fetch and report back. I'll also try the effect of limiting JTerm to
single character fetches because the JTextArea is updated character by
character (necessary to correctly handle newlines and tabs).
If you are communicating over sockets, why not use a thread for reading,
rather than attempting to poll?
Because of the way the server currently works, which is partially
explained above.

I considered having a message pair block in the server until the amount
of data requested by the client has arrived but decided against it on
the grounds that, while this works for a well-defined remote application
with predictable message sizes, dealing with variable length or
unexpected responses becomes quite difficult for both the client and the
server.

Having the server immediately return with the requested amount of data
or as much as is available is very simple for the server and dealing
with this in the client is quite easy too. For the time being this seems
to work OK, but if it proves to be a stumbling block it can be changed.
By doing it this way the server is fully responsive, even when its
handling several serial ports for different client applications. Each
client is responsible for waiting while data gets handled by the remote
system, so its less likely that they will mutually interfere.

If the client is sending and receiving a known amount of data (i.e.
sending and receiving fixed size blocks) it can use an interface method
that sleeps for the calculated period it will take to send its last
record and receive the response.

The JTerm terminal emulator is intended to be a pathological test case
for the server. Its logic sends a character at a time (maximum sockets
overhead and after a delay, intended to be too short to affect one's
typing, it simply returns as many reply characters as are available and
keeps on doing this at a high polling rate until it's emptied the
server's receive buffer. Once the buffer is empty and the user hasn't
typed any more keystrokes the poll rate doubles successively until it
hits one per second so as to be kind to the computer(s) its battering.

If, after this rather long explanation, you see objectionable ffeatures
in what I'm doing and the way I'm doing it I'd be pleased to know about
them.

You can find the complete sources for what we're talking about at
serialport.sourceforge.net in case you want to giggle at my code.
 
M

Martin Gregorie

Jean-Marie Gaillourdet said:
did you measure the delay between single data packets that arrive? If
many packets arrive in short delays it might be worth to consider
merging of those event. It might be fast enough for the user to update
the display every 20ms or so. Just guessing.
No, but I don't think that's the problem. Here's why I think that:

The server only uses a request/response model: it never sends
non-requested messages. The message set used by JTerm to find out how
much data is available and then fetch it might look like this:

Request Response
Q,0, Q,8,4,0,3000 Query data, reply: 4 rxd 0 txd, 3K buff
G,1,4 G,4,data Request up to 4 bytes, get 4 bytes

where the buffer initially contained the string "data", Q(uery)and G(et)
are commands, the commas are field separators and the second field is
always the length of data in the third and final field. The third field
can contain any byte value 0x00 to 0xff including comma.

As you can see, the server is really designed to handle formatted
messages, hence the send and receive buffers for each serial port. The
JTerm terminal emulator is designed to bash the system with worst case
messages, each containing as little data as possible. Hence the polling
rate adjustment.

I haven't timed a message pair, but with a lot of tracing diagnostics
turned on I can see every method invoked in the data fetch and display
loop as it happens. That shows a very fast turn-round on the two message
pairs followed by a really long, *MUCH* slower, set of trace messages
while the JTextArea gets updated character by character.

As I said, in the last iteration I didn't change anything apart from
converting the Thread-based fetch loop into a Timer + ActionListener and
I'm really puzzled why it's now taking so much longer to update the
JTextArea than when I was still using the extended Thread object.
 
M

Martin Gregorie

It was - and I should have done it before.

I added timing code to measure the time taken to:
- query the server
- fetch a block of data from the server
- display the block of data

and for good measure I added the ability to restrict the size of data
block that's read from the server. I ran tests with a variety of
transfer block sizes and the same set of OS-9 commands"

login
dir - outputs a list of files occupying 1/3 of the
screen
mdir -e - outputs a list of in-memory modules occupying
2 1/2 screens full.
logout

This session reads 27 bytes and outputs 4,200 bytes. I ran the timing
output from JTerm through a gawk script to calculate totals and data
rates. The results are most interesting:

- as expected, the TCP/IP data transfers run faster as the size of each
individual transfer increases even though the proportion of queries to
fetches increases.
- the transfer rate (calculated from the bytes read and the total time
needed for the queries plus retrieves varies as shown:
Chunk size transfer rate bytes read at 9600 baud
data bytes bytes/sec during each query + fetch

unlimited 6412 689
40 3042 1504
10 1181 3751
5 497 8903
1 194 22844

This shows that, provided the average fetch is more than 25 bytes or so,
the transfer rate across a 10Mb/s sockets connection is at least as fast
as the data rate on a 9600 baud serial connection. Of course, moving the
server onto the same host as JTerm, which takes .5 mS off the round
trip time according to ping, moves the break-even point down to a 12-15
byte fetch.

What about the display operation that I thought was slow?
It's slow all right. Here's how its speed varies:
Chunk size display update
data bytes bytes/sec

unlimited 239
40 183
10 167
5 174
1 209

The intriguing thing is that there's so little variation in its
performance and its preference for 10 byte at a time updates.

As I thought, I'd better optimize the display updating code if I want to
improve JTerm's performance.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,059
Latest member
cryptoseoagencies

Latest Threads

Top