2009-10-07 08:00:28 +04:00
|
|
|
The first project I focused on when I became fun-employed was improving Vty: A terminal output
|
|
|
|
library for Haskell software.
|
|
|
|
<lj-cut/>
|
|
|
|
Oh I know what you're thinking... Well no, but *I* think it's rediculous to spend time on a
|
|
|
|
*terminal* library. Something like OpenGL or web related, hell anything where any significant activity
|
|
|
|
has happened in the past few years seems more reasonable. Oh well.
|
|
|
|
|
|
|
|
This is a diary of completing vty 4 mostly written after I was done. No really specific technical
|
|
|
|
stuff is contained in this post. I suppose it counts as a sort of postmortem.
|
|
|
|
|
|
|
|
I had taken over as maintainer of vty 3 from Stefan O'Rear. Vty 3 already worked great and I didn't
|
|
|
|
really see me doing much. Still there is always something to improve. In this case vty did not
|
|
|
|
support the various terminals people wanted to use. And characters that occupy multiple output
|
|
|
|
columns were causing corruption.
|
|
|
|
|
|
|
|
Plus, optimization is always fun. So trying my hands at optimizing Haskell code sounded great.
|
|
|
|
Especially when, for the most part, there was an already fast and already working version to compare
|
|
|
|
performance data against: Vty 3.
|
|
|
|
|
|
|
|
I figured low level optimization was what I should start on. Which only makes sense considering I
|
|
|
|
was only interested in optimization fun at the time. ;-) The result of this were much faster than
|
|
|
|
before. However, since I wasn't changing the design in any significant fashion some optimizations
|
|
|
|
could not be implemented. In the end this route was only useful to define reasonable performance
|
|
|
|
goals for a rewritter output layer.
|
|
|
|
|
|
|
|
In addition I was learning Mandarin at the time. I wanted to, of course, create software to help me
|
|
|
|
study. Since I have an infatuation with terminal user interfaces I wanted a terminal library that
|
|
|
|
could handle double-width characters. I couldn't see an effective way to implement this with vty 3's
|
|
|
|
implementation.
|
|
|
|
|
|
|
|
Anybody who has needed to reimplement, or deal with the horrors of a botch reimplementation, knows
|
|
|
|
how dangerous this can be. A botched reimplementation ends up costing more than continueing to
|
|
|
|
maintain the old implementation. A reimplementation is only feasable if the immediate cost of
|
|
|
|
performing the work is offset by the profits the improvements provide.
|
|
|
|
|
|
|
|
A particular source of trouble for vty was the insanity of dealing with terminals or terminal
|
|
|
|
emulators. I'm never going to refer to the physical box of relays from the 70s and 80s that is
|
|
|
|
properly called a terminal again. Terminal emulators are now be refered to as terminals and the
|
|
|
|
others don't exist. So...
|
|
|
|
|
|
|
|
Terminals are software driven character displays and a keyboard. The software controls the diplay by
|
|
|
|
serializing to the STDOUT UTF-8 byte character sequences. Which are then displayed. And control
|
|
|
|
codes which modify the display of the characters. Input from the keyboard and events are read from
|
|
|
|
STDIN.
|
|
|
|
|
|
|
|
Why the fuck something as old as a terminal hasn't been beaten down into a simple, universally
|
|
|
|
supported set of operations by now is a mystery. I don't think curses are terminfo count. I suspect
|
|
|
|
if support for everything that cannot support the required interface is dropped things would only be
|
|
|
|
better. For this reason I only focused on supporting the following terminals: xterm-256-color with
|
|
|
|
UTF-8; Mac OS X Terminal.app; gnome terminal, kde terminal; and rxvt-unicode. Basically: All the
|
|
|
|
terminals I could easily use and behaved close to how I expected.
|
|
|
|
|
|
|
|
While I did not consider supporting the Windows platform I hoped that the abstractions used to
|
|
|
|
handle the various non-Windows terminals would simplify supporting Windows in the future.
|
|
|
|
|
|
|
|
To assure the re-implementation did not introduce regressions I repeatedly:
|
|
|
|
0. Characterized vty 3's implementation. Both in terms of functionality and performance.
|
|
|
|
1. Defined the semantics for the new implementation.
|
|
|
|
2. Verified the new implementation performed as expected: The semantics defined in 1 were
|
|
|
|
implemented correctly; No characteristics of vty 3's implementation that should be maintained
|
|
|
|
are missing; Verified characteristics of vty 3 that caused issues were not maintained.
|
|
|
|
|
|
|
|
Not all the verification steps could be automated. Some I didn't know how to. Others were
|
|
|
|
just verified through informal analysis.
|
|
|
|
|
|
|
|
The verification of some features was done by implementing an interactive test that guided and
|
|
|
|
recorded the results of a manual review. For instance the libraries representation of red and what
|
|
|
|
is actually required to get a terminal to display red. The only reasonable way to verify that final
|
|
|
|
map was for me to sit there and look at the output. Then record whether or not the output was as
|
|
|
|
expected. Since the same tests were going to have to be performed repeatedly and I wanted to record
|
|
|
|
the results of the tests I formalized this process in software: tests/interactive_terminal_test.hs.
|
|
|
|
This program recorded the results of: Describing a test to a user; Performing the test; Requesting
|
|
|
|
from the user if the test passed or failed; Then recording the users response. This paid for itself
|
|
|
|
very quickly. Not only did provide the framework to easily create about 15 individual tests. But
|
|
|
|
the program could also worked as a sort of bug reporting tool for users.
|
|
|
|
|
|
|
|
The verification that could be entirely automated was done either through the type system or
|
|
|
|
<a href="http://en.wikipedia.org/wiki/QuickCheck">QuickCheck 2</a> based verification tests.
|
|
|
|
In short an loose terms: QuickCheck informally verifies equations satisfy user specified predicates
|
|
|
|
for arbitrarially generated input. Not all input is attempted; that'd take too long. However enough
|
|
|
|
is tried to be reasonably sure that an implementation works. The looseness of the verification is
|
|
|
|
made up for the fact that QuickCheck tests are *extremely* easy and quick to implement.
|
|
|
|
|
|
|
|
I used a very simple Makefile to manage the execution of tests. The usage followed:
|
|
|
|
make => built and ran all tests.
|
|
|
|
make TEST => build and run test with name TEST. The output for a test was logged to a "results"
|
2009-10-09 02:46:58 +04:00
|
|
|
directory. The results included a time and memory profile.
|
|
|
|
Nothing fancy, but enough to support a very quick modify/test cycle.
|
|
|
|
|
|
|
|
Compared to VTY 3 the optimization of VTY 4 was easier. Each test provided basic performance
|
|
|
|
feedback in addition to verifying correctness. I investigated significant changes in the performance
|
|
|
|
data and, for each case, determined if the change was acceptable or indicated a performance
|
|
|
|
regression. Any change in performance that was due to correcting the implementation was considered
|
|
|
|
acceptable. This is in contrast to VTY 3 where all the optimizations in the final release were
|
|
|
|
micro-optimizations: Hand application of primitive types and equations.
|
|
|
|
|
|
|
|
Sun Jan 11 13:37 2009 Time and Allocation Profiling Report (Final)
|
|
|
|
total time = 3.48 secs (174 ticks @ 20 ms)
|
|
|
|
total alloc = 2,542,866,800 bytes (excludes profiling overheads)
|
|
|
|
|
|
|
|
Current release of Vty with minimal use of primitive types but an entirely different algorithm:
|
|
|
|
|
|
|
|
Thu Sep 3 13:30 2009 Time and Allocation Profiling Report (Final)
|
|
|
|
total time = 1.84 secs (92 ticks @ 20 ms)
|
|
|
|
total alloc = 1,513,254,136 bytes (excludes profiling overheads)
|
2009-10-07 08:00:28 +04:00
|
|
|
|
2009-10-08 07:32:52 +04:00
|
|
|
|