If you're doing things like financial calculations, you have to be careful about using conventional binary floating point because it can't represent decimal fractions exactly.
One approach is to use "scaled" numbers, e.g. represent your dollar amount in cents or hundredths of cents so you are always working in integers. And it requires big integers, 32 bits is only about 9 decimal digits and the 52 bits of double floats is about 15. You really need 64 bit integers which are about 19 digits. (10 bits ~ 3 decimal digits) But that still doesn't give you the ability to deal with general purpose floating point.
So Suneido has always had a decimal floating point numeric type. (Internally, for performance, it also uses plain integers when possible.) Another advantage of a decimal type is that it is simple and quick to convert to and from string form.
Back when I first wrote Suneido (~ 15 years ago) there were no 64 bit integers in C++ compilers ("long" was 32 bits) and 32 bits wasn't sufficient precision. So I had to use multiple values to hold the coefficient. Since I had to use multiple integers anyway, to simplify overflow (by using 32 bit ints for intermediate results) I used four 16 bit ints, each one holding four decimal digits for an overall precision of 16 decimal digits. (To simplify "shifting" the exponent is in terms of the 16 bit ints, i.e. it jumps 4 decimals at a time. This "granularity" causes problems with precision. Depending on the exponent, in the worst case you get as few as 10 decimal digits of precision.)
Of course, having to use multiple integers and trying to get decent performance complicated the code, especially division. I won't claim it's the greatest code, but nevertheless it's worked reasonably well for a long time.
When I implemented jSuneido, I used Java's BigDecimal. Because of the different implementation there were a few minor differences, but they were mostly edge cases that didn't matter in practical usage. (Unfortunately I had made the external dump format for numbers mirror cSuneido's internal representation so it's a little awkward converting to and from BigDecimals.)
Recently, we've started to run into issues with using API's that deal with 64 bit integers, because we don't have enough precision to store them. In jSuneido it would be easy to bump up the BigDecimal precision to 20 digits. In theory I could do the same with cSuneido, but unfortunately, the code is fairly specific to the current precision. e.g. loops are unrolled. The thought of making this change is not pleasant :-(
The other problem is that some of the code assumes that you can convert to and from 64 bit integers losslessly. But 20 decimal digits won't always fit in a 64 bit integer.
Now that we have 64 bit integer types, the obvious answer seems to be to use a 64 bit integer for the coefficient. This will be faster and simpler than using multiple small integers, and probably faster than BigDecimal since it handles arbitrary precision. And if I used the same approach in both cSuneido and jSuneido this would ensure consistent results.
Since I'm in the middle of playing with Go, I figured I'd try writing a Go version first. It should be relatively easy to port to C++ and Java if I decide to.
It took me a couple of days to write it. One of the challenges is detecting overflow when calculating with 64 bit integers, since you don't have a larger type to use for intermediate calculations. Hacker's Delight provided a few tips for this. Another useful reference was General Decimal Arithmetic.
It's about 500 lines for add, subtract, multiply, divide, and conversion to and from strings. (That's about half the size of the cSuneido C++ code.) Since I'm new to Go, it may not be the most idiomatic code. And I have only done basic testing and refactoring. "float10" isn't the greatest name. Maybe "decimal" or even "dec"? (in keeping with Go's predilection for short names) I'm open to suggestions...
I chose to pass and return by value rather than by pointer. I'm not sure if this is the best choice for a 10 byte struct. Would it be faster to pass by pointer? Returning by pointer forces heap allocation for intermediate results which isn't ideal. Pass and return by value is a good fit for immutable values which are my preference.
Go makes it really easy to benchmark so I checked the speed of division (the slowest operation). Micro-benchmarks are always dubious, but it gave me a rough idea. It showed about 400 ns per divide (on my iMac). I don't have comparable benchmarks for cSuneido or jSuneido, but that seems pretty good. I'm pretty sure it's better than cSuneido. (Of course, it's nowhere near as fast as native binary floating point done in hardware. The same benchmark with float64 gives about 7 ns per divide, although this is so small that it's even less likely to be accurate.)
As far as evaluating Go, so far I like it. Of course, it's well suited to low level code like this. Sublime Text + GoSublime works well. (The only issues have been with learning Sublime since I haven't used it much.) I might have broken out the debugger a couple of times if I'd been working in Java or C++, but I get the impression the debugger story for Go isn't that great. I managed easily enough with old school prints :-) I plan to give Eclipse + GoClipse a try at some point since I'm already familiar with Eclipse.
The code is embedded below but it's probably easier to read (or download) on GitHub.
Thursday, April 03, 2014
Wednesday, April 02, 2014
TortoiseSVN + TortoiseHg Problem
I use Subversion (SVN) for cSuneido (for historical reasons) and Mercurial (Hg) for jSuneido, both on SourceForge (again for historical reasons).On Windows I use TortoiseSVN (1.8.5) and TortoiseHg (2.11.2) with Pageant (part of PuTTY, but supplied with TortoiseHg) so I don't have to type a password all the time. This combination has worked well for a long time.
I came into work this morning and TortoiseSVN kept popping up a Plink dialog asking for my password. That's what Pageant is supposed to avoid, especially since SourceForge needs an SSH key, not a password.
TortoiseHg was working fine, which meant Pageant was ok.
I used TortoiseSVN a few days ago. As far as I can recall I didn't change anything since then. But possibly I updated it. There are so many updates going by these days that it's hard to remember.
I searched the web but didn't find anything that seemed to be related.
I tried rebooting. I tried changing my path to put TortoiseHg and TortoiseSVN in the opposite order. Didn't help.
After some digging I found TortoiseHg was using older versions of TortoisePlink and pageant (both from 2012) whereas TortoiseSVN had a new TortoisePlink (from 2014). I wasn't sure it was a good idea, but I tried replacing the new TortoisePlink with the old one, thinking that maybe it needed to match the version of pageant.
That worked! Or at least appears to work. (I even rebooted to make sure the problem wouldn't come back.) It's probably going to break next time I update TortoiseSVN, and I'll probably forget the fix, but at least I'll have this blog post to jog my memory :-) And hopefully in the long run this will get sorted out. I can't be the only person running both. I'm not sure why TortoiseHg has such old versions. There seem to have been similar version issues a few years ago.
Monday, March 31, 2014
The Go Programming Language
Go has been around for a while. I looked at it when it first came out but didn't get too excited, partly because there weren't any good books about it, and that's how I like to investigate a language.
Recently I've been looking at it again. I've read two books - Programming in Go by Mark Summerfield and The Go Programming Language Phrasebook by David Chisnall. An Introduction to Programming in Go by Caleb Doxsey is available for free. These are decent, but so far I haven't found a Go book that I'd call great. There's a lot of material on the web which is useful, but I still prefer a good book. Also, most of the material is introductory - I haven't found much expert level material.
Here are my thoughts on Go (1.2), in no particular order. These are personal opinions and biased by thinking in terms of implementing Suneido, because that's the best basis for comparison that I have.
+ safe
Pointers, but no pointer arithmetic. Array bounds are checked. In theory, crash proof. (Like Java, unlike C/C++) But if you need it, there's the unsafe package.
+ garbage collection
Not as mature or high performance as Java, but steadily improving. And because not everything has to be on the heap (as in Java) there is less pressure on GC.
+ available on main platforms (Linux, Mac, Windows)
+ optional semicolons
Minor, but it's one less thing to type and to clutter up the code, and definitely my preference. I was always disappointed that D chose to keep semicolons.
+ capitalization for public / private
I'm biased since I use a similar approach in Suneido. The only (minor) thing I don't like about it is that you can't use capitalization to differentiate type names as is normal in C++ or Java.
+ goroutines and channels
An attractive alternative to threads and locks.
– no generics
Not so critical for application code, but this makes it really hard for library writers to provide things like alternate data structures. There's a built-in map that is generic, but if you want an ordered map, or a concurrent map or an immutable map you can't make it generic. Obviously, this is not an easy thing to add and it adds a lot of complexity to the language, but to me it's a drawback.
? built-in maps (hash tables)
Maps are obviously a good thing to have, but making them built-in to the language seems like an admission that the language itself is not sufficient to write such things (due to lack of generics and operator overloading)
+ fast compiles
This was one of the explicit goals of Go.
+ simple, standard build system
No complicated make or ant files.
+ native executables (no runtime required like Java or .NET)
I don't mind having a VM like Java or .NET, but it is an extra hassle. Even with .NET, which is part of Windows, you can run into versioning issues. And having to distribute a 90mb JRE is a pain.
+ variable types can be inferred from initialization
e.g. var x = 123 or just x := 123
This avoids having to duplicate long types e.g. m := map[long_type]another_long_type
+ multiple return values, multiple assignment
e.g. a,b = fn() or x,y = y,x
This means you don't have to create special data structures (and in Java, allocate them on the heap) just to return multiple values.
– no ? : ternary operator
In conjunction with mandatory braces, this turns thing like "return x < y ? x : y" into 5 lines of code. Supposedly ?: is hard to understand, personally I've never found it a problem. If you don't want the extra operators you can make if-then-else an expression like Scala and write "return if (x < y) x else y". However you write it, it's still a branch you have to understand.
+ standalone functions
Not just methods as in Java. (Java 8 now has lambdas, but still doesn't allow top-level functions.)
+ functions are first class values
You can pass around references to functions, put them in variables or data structures, etc.
+ closures
Function literals are automatically closures that can reference variables in their environment, even if the function outlasts the environment.
+ slices
A slice is a safe reference to a portion of an array. Unlike C/C++ pointers, a slice includes length and capacity. This is a big improvement over bare pointers. (D has similar slices.) See Arrays, slices (and strings)
– no immutable or read-only or const (other than const scalars)
I'm not surprised at this, but it's too bad. Immutability is very useful, especially with concurrency. Interestingly, Go strings are immutable (so at some level people see the benefit), but nothing else. One unfortunate result of this is that conversions between strings and arrays of bytes require copying, even though the underlying data is identical. This is another issue, like generics, that can add considerable complexity to the language and type system. And even where it is attempted, like C++ const, it often isn't ideal. The D language does have immutable and pure. But I would love to see immutable data and pure functions. (You can write your own immutable data structures, but again the lack of generics and operator overloading makes them clumsy.)
– no concurrent containers
I realize the Go way is to use channels and goroutines for concurrency, but I think there are still going to be times when a concurrent container would be useful and give better performance. And without generics, it's hard for libraries to provide this.
+ not everything on the heap (as opposed to Java)
You can embed one struct inside another without a pointer and a separate heap allocation. And you can pass or return a struct or put it in a variable by value, again meaning it doesn't have to be on the heap.
? no classes or inheritance
This is one of the more unconventional aspects of Go. Not having written much Go code it's hard to judge this. My feeling is that Go provides good alternatives. The only drawback may be porting existing code that uses classes and inheritance.
+ no separate primitives and boxed versions
This is a pain point in Java and a definite performance issue when implementing something like Suneido.
+ type declarations
Similar to a C/C++ typedef except that a Go type declaration introduces a new type. This is useful to create short name for a complex type, or to prevent mixing incompatible units e.g. celsius and fahrenheit.
+ can define methods on scalars (e.g. int or string), not just on classes
For example, you could declare Celsius as int, and then define a ToFahrenheit method on it. (This is a bit like the Common Lisp Object System)
+ "duck" typed interfaces
Types satisfy interfaces implicitly (by having the required methods), they do not have to explicitly declare what interfaces they satisfy. Very nice.
? no function overloading
Minor. It means more name variations, but keeps the language simpler.
? no operator overloading
Mostly not an issue. Makes it more awkward to use library provided data types.
+ flexible switch statement
Not limited to integer constants like C.
? no assert
This is explained in the FAQ but I'm not sure I agree. But it's minor because it's easy enough to write your own.
? error return values instead of exceptions, no try-catch-finally
This is different than what I'm used to but I haven't written enough Go code to really evaluate it. Go's panic and recover are sufficient to implement Suneido's exceptions. See the FAQ, Error handling and Go, and Defer, Panic, and Recover
+ can integrate with C
+ strings are arrays of bytes (generally but not necessarily UTF8), not wide characters
The D language also takes this approach. It makes sense to me, and also fits with how Suneido works. One of the advantages is that it reduces conversions when reading UTF8 files. It also reduces the size in memory when dealing with mostly ASCII. See Strings, bytes, runes and characters in Go
+ decent standard libraries and an increasing number of third party ones
+ standard testing framework
Basic but reasonable.
+ good standard tools
Nice to have standard tools for building, formatting, embedded documentation, profiling, race detection, etc.
– limited IDE and refactoring support
Although there are Eclipse and IntelliJ plugins available, they are fairly basic and don't include much refactoring support. I've been using the Sublime Text plugin. It sounds like the primary Go developers aren't IDE users so this area has lagged a bit. There is some limited refactoring ability in the go fmt tool. This area will likely improve over time.
+ standard formatting
Minor, but it's nice to sidestep any formatting debates. I like that they chose tabs for indenting, since that's always been my preference. But I'm sure it bugs people who prefer spaces.
Recently I've been looking at it again. I've read two books - Programming in Go by Mark Summerfield and The Go Programming Language Phrasebook by David Chisnall. An Introduction to Programming in Go by Caleb Doxsey is available for free. These are decent, but so far I haven't found a Go book that I'd call great. There's a lot of material on the web which is useful, but I still prefer a good book. Also, most of the material is introductory - I haven't found much expert level material.
Here are my thoughts on Go (1.2), in no particular order. These are personal opinions and biased by thinking in terms of implementing Suneido, because that's the best basis for comparison that I have.
+ safe
Pointers, but no pointer arithmetic. Array bounds are checked. In theory, crash proof. (Like Java, unlike C/C++) But if you need it, there's the unsafe package.
+ garbage collection
Not as mature or high performance as Java, but steadily improving. And because not everything has to be on the heap (as in Java) there is less pressure on GC.
+ available on main platforms (Linux, Mac, Windows)
+ optional semicolons
Minor, but it's one less thing to type and to clutter up the code, and definitely my preference. I was always disappointed that D chose to keep semicolons.
+ capitalization for public / private
I'm biased since I use a similar approach in Suneido. The only (minor) thing I don't like about it is that you can't use capitalization to differentiate type names as is normal in C++ or Java.
+ goroutines and channels
An attractive alternative to threads and locks.
– no generics
Not so critical for application code, but this makes it really hard for library writers to provide things like alternate data structures. There's a built-in map that is generic, but if you want an ordered map, or a concurrent map or an immutable map you can't make it generic. Obviously, this is not an easy thing to add and it adds a lot of complexity to the language, but to me it's a drawback.
? built-in maps (hash tables)
Maps are obviously a good thing to have, but making them built-in to the language seems like an admission that the language itself is not sufficient to write such things (due to lack of generics and operator overloading)
+ fast compiles
This was one of the explicit goals of Go.
+ simple, standard build system
No complicated make or ant files.
+ native executables (no runtime required like Java or .NET)
I don't mind having a VM like Java or .NET, but it is an extra hassle. Even with .NET, which is part of Windows, you can run into versioning issues. And having to distribute a 90mb JRE is a pain.
+ variable types can be inferred from initialization
e.g. var x = 123 or just x := 123
This avoids having to duplicate long types e.g. m := map[long_type]another_long_type
+ multiple return values, multiple assignment
e.g. a,b = fn() or x,y = y,x
This means you don't have to create special data structures (and in Java, allocate them on the heap) just to return multiple values.
– no ? : ternary operator
In conjunction with mandatory braces, this turns thing like "return x < y ? x : y" into 5 lines of code. Supposedly ?: is hard to understand, personally I've never found it a problem. If you don't want the extra operators you can make if-then-else an expression like Scala and write "return if (x < y) x else y". However you write it, it's still a branch you have to understand.
+ standalone functions
Not just methods as in Java. (Java 8 now has lambdas, but still doesn't allow top-level functions.)
+ functions are first class values
You can pass around references to functions, put them in variables or data structures, etc.
+ closures
Function literals are automatically closures that can reference variables in their environment, even if the function outlasts the environment.
+ slices
A slice is a safe reference to a portion of an array. Unlike C/C++ pointers, a slice includes length and capacity. This is a big improvement over bare pointers. (D has similar slices.) See Arrays, slices (and strings)
– no immutable or read-only or const (other than const scalars)
I'm not surprised at this, but it's too bad. Immutability is very useful, especially with concurrency. Interestingly, Go strings are immutable (so at some level people see the benefit), but nothing else. One unfortunate result of this is that conversions between strings and arrays of bytes require copying, even though the underlying data is identical. This is another issue, like generics, that can add considerable complexity to the language and type system. And even where it is attempted, like C++ const, it often isn't ideal. The D language does have immutable and pure. But I would love to see immutable data and pure functions. (You can write your own immutable data structures, but again the lack of generics and operator overloading makes them clumsy.)
– no concurrent containers
I realize the Go way is to use channels and goroutines for concurrency, but I think there are still going to be times when a concurrent container would be useful and give better performance. And without generics, it's hard for libraries to provide this.
+ not everything on the heap (as opposed to Java)
You can embed one struct inside another without a pointer and a separate heap allocation. And you can pass or return a struct or put it in a variable by value, again meaning it doesn't have to be on the heap.
? no classes or inheritance
This is one of the more unconventional aspects of Go. Not having written much Go code it's hard to judge this. My feeling is that Go provides good alternatives. The only drawback may be porting existing code that uses classes and inheritance.
+ no separate primitives and boxed versions
This is a pain point in Java and a definite performance issue when implementing something like Suneido.
+ type declarations
Similar to a C/C++ typedef except that a Go type declaration introduces a new type. This is useful to create short name for a complex type, or to prevent mixing incompatible units e.g. celsius and fahrenheit.
+ can define methods on scalars (e.g. int or string), not just on classes
For example, you could declare Celsius as int, and then define a ToFahrenheit method on it. (This is a bit like the Common Lisp Object System)
+ "duck" typed interfaces
Types satisfy interfaces implicitly (by having the required methods), they do not have to explicitly declare what interfaces they satisfy. Very nice.
? no function overloading
Minor. It means more name variations, but keeps the language simpler.
? no operator overloading
Mostly not an issue. Makes it more awkward to use library provided data types.
+ flexible switch statement
Not limited to integer constants like C.
? no assert
This is explained in the FAQ but I'm not sure I agree. But it's minor because it's easy enough to write your own.
? error return values instead of exceptions, no try-catch-finally
This is different than what I'm used to but I haven't written enough Go code to really evaluate it. Go's panic and recover are sufficient to implement Suneido's exceptions. See the FAQ, Error handling and Go, and Defer, Panic, and Recover
+ can integrate with C
+ strings are arrays of bytes (generally but not necessarily UTF8), not wide characters
The D language also takes this approach. It makes sense to me, and also fits with how Suneido works. One of the advantages is that it reduces conversions when reading UTF8 files. It also reduces the size in memory when dealing with mostly ASCII. See Strings, bytes, runes and characters in Go
+ decent standard libraries and an increasing number of third party ones
+ standard testing framework
Basic but reasonable.
+ good standard tools
Nice to have standard tools for building, formatting, embedded documentation, profiling, race detection, etc.
– limited IDE and refactoring support
Although there are Eclipse and IntelliJ plugins available, they are fairly basic and don't include much refactoring support. I've been using the Sublime Text plugin. It sounds like the primary Go developers aren't IDE users so this area has lagged a bit. There is some limited refactoring ability in the go fmt tool. This area will likely improve over time.
+ standard formatting
Minor, but it's nice to sidestep any formatting debates. I like that they chose tabs for indenting, since that's always been my preference. But I'm sure it bugs people who prefer spaces.
Wednesday, March 26, 2014
B-tree Range Estimation
One of the trickier issues to deal with in Suneido is when the database query optimizer picks what seems like a poor strategy. One of the difficulties is that these issues are usually data dependant - so you need to do the debugging on a large database.
Suneido's query optimizer is fairly straightforward, but even on relatively simple queries there are a lot of possible strategies and it can be quite hard to understand the end results.
We had an issue recently with jSuneido where the sort could be satisfied by an index, but the strategy it chose was reading by a different index and then sorting (with a temporary index).
It wasn't hard to discover that it thought reading by the other index was enough faster to justify the temporary index. The problem was it was wrong.
It came down to the the btree range estimation. It was estimating the same key range quite differently for the two indexes, where they should have been exactly the same.
One of the important choices in query optimization is which indexes to use. To help with this, Suneido uses the index B-trees to estimate how "selective" a given range of index keys is, i.e. what fraction of the records does a range include. Because the query optimizer looks at lots of possible strategies (combinatorial explosion) you want this estimation to be fast. You don't want to read the entire index.
Strangely, I used the identical approach on both cSuneido and jSuneido, but they ended up choosing different strategies for the same data.
The code was doing a lookup on the "from" and "to" keys, estimating the position of the key from the path down the tree. Because a single path down the tree only looks at a few nodes out of a potentially large number, it has to assume that the rest of the tree is balanced and node sizes are "average".
It didn't take long to discover a rather blatent bug in both jSuneido and cSuneido. I was averaging the node sizes of the "from" search and the "to" search in order to get a better idea of the average node size on each level. But I wasn't adjusting the position within the node. For example, if the "from" search went through position 5 of a node of size 10, and the "to" search went through position 25 of a node of size 30, when I average the node sizes it now thought the "to" search went through position 25 of a node of size 20 - obviously wrong. On the positive side, if the node sizes don't vary too much then it doesn't have a big affect.
One reason why this bug hadn't surfaced yet is that the exact estimates aren't that important since the optimizer is just comparing whether different indexes are better or worse, not what the absolute numbers are.
That bug was easy enough to fix, and I came up with a much simpler way to write the code, but I still wasn't getting very accurate results. I started running the numbers through a spreadsheet to try to see what was wrong with my calculations.
After banging my head against the wall for a long time I finally realized that the problem was that the trees were actually not very balanced. A B-tree only guarantees that the tree is balanced in terms of height i.e. all the branches are the same length. It does not guarantee that each branch of the tree leads to the same number of keys. Most B-tree allow nodes to vary between half empty and full. When a node gets full it is split into two half full nodes.
However, if you add keys in order, this leads to most nodes only being half full. So Suneido uses a common optimization to split unevenly if a node gets full by adding on the end. In the "worst" case (in terms of balance) this leads to the root of the tree having a left branch that is full, and a right branch that has a single key. That is exactly what my data looked like in the "bad" case, but I hadn't realized the significance.
Side note - this optimization applies more to B-trees that use fixed node sizes, like cSuneido. jSuneido's append-only database uses variable sized nodes, so space wastage isn't an issue. However, you still want to keep the branching high to minimize the height of the tree.
My solution was for the estimation code to look at the sizes of all the nodes on the first and second level of the tree (root and one level below), rather than assume they were all the same average size. This doesn't handle the tree being unbalanced below there, but the lower levels of the tree have less of an effect on the final result. Finally I got some results that were at least in the right ballpark, and good enough so that jSuneido now chooses the "correct" strategy.
Maybe I've been spoiled by Java, but the unsafe nature of C++ scares me. I made a stupid bug in the cSuneido changes that wrote one past the end of an array. This passed all the unit tests, but gave some strange random errors later on. In this case it wasn't hard to find the problem, but it makes me wonder.
As usual the code is in version control on SourceForge.
Suneido's query optimizer is fairly straightforward, but even on relatively simple queries there are a lot of possible strategies and it can be quite hard to understand the end results.
We had an issue recently with jSuneido where the sort could be satisfied by an index, but the strategy it chose was reading by a different index and then sorting (with a temporary index).
It wasn't hard to discover that it thought reading by the other index was enough faster to justify the temporary index. The problem was it was wrong.
It came down to the the btree range estimation. It was estimating the same key range quite differently for the two indexes, where they should have been exactly the same.
One of the important choices in query optimization is which indexes to use. To help with this, Suneido uses the index B-trees to estimate how "selective" a given range of index keys is, i.e. what fraction of the records does a range include. Because the query optimizer looks at lots of possible strategies (combinatorial explosion) you want this estimation to be fast. You don't want to read the entire index.
Strangely, I used the identical approach on both cSuneido and jSuneido, but they ended up choosing different strategies for the same data.
The code was doing a lookup on the "from" and "to" keys, estimating the position of the key from the path down the tree. Because a single path down the tree only looks at a few nodes out of a potentially large number, it has to assume that the rest of the tree is balanced and node sizes are "average".
It didn't take long to discover a rather blatent bug in both jSuneido and cSuneido. I was averaging the node sizes of the "from" search and the "to" search in order to get a better idea of the average node size on each level. But I wasn't adjusting the position within the node. For example, if the "from" search went through position 5 of a node of size 10, and the "to" search went through position 25 of a node of size 30, when I average the node sizes it now thought the "to" search went through position 25 of a node of size 20 - obviously wrong. On the positive side, if the node sizes don't vary too much then it doesn't have a big affect.
One reason why this bug hadn't surfaced yet is that the exact estimates aren't that important since the optimizer is just comparing whether different indexes are better or worse, not what the absolute numbers are.
That bug was easy enough to fix, and I came up with a much simpler way to write the code, but I still wasn't getting very accurate results. I started running the numbers through a spreadsheet to try to see what was wrong with my calculations.
After banging my head against the wall for a long time I finally realized that the problem was that the trees were actually not very balanced. A B-tree only guarantees that the tree is balanced in terms of height i.e. all the branches are the same length. It does not guarantee that each branch of the tree leads to the same number of keys. Most B-tree allow nodes to vary between half empty and full. When a node gets full it is split into two half full nodes.
However, if you add keys in order, this leads to most nodes only being half full. So Suneido uses a common optimization to split unevenly if a node gets full by adding on the end. In the "worst" case (in terms of balance) this leads to the root of the tree having a left branch that is full, and a right branch that has a single key. That is exactly what my data looked like in the "bad" case, but I hadn't realized the significance.
Side note - this optimization applies more to B-trees that use fixed node sizes, like cSuneido. jSuneido's append-only database uses variable sized nodes, so space wastage isn't an issue. However, you still want to keep the branching high to minimize the height of the tree.
My solution was for the estimation code to look at the sizes of all the nodes on the first and second level of the tree (root and one level below), rather than assume they were all the same average size. This doesn't handle the tree being unbalanced below there, but the lower levels of the tree have less of an effect on the final result. Finally I got some results that were at least in the right ballpark, and good enough so that jSuneido now chooses the "correct" strategy.
Maybe I've been spoiled by Java, but the unsafe nature of C++ scares me. I made a stupid bug in the cSuneido changes that wrote one past the end of an array. This passed all the unit tests, but gave some strange random errors later on. In this case it wasn't hard to find the problem, but it makes me wonder.
As usual the code is in version control on SourceForge.
Thursday, March 20, 2014
A Curious Error
I got this curious error from Evernote on the Mac:
I'm pretty sure that small amount of text doesn't exceed 100mb :-)
Maybe it thought I was trying to insert something huge, if so, it wasn't anything I did intentionally. (I think I hit the TAB key, but when I tried that again it didn't give any errors.)
I'm pretty sure that small amount of text doesn't exceed 100mb :-)
Maybe it thought I was trying to insert something huge, if so, it wasn't anything I did intentionally. (I think I hit the TAB key, but when I tried that again it didn't give any errors.)
Tuesday, March 18, 2014
Eclipse Crashing
I got back from six weeks travel and fired up Eclipse (4.3 Kepler on Mac OS X). Not surprisingly, there were updates so I said ok to install them and restarted when prompted. Except instead of restarting it crashed :-( I tried restarting several more times but it kept crashing.
Rather than waste a lot of time figuring out what went wrong it was quicker to rename my old copy of Eclipse, download a new copy, and then import my plugins from the old install. (One of the nice things about Eclipse is that it's just a folder, and you can trivially have multiple installs.)
The only other thing I had to do was check which Java Eclipse was using (Eclipse > Preferences > Java > Installed JREs). I found it only listed an old version so I used the Search button to find the new one, made it the default, quit from Eclipse, and deleted the old version of Java to ensure nothing would use it.
You'd think that leaving a computer turned off would ensure that everything would work when you got back (barring hardware failures). But interrupting the modern day firehose of updates unfortunately often leads to problems. (Although in theory it shouldn't.)
Rather than waste a lot of time figuring out what went wrong it was quicker to rename my old copy of Eclipse, download a new copy, and then import my plugins from the old install. (One of the nice things about Eclipse is that it's just a folder, and you can trivially have multiple installs.)
The only other thing I had to do was check which Java Eclipse was using (Eclipse > Preferences > Java > Installed JREs). I found it only listed an old version so I used the Search button to find the new one, made it the default, quit from Eclipse, and deleted the old version of Java to ensure nothing would use it.
You'd think that leaving a computer turned off would ensure that everything would work when you got back (barring hardware failures). But interrupting the modern day firehose of updates unfortunately often leads to problems. (Although in theory it shouldn't.)
Sunday, January 26, 2014
Java 8
I just finished reading Java SE 8 for the Really Impatient by Cay S. Horstmann. I'd recommend it as an introduction to Java 8 features. I've also read Cay's Scala for the Impatient, and used his Core Java books for reference. No doubt there will be other Java 8 books arriving soon. Pragmatic Programmers has Functional Programming in Java: Harnessing the Power of Java 8 Lambda Expressions in beta. And Manning has Java 8 Lambdas in Action in Early Access. But unless I'm in a rush to learn about something, I don't usually go for beta or early access versions because I'd rather wait and read the final product.
Java 8 has seemed so far in the future that I haven't been thinking about it much. But I see it's supposed to be released in March, which isn't really that far off.
If I was starting a JVM project from scratch, I'd probably lean towards Scala. But for maintaining and improving the jSuneido Java code, I'm looking forward to Java 8, especially lambdas.
The current release version of Eclipse doesn't support Java 8, but there are early access releases available. Presumably support will be included in the next version of Eclipse. Intellij also has early support for Java 8.
Netbeans may be the best bet right now. Version 7.4 has Java 8 support, and the beta for Netbeans 8 is available.
Java 8 has seemed so far in the future that I haven't been thinking about it much. But I see it's supposed to be released in March, which isn't really that far off.
If I was starting a JVM project from scratch, I'd probably lean towards Scala. But for maintaining and improving the jSuneido Java code, I'm looking forward to Java 8, especially lambdas.
The current release version of Eclipse doesn't support Java 8, but there are early access releases available. Presumably support will be included in the next version of Eclipse. Intellij also has early support for Java 8.
Netbeans may be the best bet right now. Version 7.4 has Java 8 support, and the beta for Netbeans 8 is available.
Thursday, January 23, 2014
jSuneido Network Bug
Our installations run a scheduler as a client. (On jSuneido this could probably just be a thread on the server, but cSuneido is single threaded.)
On Windows, when we shut down the server, the scheduler will exit. On Linux the scheduler would hang.
I narrowed it down to a simple test case (on OS X, which seemed to behave like Linux)
- start the server
- start a client REPL
- from the client,execute: ServerEval("Exit")
- server exits
- client hangs (on Linux but not on Windows)
Strangely, if you killed the server (with Ctrl+C) then the client would get an exception instead of hanging.
I assumed that the client was blocking when it tried to read the response from the ServerEval (which never came because the server had terminated). And that on Linux, for some reason, it didn't recognize the socket was closed when it was blocked reading, although that didn't make a lot of sense.
I ran the client in the debugger and when it was hung, I paused it to see where it was. Sure enough it was in the socket read.
I searched the web trying to find anything related. There wasn't much, which was surprising. Most problems like this are documented by someone.
But I did notice some of the code examples were checking the return value from channel.read and I wasn't. The documentation said read returns -1 when "the channel has reached end-of-stream". That didn't sound like channel closed to me, but it seemed like I should be checking it anyway.
And that was the problem. It wasn't actually blocking on the read, the read was returning -1, but I was looping until I read all the data, and that's what was hanging it.
To verify, I restored the code, made it hang, and checked the CPU usage - sure enough the client Java was at 100% (because it was in a tight loop calling channel.read over and over).
I'm still not sure why killing the server behaves differently from exiting normally. I guess the socket gets closed differently. (i.e. gracefully or not)
I'm still not sure why killing the server behaves differently from exiting normally. I guess the socket gets closed differently. (i.e. gracefully or not)
In hindsight it seems like an obvious bug in my code (not checking the return value). I think what threw me off was that it worked fine in Windows. Java is usually pretty good at hiding operating system differences, but not in this case.
Tuesday, January 21, 2014
Building cSuneido with Visual Studio 2013
It's not that long ago since I switched to building cSuneido with VS 2012, but after listening to some Channel 9 podcasts about enhancements to the C++ compiler I figured I should give the new version a try.Note: Confusingly, Visual Studio 2013 = Visual C++ version 12 - off by one error :-)
The version I'm using is the free Visual Studio Express 2013 for Windows Desktop.
I started a new solution and projects rather than convert / update the existing ones so I wouldn't bring over any undesired garbage. Of course, starting from scratch meant running into some of the same errors as other times, but at least it's a little fresher in my mind this time.
One advantage of VS 2013 is that it came with support for building XP compatible applications. Originally they didn't have this in VS 2012 and it was added later (probably after the outcry from developers). I'd prefer to drop support for XP but we still have a lot of customers running it. We're working on getting them to upgrade.
I fixed a few more warnings in the code that the new compiler found, but other than that it went pretty smoothly.
I haven't measured the speed of the resulting executable, but from running the tests etc. it doesn't seem like there's significant difference.
Assuming we don't find any problems we'll switch to using this version for our customers.
As usual, the code changes and the Visual Studio solution and projects are in version control on SourceForge. If you try building it, let me know how it goes.
Wednesday, January 08, 2014
A User Interface Detail
I recently read Microinteractions. (recommended) It gives lots of small examples of user interface/experience, many from Little Big Details. Which made me think of one detail from Suneido.
In Suneido's IDE, the LibraryView code editor has tabs, like a lot of editors and IDE's and other software like browsers. Suneido uses the Windows tab control - pretty standard.
The Windows tab control lets you put an icon on the tab, again, nothing new.
We also have a right-click context menu on the tab with the usual option for closing the tab. But that's awkward if you want to close multiple tabs. I wanted a "close" button on the tabs, like you see in a lot of places, eg. Chrome
But the Windows tab control doesn't have an easy way to do that (AFAIK), so I "cheated" and just switched the icon when you moused over the tab. (Note: You still have to click on the actual close button, clicking anywhere else on the tab just selects it.)
Although I did it that way for expediency, it turned out to have a few nice benefits. One is that the close button doesn't take up space on every tab. Eclipse only shows the close button on the current selected tab, but it still reserves the space for it on all the other tabs:
But the benefit that I really like is that you can close a series of contiguous tabs by repeatedly clicking the close button of the leftmost one without moving the mouse. Whereas when the close button is on the right hand side of variable length tabs, you have to move the mouse to a new position after closing each tab. Chrome has mostly fixed length tabs, but they shrink/expand when required which still throws off the positioning. Admittedly, in some cases you could use the right-click context menu to close all the tabs, or close all except the current one. But this is simpler and also works to close N contiguous tabs by simply clicking N times in the same spot.
The downside of not always showing the close button is that it's not as discover-able. But in this case I don't think that's a big deal.
Obviously this is a pretty minor issue, but it surprises me that other tab controls (AFAIK) haven't used this approach.
In Suneido's IDE, the LibraryView code editor has tabs, like a lot of editors and IDE's and other software like browsers. Suneido uses the Windows tab control - pretty standard.
The Windows tab control lets you put an icon on the tab, again, nothing new.
We also have a right-click context menu on the tab with the usual option for closing the tab. But that's awkward if you want to close multiple tabs. I wanted a "close" button on the tabs, like you see in a lot of places, eg. Chrome
But the Windows tab control doesn't have an easy way to do that (AFAIK), so I "cheated" and just switched the icon when you moused over the tab. (Note: You still have to click on the actual close button, clicking anywhere else on the tab just selects it.)
Although I did it that way for expediency, it turned out to have a few nice benefits. One is that the close button doesn't take up space on every tab. Eclipse only shows the close button on the current selected tab, but it still reserves the space for it on all the other tabs:
But the benefit that I really like is that you can close a series of contiguous tabs by repeatedly clicking the close button of the leftmost one without moving the mouse. Whereas when the close button is on the right hand side of variable length tabs, you have to move the mouse to a new position after closing each tab. Chrome has mostly fixed length tabs, but they shrink/expand when required which still throws off the positioning. Admittedly, in some cases you could use the right-click context menu to close all the tabs, or close all except the current one. But this is simpler and also works to close N contiguous tabs by simply clicking N times in the same spot.
The downside of not always showing the close button is that it's not as discover-able. But in this case I don't think that's a big deal.
Obviously this is a pretty minor issue, but it surprises me that other tab controls (AFAIK) haven't used this approach.
Saturday, November 09, 2013
Hi-tech Travelling Blues
[On the train to Montreal.] After getting my fill of reading I pulled out my laptop to do a little programming. I clicked on Eclipse and got a message that I needed Java 6 and it wouldn't start. It helpfully offered to install one but I had no internet so that failed. This was working fine when I used it a few days ago so I assume an OS X update removed it.
I like automatic updates but only if they don't blatantly break stuff. I've already gone through this reinstall of Java multiple times. It's not a big deal when you're connected. I understand the security issues, but that's pretty much all related to the browser plugin. I'm not sure why it has to keep removing Java entirely rather than just the plugin.
I hunted around and found I still had several copies of Java 7 but I couldn't figure out how to get Eclipse to use them. I found a spot in the plist where you could specify a particular JVM but that didn't seem to help. I'm not sure why it was insisting on Java 6.
There's probably a way to get it to work but I didn't have the patience and gave up frustrated. After all, I am on holidays, and this was supposed to be recreational programming!
But five minutes later I remembered that I had a computer within my computer - my Windows VM in Parallels, where I also have Eclipse. I got my laptop back out and fired up Parallels. Sure enough, that copy was out of reach of OS X updates and was still functional. (Windows updates haven't decided to arbitrarily remove Java, yet.)
I don't normally do my Eclipse development under Windows on my MacBook so this copy of my source code was out of date but I could get the files I needed from OS X.
So in the end I got operational. During our layover in Toronto I managed to install the missing JVM using Starbucks Wifi so I should be functional on OS X again. And just in case, I brought the Windows copy of my source code up to date. (Although for some reason it failed when I tried to pull the changes on OS X. Argh!)
Now I just have to figure out why Eclipse is using Java 6 instead of 7 ...
Tuesday, October 22, 2013
Updating Source Code to Java 7
I had this vague memory that NetBeans had a way to upgrade source code to Java 7, which seemed like a good thing to do. But when I searched on the web I couldn't find much. I did find stuff about the IDE giving hints and fixes individually in the IDE but nothing about mass changes. (Which is partly what prompted me to write this.) I also looked for a way to do it with Eclipse but didn't find anything.
So I downloaded the latest NetBeans (7.4) and hunted through the menus. I found Refactor > Inspect and Transform which has a Configuration choice for Convert to JDK 7.
TIP: Set your tab and formatting (e.g. switches) preferences before you run Inspect and Transform. It didn't seem to work correctly when I changed the preferences while Inspect and Transform was open.
It found the following applicable changes in my code:
The majority were diamond inference. There would have been even more, but I already used Guava's helpers such as Lists.newArrayList which avoid repeating the generic types.
Convert to try-with-resources didn't merge surrounding try-catch's but there were only a few of these so it was easy to fix them manually.
I wasn't too sure about the last item - replacing catching general exceptions with catching multiple specific exceptions. It seemed like it wouldn't catch everything it did before so I didn't accept those changes. (unchecked them)
But when I clicked on Do Refactoring I got a little "Refactoring" window that was blank. I assumed it was working and left it for a while. But it never went away, and when I tried to close it I couldn't. So I exited out of NetBeans (with no problems) and tried it again. This time nothing happened (except the Inspect and Transform window closed). I thought maybe it was finished but nothing had changed. I ran it again and got only a few of the issues and Do Refactoring worked (on those few). Next time I ran it, I got the long list again. I finally noticed the error marker in the bottom right. I submitted the error and it appears to be a known bug :-(
I ended up doing one package at a time and that seemed to work fine.
So I downloaded the latest NetBeans (7.4) and hunted through the menus. I found Refactor > Inspect and Transform which has a Configuration choice for Convert to JDK 7.
TIP: Set your tab and formatting (e.g. switches) preferences before you run Inspect and Transform. It didn't seem to work correctly when I changed the preferences while Inspect and Transform was open.
It found the following applicable changes in my code:
- Use diamond inference
- Convert to switch over strings
- Convert to try-with-resources
- Replace with multicatch
- Replace with multicatch catching specific exceptions
The majority were diamond inference. There would have been even more, but I already used Guava's helpers such as Lists.newArrayList which avoid repeating the generic types.
Convert to try-with-resources didn't merge surrounding try-catch's but there were only a few of these so it was easy to fix them manually.
I wasn't too sure about the last item - replacing catching general exceptions with catching multiple specific exceptions. It seemed like it wouldn't catch everything it did before so I didn't accept those changes. (unchecked them)
But when I clicked on Do Refactoring I got a little "Refactoring" window that was blank. I assumed it was working and left it for a while. But it never went away, and when I tried to close it I couldn't. So I exited out of NetBeans (with no problems) and tried it again. This time nothing happened (except the Inspect and Transform window closed). I thought maybe it was finished but nothing had changed. I ran it again and got only a few of the issues and Do Refactoring worked (on those few). Next time I ran it, I got the long list again. I finally noticed the error marker in the bottom right. I submitted the error and it appears to be a known bug :-(
I ended up doing one package at a time and that seemed to work fine.
Monday, October 21, 2013
Fixing a Suneido Design Problem
In Suneido object.Delete(key) returns false if the member isn't found, otherwise it returns the object.
Several times we've had bugs resulting from doing things like:
object.Delete(key).Add(...)
which works as expected, except when key isn't found and Delete returns false.
It's likely the worst of possible designs. It would have been better if it returned nothing, or true/false, or my favorite - always the object.
I've been aware of this problem for quite a while but I was hesitant to change it because I was afraid I'd break existing code. I finally decided to go through our code and see if it would actually break much.
There were about 800 uses of Delete.
By far the majority ignored the return value.
I didn't find a single place where we made use of the false return value.
I did find a number of uses which assumed that it always returned the object - i.e. potential bugs.
Other than being tedious, the worst part was seeing all the ugly code. I wonder if it's possible to write code that doesn't make you cringe when you come back to it later.
I found quite a few places where we were doing multiple deletes so while I was at it, I changed Delete to handle multiple arguments.
This is a small change, but I think it's just as important to get the details right as it is to work on the big picture.
Several times we've had bugs resulting from doing things like:
object.Delete(key).Add(...)
which works as expected, except when key isn't found and Delete returns false.
It's likely the worst of possible designs. It would have been better if it returned nothing, or true/false, or my favorite - always the object.
I've been aware of this problem for quite a while but I was hesitant to change it because I was afraid I'd break existing code. I finally decided to go through our code and see if it would actually break much.
There were about 800 uses of Delete.
By far the majority ignored the return value.
I didn't find a single place where we made use of the false return value.
I did find a number of uses which assumed that it always returned the object - i.e. potential bugs.
Other than being tedious, the worst part was seeing all the ugly code. I wonder if it's possible to write code that doesn't make you cringe when you come back to it later.
I found quite a few places where we were doing multiple deletes so while I was at it, I changed Delete to handle multiple arguments.
This is a small change, but I think it's just as important to get the details right as it is to work on the big picture.
Monday, October 14, 2013
More Hamcrest Hassles
The Hamcrest library is very useful. So useful that other useful libraries, like JUnit, include pieces of it. And then JUnit is so useful that Eclipse includes its own copy.
Not surprisingly, having multiple copies, of different versions, of different subsets, with some signed and some not, results in numerous problems. For accounts of my own experiences see: Upgrading to Eclipse Kepler 4.3, Eclipse Hamcrest Runaround, and I Give Up. Searching the web will find lots of other people with similar problems.
Once again, I had thought I had solved this by not using Eclipse's copy of JUnit. Everything has appeared to be working fine for months.
Until I was adding a test today. In good TDD style, I started by adding the test before making the change. It failed, as expected, but not with the error message I expected. I got:
java.lang.NoSuchMethodError: org.hamcrest.Matcher.describeMismatch
I thought maybe it was because I was using org.junit.Assert.assertThat instead of org.hamcrest.MatcherAssert.assertThat - but switching it didn't fix anything.
I also noticed that Eclipse was marking org.hamcrest.CoreMatchers.is as deprecated. Maybe it should be org.hamcrest.Matchers.is? Nope, still deprecated. I eventually found a comment on Stack Overflow that there are three overloads of "is" and it's only the class one that is deprecated, not the one I'm using. So I guess I just live with that warning :-(
Another comment on Stack Overflow mentioned that Mockito also included it's own version of Hamcrest. Hmmm... I don't think I was aware of that before.
One of the suggested solutions to this problem is to rearrange the order of the jar files on the class path. I had tried that previously without any success. But I was only moving JUnit and Hamcrest, since I wasn't aware that Mockito was also involved.
I find the Eclipse project properties Java Build Path a little confusing. There's a Libraries tab that lists all the jars alphabetically and you can't change the order. And then there's an Order and Export tab where you can change the order. I'm not sure why two tabs are needed. There's also a Referenced Libraries in the Package Explorer that does show the actual order, but doesn't let you change it.
I moved Mockito down in the list so it was below Hamcrest (JUnit was already below) and sure enough, that solved the problem.
Except now I had a different error in another test. That one turned out to be because I was using "is" with a class. Maybe it's deprecated in one of the versions/copies of Hamcrest but removed in the version of Hamcrest I'm explicitly including? Luckily this was simple to fix by just changing it to instanceOf.
It looks like you can download separate jars for Mockito which would let you leave out it's copy of Hamcrest. Except that it appears to be using a different version of Hamcrest (1.1) from the one I'm using (1.3). I have no idea if that would cause problems, but since it's currently not broken, I don't think I'll try to fix it!
Probably someone out there will tell me that I should be using Maven to manage dependencies, and maybe I should. But I'm not so sure that would eliminate these problems. I see several comments on the web about the same issues when using Maven.
Not surprisingly, having multiple copies, of different versions, of different subsets, with some signed and some not, results in numerous problems. For accounts of my own experiences see: Upgrading to Eclipse Kepler 4.3, Eclipse Hamcrest Runaround, and I Give Up. Searching the web will find lots of other people with similar problems.
Once again, I had thought I had solved this by not using Eclipse's copy of JUnit. Everything has appeared to be working fine for months.
Until I was adding a test today. In good TDD style, I started by adding the test before making the change. It failed, as expected, but not with the error message I expected. I got:
java.lang.NoSuchMethodError: org.hamcrest.Matcher.describeMismatch
I thought maybe it was because I was using org.junit.Assert.assertThat instead of org.hamcrest.MatcherAssert.assertThat - but switching it didn't fix anything.
I also noticed that Eclipse was marking org.hamcrest.CoreMatchers.is as deprecated. Maybe it should be org.hamcrest.Matchers.is? Nope, still deprecated. I eventually found a comment on Stack Overflow that there are three overloads of "is" and it's only the class one that is deprecated, not the one I'm using. So I guess I just live with that warning :-(
Another comment on Stack Overflow mentioned that Mockito also included it's own version of Hamcrest. Hmmm... I don't think I was aware of that before.
One of the suggested solutions to this problem is to rearrange the order of the jar files on the class path. I had tried that previously without any success. But I was only moving JUnit and Hamcrest, since I wasn't aware that Mockito was also involved.
I find the Eclipse project properties Java Build Path a little confusing. There's a Libraries tab that lists all the jars alphabetically and you can't change the order. And then there's an Order and Export tab where you can change the order. I'm not sure why two tabs are needed. There's also a Referenced Libraries in the Package Explorer that does show the actual order, but doesn't let you change it.
I moved Mockito down in the list so it was below Hamcrest (JUnit was already below) and sure enough, that solved the problem.
Except now I had a different error in another test. That one turned out to be because I was using "is" with a class. Maybe it's deprecated in one of the versions/copies of Hamcrest but removed in the version of Hamcrest I'm explicitly including? Luckily this was simple to fix by just changing it to instanceOf.
It looks like you can download separate jars for Mockito which would let you leave out it's copy of Hamcrest. Except that it appears to be using a different version of Hamcrest (1.1) from the one I'm using (1.3). I have no idea if that would cause problems, but since it's currently not broken, I don't think I'll try to fix it!
Probably someone out there will tell me that I should be using Maven to manage dependencies, and maybe I should. But I'm not so sure that would eliminate these problems. I see several comments on the web about the same issues when using Maven.
Sunday, October 13, 2013
jSuneido GUI
What's special about this screenshot of the IDE isn't what's visible.
It's that this is running on jSuneido! (the Java implementation of Suneido).
Up till now jSuneido has only been the server side. The only "UI" it had was a command line REPL.
But in the long run, I'd rather not support two implementations of Suneido. It would be nice if we could just use jSuneido since it's a better implementation.
Suneido's user interface is Win32 based and implemented with the DLL interface. None of the UI is built into the exe, it's all Suneido code in stdlib. (Other than a few support functions.)
Ideally, we'd switch to a portable GUI, but that's a huge job and would likely mean a bunch of changes to our application code.
So we decided to see if we could implement a Windows DLL interface in jSuneido and get Suneido's existing GUI to run on it.
One of Suneido's early programmers , Victor Schappert, returned to us and worked on this project. Thanks Victor!
As you can see, it's far enough along to run most of the IDE. but we still have a few things left to do like the COM interface and SuneidoAPP interface to the IE browser component.
As usual, the code is in version control on SourceForge. The JSDI project in Mercurial (Hg) contains a support dll for jSuneido (written in C++) that jSuneido talks to via JNI. A pre-built version of jsdi.dll is included in the jSuneido project.
Monday, August 12, 2013
A Recurring Lack of Assertiveness
We recently ran into a problem with jSuneido - when you loaded a certain customer's dumped database it would load successfully but then fail the consistency checks.
After looking at where and how it was failing (a negative size value), I figured the problem was probably that the database had a single table larger than 2 gb and I was using a 32 bit int to store the size of commits so it was wrapping around. Normally commits wouldn't be anywhere near that large, but when bulk loading each table is written as a single commit.
I felt pretty good about finding the problem so quickly and easily.
I put asserts into the code to confirm that this was the problem. But they didn't fail. Hmmm... maybe I put them in the wrong place. I added more asserts in other places. They still didn't fail.
So I fell back on the age old debugging method of inserting print's. Of course, it's a big database with a lot of tables so there was lots of output. I skimmed through it and couldn't find a table bigger than 2gb.
So much for finding the problem quickly and easily!
The next day, at home, I continued working on it, partly just trying to remember how the database code works! On the positive side, I updated some comments and diagrams while I was at it.
Eventually, I ended up full circle, finding that there was indeed a table bigger than 2gb and my original guess about the problem was correct! Argh! (I'd missed it when I skimmed through the prints.)
The problem was that I didn't have assert's enabled, which is why the asserts I added didn't fail. I've been burnt by this before - see Don't Forget to enable Java assert and Burnt by Java assert Again. You'd think I would learn.
Part of the problem is the way Eclipse works. You can set options on JRE's, but when you update to a new version of Java, then you have to remember to set the options again (which I had forgotten to do, both at work and at home). It's too bad there isn't a way to set options that are common to all JRE's.
You can also set options in Eclipse launch configurations, but I have a ton of them, and again (AFAIK) there isn't a way to set default options that are common to all launch configurations.
I thought I had good defenses in place for this. I have a test which confirms that assert is enabled. But I'm using Infinitest to run my tests automatically and it must enable asserts itself. So unless I run the test manually, it's useless for confirming that I have asserts enabled.
I also enable asserts programmatically in the start-up code. But while I was testing I was running specific classes and bypassing the start-up code.
I'm not sure what else I can do to defend against this. Any suggestions?
After looking at where and how it was failing (a negative size value), I figured the problem was probably that the database had a single table larger than 2 gb and I was using a 32 bit int to store the size of commits so it was wrapping around. Normally commits wouldn't be anywhere near that large, but when bulk loading each table is written as a single commit.
I felt pretty good about finding the problem so quickly and easily.
I put asserts into the code to confirm that this was the problem. But they didn't fail. Hmmm... maybe I put them in the wrong place. I added more asserts in other places. They still didn't fail.
So I fell back on the age old debugging method of inserting print's. Of course, it's a big database with a lot of tables so there was lots of output. I skimmed through it and couldn't find a table bigger than 2gb.
So much for finding the problem quickly and easily!
The next day, at home, I continued working on it, partly just trying to remember how the database code works! On the positive side, I updated some comments and diagrams while I was at it.
Eventually, I ended up full circle, finding that there was indeed a table bigger than 2gb and my original guess about the problem was correct! Argh! (I'd missed it when I skimmed through the prints.)
The problem was that I didn't have assert's enabled, which is why the asserts I added didn't fail. I've been burnt by this before - see Don't Forget to enable Java assert and Burnt by Java assert Again. You'd think I would learn.
Part of the problem is the way Eclipse works. You can set options on JRE's, but when you update to a new version of Java, then you have to remember to set the options again (which I had forgotten to do, both at work and at home). It's too bad there isn't a way to set options that are common to all JRE's.
You can also set options in Eclipse launch configurations, but I have a ton of them, and again (AFAIK) there isn't a way to set default options that are common to all launch configurations.
I thought I had good defenses in place for this. I have a test which confirms that assert is enabled. But I'm using Infinitest to run my tests automatically and it must enable asserts itself. So unless I run the test manually, it's useless for confirming that I have asserts enabled.
I also enable asserts programmatically in the start-up code. But while I was testing I was running specific classes and bypassing the start-up code.
I'm not sure what else I can do to defend against this. Any suggestions?
Thursday, August 01, 2013
Upgrading to Eclipse 4.3 Kepler
Another relatively smooth upgrade.
I downloaded the Eclipse IDE for Java Developers (rather than Standard) since I don't do any plugin development.
See Top 10 Eclipse Kepler Features
I did have one problem on Mac OS X - when you try to run Eclipse you get an error that it is "damaged and can't be opened". I'd run into this before. It's a known issue which, for some reason, the Eclipse developers have closed as "RESOLVED NOT_ECLIPSE" i.e. not their problem. However, as one of the commenters points out, other apps don't have this problem, so it's obviously something Eclipse is doing different if not technically "wrong". It's relatively easy to work around by doing:
xattr -d com.apple.quarantine Eclipse.app/
I imported my plug-ins from my previous Eclipse 4.2 Juno and they all came across without any problems. Even Mercurial still seems to be functional. Here's the list of plug-ins I'm currently using:
I knew I'd run into this before so I went to my blog to see how I'd resolved it. I found Eclipse Hamcrest Runaround and I Give Up but nothing about how I eventually got it to work. It's funny how my blogs have become my external memory and I'm unhappy if I forget to record something.
After searching the web and messing around I realized the easiest solution is just to remove the Eclipse JUnit from the build path and have my own junit, hamcrest-core, and hamcrest-library. That seems to solve the problem.
See also:
Although I don't feel like I'm usually recording much useful information, these upgrade posts are some of my most frequently visited. That may just be because they come up in searches.
I downloaded the Eclipse IDE for Java Developers (rather than Standard) since I don't do any plugin development.
See Top 10 Eclipse Kepler Features
I did have one problem on Mac OS X - when you try to run Eclipse you get an error that it is "damaged and can't be opened". I'd run into this before. It's a known issue which, for some reason, the Eclipse developers have closed as "RESOLVED NOT_ECLIPSE" i.e. not their problem. However, as one of the commenters points out, other apps don't have this problem, so it's obviously something Eclipse is doing different if not technically "wrong". It's relatively easy to work around by doing:
xattr -d com.apple.quarantine Eclipse.app/
I imported my plug-ins from my previous Eclipse 4.2 Juno and they all came across without any problems. Even Mercurial still seems to be functional. Here's the list of plug-ins I'm currently using:
- Bytecode Outline
- EclEmma Java Code Coverage
- Checkstyle
- FindBugs
- Infinitest
- MercurialEclipse
- Metrics plugin for Eclipse
I knew I'd run into this before so I went to my blog to see how I'd resolved it. I found Eclipse Hamcrest Runaround and I Give Up but nothing about how I eventually got it to work. It's funny how my blogs have become my external memory and I'm unhappy if I forget to record something.
After searching the web and messing around I realized the easiest solution is just to remove the Eclipse JUnit from the build path and have my own junit, hamcrest-core, and hamcrest-library. That seems to solve the problem.
See also:
Although I don't feel like I'm usually recording much useful information, these upgrade posts are some of my most frequently visited. That may just be because they come up in searches.
Friday, July 12, 2013
Systems Upgrade
I haven't done anything with my home computer systems for several years and it seemed like time for some upgrades and some preventative maintenance.
My Time Capsule is getting older and I knew sooner or later it would fail. It's nice having wireless router and network storage all in one unit. On the other hand, if it fails you're in trouble. Being paranoid about backups, I also decided I should have some kind of redundant storage.
So I replaced the Time Capsule with an ASUS RT-AC66U wireless router and a Synology DS413 4 bay NAS server with three 2tb Western Digital Red drives. The default setup for the Synology NAS will handle a single drive failure without losing any data. It combines the capacity of all the drives so I ended up with about 4tb of storage from the three 2tb drives. I can add a fourth drive at any time (of any size) if I need more space. This was a pretty painless upgrade and improved both wireless and storage speed and space.
I've been mildly tempted by a new iMac, but mostly for more memory and an SSD, which I decided I could get without replacing the whole machine, since it's got a decent i7 and is otherwise fine. (USB 3 and Thunderbolt would be nice, but not essential.) One advantage of the older iMac over the newer models is that it has the SD memory card slot on the side whereas the new "skinny" machines have it on the back where it would be a lot more awkward to use.
The trick was that I need more space (mostly for photos) than I can reasonably get with an SSD, which meant an SSD plus a hard drive. But since my model of iMac doesn't have space for a second drive, that meant removing the optical drive and putting the SSD there (using a "data doubler"). I didn't really mind losing the optical drive - I can't remember the last time I used it. A new iMac wouldn't have had one anyway. And I can always get an external one.
I could have kept the existing 2tb hard drive since it wasn't full, but I decided to set up a "Fusion" drive which combines the hard drive and SSD and automatically migrates data to the appropriate drive. This requires wiping out the hard drive and I was nervous about depending on my backups. So I bought a new 3tb Seagate Barracuda drive and kept the old drive as an extra backup.
I also upgraded the memory from 8gb to 16gb.
Having been in the computer business through the whole progression from 5mb hard drives to 500mb, to gigabytes, and now to terabytes, I sometimes have to think twice about the sizes I'm talking about. Is that backup 1000mb or 1000gb? I know everyone's tired of hearing it from us old timers, but it's still mind boggling that the current drives have grown something like a million times bigger over the course of one working career.
If it had been a PC I probably would have done the upgrade myself (although my hardware days are long past), but to get inside an iMac you have to take the glass off the front which seemed a little scary to me so I got our local Apple dealer to do it.
I had made an OS X installer USB thumb drive beforehand and I had no problems booting from this, setting up the Fusion drive, and installing OS X. Then I used the Migration Assistant to restore from my Time Machine backup. This took roughly 4 hours for about a terabyte of data and restored all my files and applications. It was nice not having to re-install applications.
The only thing I missed was my Parallels Windows VM. For some reason this wasn't included in my Time Machine backup. I had previously excluded it, but I was sure I had started including it. I'm not sure what happened.
I put my old hard drive into an external USB 3 / Firewire 800 enclosure and retrieved the VM with no problems.
The iMac definitely seems faster. If I watch the drive activity (using iStat Menus) it appears the Fusion drive is working properly. The only concern I have is that the new hard disk seems to be running quite hot, even when the machine has been "sleeping". The preferences are set to power down the drive but it maybe that isn't working.
All in all it went quite smoothly and hopefully will keep me happy for a few more years.
My Time Capsule is getting older and I knew sooner or later it would fail. It's nice having wireless router and network storage all in one unit. On the other hand, if it fails you're in trouble. Being paranoid about backups, I also decided I should have some kind of redundant storage.
So I replaced the Time Capsule with an ASUS RT-AC66U wireless router and a Synology DS413 4 bay NAS server with three 2tb Western Digital Red drives. The default setup for the Synology NAS will handle a single drive failure without losing any data. It combines the capacity of all the drives so I ended up with about 4tb of storage from the three 2tb drives. I can add a fourth drive at any time (of any size) if I need more space. This was a pretty painless upgrade and improved both wireless and storage speed and space.
I've been mildly tempted by a new iMac, but mostly for more memory and an SSD, which I decided I could get without replacing the whole machine, since it's got a decent i7 and is otherwise fine. (USB 3 and Thunderbolt would be nice, but not essential.) One advantage of the older iMac over the newer models is that it has the SD memory card slot on the side whereas the new "skinny" machines have it on the back where it would be a lot more awkward to use.
The trick was that I need more space (mostly for photos) than I can reasonably get with an SSD, which meant an SSD plus a hard drive. But since my model of iMac doesn't have space for a second drive, that meant removing the optical drive and putting the SSD there (using a "data doubler"). I didn't really mind losing the optical drive - I can't remember the last time I used it. A new iMac wouldn't have had one anyway. And I can always get an external one.
I could have kept the existing 2tb hard drive since it wasn't full, but I decided to set up a "Fusion" drive which combines the hard drive and SSD and automatically migrates data to the appropriate drive. This requires wiping out the hard drive and I was nervous about depending on my backups. So I bought a new 3tb Seagate Barracuda drive and kept the old drive as an extra backup.
I also upgraded the memory from 8gb to 16gb.
Having been in the computer business through the whole progression from 5mb hard drives to 500mb, to gigabytes, and now to terabytes, I sometimes have to think twice about the sizes I'm talking about. Is that backup 1000mb or 1000gb? I know everyone's tired of hearing it from us old timers, but it's still mind boggling that the current drives have grown something like a million times bigger over the course of one working career.
If it had been a PC I probably would have done the upgrade myself (although my hardware days are long past), but to get inside an iMac you have to take the glass off the front which seemed a little scary to me so I got our local Apple dealer to do it.
I had made an OS X installer USB thumb drive beforehand and I had no problems booting from this, setting up the Fusion drive, and installing OS X. Then I used the Migration Assistant to restore from my Time Machine backup. This took roughly 4 hours for about a terabyte of data and restored all my files and applications. It was nice not having to re-install applications.
The only thing I missed was my Parallels Windows VM. For some reason this wasn't included in my Time Machine backup. I had previously excluded it, but I was sure I had started including it. I'm not sure what happened.
I put my old hard drive into an external USB 3 / Firewire 800 enclosure and retrieved the VM with no problems.
The iMac definitely seems faster. If I watch the drive activity (using iStat Menus) it appears the Fusion drive is working properly. The only concern I have is that the new hard disk seems to be running quite hot, even when the machine has been "sleeping". The preferences are set to power down the drive but it maybe that isn't working.
All in all it went quite smoothly and hopefully will keep me happy for a few more years.
Tuesday, July 02, 2013
Of Mice and Keyboards
At home, on my iMac, I use the Apple full size wired keyboard and magic (touch) mouse. (In addition to not having to worry about batteries, the wired keyboard also has USB ports at either end which are much more accessible than the back of the computer.) I have a magic trackpad too, but I don't find I use it much.
At work, on my Windows PC, I wanted a similar keyboard. For a while I used the same Apple keyboard, but it wasn't ideal because it's missing Windows specific keys.
So I switched to the Logitech Wireless Solar keyboard, which has a similar look and feel but with a Windows layout. I've been pretty happy with it. The solar has worked great and it's nice not to have to change batteries. We've ended up with quite a few of these around the office.
Unlike many people, I actually liked it when Apple switched the default direction of mouse scrolling. Partly, I guess, because it was similar to iPhone and iPad.
But I had a hard time switching between one direction of scrolling at home, and another at work. I also quite liked the Apple magic touch mouse, so I bought the Logitech t620 Touch Mouse which is quite similar.
At first, I thought it was pretty good. But after a while it started to drive me crazy. It was way too sensitive and I would end up scrolling all over the place unintentionally. I stuck with it, thinking I'd get used to it, but if anything it got worse after a driver update. I occasionally have the same problem with the Apple mouse, but nowhere near as bad.
I finally got fed up and shopped for a new mouse. (Hopefully someone else in the office will have better luck with it.) I wanted another Logitech one so I could share the same dongle. I could have gone back to a traditional mouse wheel, but I decided to try the Logitech t400 Zone Touch Mouse.
But things are never simple with computers - I couldn't get the reverse scrolling to work. Logitech's Set Point software has a check box for this, but it had no effect. I did the usual incantations of uninstall, reinstall, reboot, etc. but no luck.
When I started searching the web, I remembered that originally I had used a registry hack to switch scrolling direction. Even better, someone had supplied a Powershell command line to do it, rather than manually editing the registry. (It would be nice if the Windows control panel had a way to change this setting.) But it still didn't work! I ended up uninstalling the Logitech Set Point software. The mouse works fine without it, and now the scrolling works the way I want.
So far I've been pretty happy with this compromise. I occasionally find myself trying to scroll with my finger not on the touch part, but other than that it seems fine. It still has the ability to scroll horizontally, which is occasionally useful, but because the touch sensitive area is limited, I don't find I trigger it accidentally. The smallish size, and rubber sides feel quite comfortable.
Hopefully this combo will keep me happy for a while!
At work, on my Windows PC, I wanted a similar keyboard. For a while I used the same Apple keyboard, but it wasn't ideal because it's missing Windows specific keys.
So I switched to the Logitech Wireless Solar keyboard, which has a similar look and feel but with a Windows layout. I've been pretty happy with it. The solar has worked great and it's nice not to have to change batteries. We've ended up with quite a few of these around the office.
Unlike many people, I actually liked it when Apple switched the default direction of mouse scrolling. Partly, I guess, because it was similar to iPhone and iPad.
But I had a hard time switching between one direction of scrolling at home, and another at work. I also quite liked the Apple magic touch mouse, so I bought the Logitech t620 Touch Mouse which is quite similar.
At first, I thought it was pretty good. But after a while it started to drive me crazy. It was way too sensitive and I would end up scrolling all over the place unintentionally. I stuck with it, thinking I'd get used to it, but if anything it got worse after a driver update. I occasionally have the same problem with the Apple mouse, but nowhere near as bad.
I finally got fed up and shopped for a new mouse. (Hopefully someone else in the office will have better luck with it.) I wanted another Logitech one so I could share the same dongle. I could have gone back to a traditional mouse wheel, but I decided to try the Logitech t400 Zone Touch Mouse.
But things are never simple with computers - I couldn't get the reverse scrolling to work. Logitech's Set Point software has a check box for this, but it had no effect. I did the usual incantations of uninstall, reinstall, reboot, etc. but no luck.
When I started searching the web, I remembered that originally I had used a registry hack to switch scrolling direction. Even better, someone had supplied a Powershell command line to do it, rather than manually editing the registry. (It would be nice if the Windows control panel had a way to change this setting.) But it still didn't work! I ended up uninstalling the Logitech Set Point software. The mouse works fine without it, and now the scrolling works the way I want.
So far I've been pretty happy with this compromise. I occasionally find myself trying to scroll with my finger not on the touch part, but other than that it seems fine. It still has the ability to scroll horizontally, which is occasionally useful, but because the touch sensitive area is limited, I don't find I trigger it accidentally. The smallish size, and rubber sides feel quite comfortable.
Hopefully this combo will keep me happy for a while!
Thursday, June 27, 2013
Jot! iPad App
I have a bunch of iPad drawing apps (which is a little odd because I don't draw). The one I like for doing quick diagrams is Jot! Whiteboard by Tabula Rasa. There are both paid and free versions. (I also use Google Docs drawing program for more "formal" diagrams.)
What I like best is that you can move/copy/delete things (or groups of things) after you've drawn them. That's hard or impossible with a lot of "paint" type programs.
You can also easily add text boxes, which is handy for the kind of diagrams I draw.
Here's a couple of examples (the meaning isn't important)
Sunday, May 05, 2013
Optimizing Tr
Suneido has a string.Tr function, similar to the Unix tr command. Recently, I was looking at the stdlib Base64 code. Decode was using string.Tr to strip any newlines. However, there may not be any newlines. Which made me wonder what Tr did in this case - did it still make a copy of the string? Sure enough, it did.
My first reaction was to add a guard:
if s.Has?('\n')
s = s.Tr('\n')
Then I started to wonder where else we should be doing this. But that was ugly. It made more sense to build it into Tr.
But implementing it as above would mean doing an extra scan of the source string. Since the code was already scanning, it would be more efficient to just defer copying until found somewhere where you needed to make a change.
I implemented this in the C++ code. It complicated it a little, but not too bad.
Then I tried to implement the same thing in the Java code. Not being able to "cheat" with macros like in the C++ version meant I had to create an instance to share the variables. This defeated part of the purpose (to avoid allocation) and seemed ugly.
Instead I decided to do an initial scan to find the first character to be changed, and then either return or continue from that point. This still avoided redundant scanning, without complicating the main part of the code.
In the process, I discovered there were a few other cases I could short circuit - if the source string is empty, or if the "from" character set is empty then you can just return the source string unchanged. Also, if there are no ranges in the from set or the to set, then you don't need to expand the sets, avoiding more allocation and copying, albeit only on the sets. I also simplified the code a little. (It was a good thing I had tests since I "simplified" a little too aggressively a few times!) See the code.
I also decided to add a cache for expanding sets with ranges. I'm not sure that's justified, but it's similar to what I do with regular expressions. The regular expression code had been using a home-brew LruCache, but I switched to Google Guava's caching facilities. That also allowed time based expiry so I could make the cache bigger without wasting space if it wasn't needed.
Then I went back and revised the C++ tr code using the same approach as the Java code. For some reason I had originally use std::vector for the sets. I switched to making them gcstring's to avoid making a new set if there are no ranges to expand. I also added a cache, using the existing CacheMap that was being used for regular expressions.
Although it sometimes bugs me to have to update two implementations of Suneido, it does have its benefits. It means if I want to use the same approach I can't take too much advantage of specific language features (e.g. macros). This might mean I can't use some fancy feature, but in many cases that's not the wisest anyway. By the time I've written the code in two different languages, I think the end result is usually better than if I'd just written it once.
Again, I'm guilty of optimizing without real proof of whether it's justified. Tr is fairly heavily used in some areas, and the changes will probably speed up those areas. But whether that makes much difference in the bigger picture is questionable.
See also previous posts: String Building Internals and An Amusing Bug
My first reaction was to add a guard:
if s.Has?('\n')
s = s.Tr('\n')
Then I started to wonder where else we should be doing this. But that was ugly. It made more sense to build it into Tr.
But implementing it as above would mean doing an extra scan of the source string. Since the code was already scanning, it would be more efficient to just defer copying until found somewhere where you needed to make a change.
I implemented this in the C++ code. It complicated it a little, but not too bad.
Then I tried to implement the same thing in the Java code. Not being able to "cheat" with macros like in the C++ version meant I had to create an instance to share the variables. This defeated part of the purpose (to avoid allocation) and seemed ugly.
Instead I decided to do an initial scan to find the first character to be changed, and then either return or continue from that point. This still avoided redundant scanning, without complicating the main part of the code.
In the process, I discovered there were a few other cases I could short circuit - if the source string is empty, or if the "from" character set is empty then you can just return the source string unchanged. Also, if there are no ranges in the from set or the to set, then you don't need to expand the sets, avoiding more allocation and copying, albeit only on the sets. I also simplified the code a little. (It was a good thing I had tests since I "simplified" a little too aggressively a few times!) See the code.
I also decided to add a cache for expanding sets with ranges. I'm not sure that's justified, but it's similar to what I do with regular expressions. The regular expression code had been using a home-brew LruCache, but I switched to Google Guava's caching facilities. That also allowed time based expiry so I could make the cache bigger without wasting space if it wasn't needed.
Then I went back and revised the C++ tr code using the same approach as the Java code. For some reason I had originally use std::vector for the sets. I switched to making them gcstring's to avoid making a new set if there are no ranges to expand. I also added a cache, using the existing CacheMap that was being used for regular expressions.
Although it sometimes bugs me to have to update two implementations of Suneido, it does have its benefits. It means if I want to use the same approach I can't take too much advantage of specific language features (e.g. macros). This might mean I can't use some fancy feature, but in many cases that's not the wisest anyway. By the time I've written the code in two different languages, I think the end result is usually better than if I'd just written it once.
Again, I'm guilty of optimizing without real proof of whether it's justified. Tr is fairly heavily used in some areas, and the changes will probably speed up those areas. But whether that makes much difference in the bigger picture is questionable.
An amusing historical aside - I originally ported this code from Software Tools by Kernighan and Plauger, which uses Ratfor (rational Fortran). The code's origins are still visible in some of the names and structure. This book was an inspiration in my early programming days. I still have my original copy, although it's falling apart. It's pretty cool that it's still in print 37 years later.
See also previous posts: String Building Internals and An Amusing Bug
Friday, May 03, 2013
An Amusing Bug
I recently realized that the block form of Suneido's string.Replace was a more efficient way to "map" over strings.
As I mentioned in my recent post on String Building Internals, I also discovered cSuneido's string.Replace didn't handle nul's. I fixed this problem and we sent out the new suneido.exe to our beta customers.
And we started to get support calls that PDF attachments weren't being sent properly. Sure enough it was the new version of Base64.Encode that used string.Replace.
But I had tests, and we had also tested manually and it worked fine. We got one of the problem files from a customer and sure enough it failed.
Digging into it, I could see that it was only encoding part of the file. That seemed a bit like the previous nul problem, which led me in the wrong direction for a while.
More testing revealed it was encoding the first 39996 characters of the file. That seemed like an odd number. My first thought was that it was in the general vicinity of SHORT_MAX or 32767. When I first wrote the C++ version of Suneido I was still trying to use short int's when possible. This has led to a number of issues since SHORT_MAX isn't very big in modern terms. But the relevant code wasn't using any short int's.
But I noticed a magic number of 9999 in the code. Base64 encode outputs groups of 4 characters. 4 x 9999 - 39996. Aha!
string.Replace takes an optional argument for how many replacements to do. Usually, you either want 1 or all. When not specified, the count was defaulting to 9999. For "normal" usage, that's plenty. But when using replace to map large strings, it obviously isn't.
I changed it to INT_MAX and that fixed the problem. Out of curiosity I went and checked the jSuneido code. (It did not have the same issue.) Frustratingly, it already had INTMAX. I don't think I foresaw this issue when I ported the code, it probably just bugged me to have a magic number.
I'm not sure why this strikes me as amusing. It just seems funny that the "bug" was not something obscure, just a badly chosen limit, that no doubt seemed entirely reasonable at the time.
As I mentioned in my recent post on String Building Internals, I also discovered cSuneido's string.Replace didn't handle nul's. I fixed this problem and we sent out the new suneido.exe to our beta customers.
And we started to get support calls that PDF attachments weren't being sent properly. Sure enough it was the new version of Base64.Encode that used string.Replace.
But I had tests, and we had also tested manually and it worked fine. We got one of the problem files from a customer and sure enough it failed.
Digging into it, I could see that it was only encoding part of the file. That seemed a bit like the previous nul problem, which led me in the wrong direction for a while.
More testing revealed it was encoding the first 39996 characters of the file. That seemed like an odd number. My first thought was that it was in the general vicinity of SHORT_MAX or 32767. When I first wrote the C++ version of Suneido I was still trying to use short int's when possible. This has led to a number of issues since SHORT_MAX isn't very big in modern terms. But the relevant code wasn't using any short int's.
But I noticed a magic number of 9999 in the code. Base64 encode outputs groups of 4 characters. 4 x 9999 - 39996. Aha!
string.Replace takes an optional argument for how many replacements to do. Usually, you either want 1 or all. When not specified, the count was defaulting to 9999. For "normal" usage, that's plenty. But when using replace to map large strings, it obviously isn't.
I changed it to INT_MAX and that fixed the problem. Out of curiosity I went and checked the jSuneido code. (It did not have the same issue.) Frustratingly, it already had INTMAX. I don't think I foresaw this issue when I ported the code, it probably just bugged me to have a magic number.
I'm not sure why this strikes me as amusing. It just seems funny that the "bug" was not something obscure, just a badly chosen limit, that no doubt seemed entirely reasonable at the time.
Friday, April 26, 2013
No auto-update for 64 bit Java on Windows
It's hard to believe after all these years, and all the security issues, that there's still no auto-update for 64 bit Java on Windows.
I have known that Java on my Windows machine wasn't updating properly, but I just assumed it was because I had multiple copies and versions etc. But it's a hassle having to remember to manually download and install updates so finally I decided to try to fix it, only to discover there is no fix.
This was entered as a bug in 2006, and it's currently scheduled to be fixed in Java 8 (!)
Sometimes you really have to wonder about how these things get prioritized. Granted, our customers say the same thing about us, but considering what a security issue this is, I would have thought it would get addressed. Back in 2006 I can see thinking "no one" was running 64 bit, but nowadays that's not a good assumption.
I have known that Java on my Windows machine wasn't updating properly, but I just assumed it was because I had multiple copies and versions etc. But it's a hassle having to remember to manually download and install updates so finally I decided to try to fix it, only to discover there is no fix.
This was entered as a bug in 2006, and it's currently scheduled to be fixed in Java 8 (!)
Sometimes you really have to wonder about how these things get prioritized. Granted, our customers say the same thing about us, but considering what a security issue this is, I would have thought it would get addressed. Back in 2006 I can see thinking "no one" was running 64 bit, but nowadays that's not a good assumption.
Sunday, March 31, 2013
String Building Internals
Recently we ran into a problem where a service using cSuneido was running out of memory. I confidently switched the service to use jSuneido, and got stack overflows. So much for my confidence!
We tracked it down to calling Base64.Encode on big strings (e.g. 1mb) (This came from sending email with large attachments.)
Base64.Encode was building the result string a character at a time by concatenating. In many languages this would be a bad idea for big strings. (e.g. In Java you would use StringBuilder instead.) But Suneido does not require you to switch to a different approach for big strings, the implementation is intended to handle it automatically.
Up till now, Suneido has handled this by deferring concatenation of larger strings. If the length of the result is greater than a threshold (64 bytes in cSuneido, 256 bytes in jSuneido) then the result is a "Concat" that simply points to the two pieces. This defers allocating a large result string and copying into it until some operation requires the value of the result. Basically, Suneido makes a linked list of the pieces.
For example, if you built a 1000 character string by adding one character at a time, Suneido would only allocate and copy the result once, not 1000 times. (It still has to create 1000 Concat instances but these are small.)
In practice, this has worked well for many years.
I thought I had implemented the same string handling in jSuneido, but actually I'd taken a shortcut, and that was what caused the stack overflows.
The obvious way to convert the tree of Concat's to a regular string is a recursive approach, first copying the left side of each concat, and then copying the right side, each of which could be either a simple string or another Concat. The problem is that the tree is not balanced. The most common case is appending on the end, which leads to a tree that looks like:
But with many more levels. If your tree has a million levels, then recursing on each side (in particular the left side) will cause stack overflow. In cSuneido I had carefully iterated on the left side and recursed on the right. (Basically doing manual tail-call elimination.) But when I ported the code to Java I had unknowingly simplified to recurse on both sides.
Once I knew what the problem was, it was easy enough to fix, although a little trickier due to using StringBuilder.
Thinking about the problem I realized in this case, there was a better way to write Base64.Encode. I could use the form of string.Replace that takes a function to apply to each match, and simply replace every three characters with the corresponding four characters in Base64. Since string.Replace uses a buffer that doubles in size as required (via StringBuilder in jSuneido), this is quite efficient. (In the process I discovered string.Replace in cSuneido choked on nul's due to remnants of nul terminated string handling. Easy enough to fix.)
But that left the problem of why cSuneido ran out of memory. I soon realized that the whole Concat approach was hugely wasteful of memory, especially when concatenating very small strings. Each added piece requires one memory block for the string, and another for the Concat. Most heaps have a minimum size for a heap block e.g. 32 bytes. So in the case of appending a single character, it was using 64 bytes. So building a 1 mb string a character at a time would use something like 64 mb. Ouch!
In theory, cSuneido could still handle that, but because it uses conservative garbage collection, spurious references into the tree could end up with large amounts of garbage accumulating.
Maybe my whole Concat scheme wasn't ideal. To be fair, it has worked well up till now, and still works well for moderate string sizes. It just wasn't designed for working with strings that are millions of characters long.
I dreamed up various complicated alternative approaches but I wasn't excited about implementing them. It seemed like there had to be a simpler solution.
Finally, I realized that if you only worried about the most common case of appending on the end, you could use a doubling buffer approach (like StringBuilder). The result of a larger concatenation would be a StrBuf (instead of a concat). Because strings are immutable, when you appended a string onto a StrBuf, it would add to the existing buffer (sharing it) if there was room, or else allocate a new buffer (twice as big).
Theoretically, this is slightly slower than the Concat approach because it has to copy the right hand string. But in the common case of repeatedly appending small strings, it's not bad.
This approach doesn't appear to be noticeably faster or slower, either in large scale (our entire application test suite) or in micro-benchmarks (creating a large string by repeatedly appending one character). But, as expected, it does appear to use significantly less memory (roughly half as much heap space).
One disadvantage of this approach is that it doesn't help with building strings in reverse order. (i.e. by repeatedly adding to the beginning of a string) That's unfortunate, but in practice I don't think it's too common.
A nice side benefit is that this approach is actually simpler to implement than the old approach. There is no tree, no recursion, and no need for manual tail-call elimination.
Unfortunately, in Java (where I've been working on this) you still have to convert the StringBuilder to a string when required, which makes yet another copy. In cSuneido I should be able to simply create a string wrapping the existing buffer.
Thinking about it some more, I wasn't happy with the extra copying - a lot more than the previous approach. I realized I could combine the two approaches - use a doubling array, but storing references to the pieces rather than the actual characters. This eliminates the copying while still reducing memory usage.
Note: Concats and Pieces cannot be combined because Pieces is shared - appending to a Concats will result in a new Concats that (usually) shares the same Pieces. Concats is effectively immutable, Pieces is mutable and synchronized. (It would be nice if Java let you combine a class and an array to make variable sized objects, but it doesn't.)
However, in the worst case of building a large string one character at a time, this would still end up requiring a huge array, plus all the one character strings. But given the array of strings, it's relatively easy to merge small strings and "compact" the array, reducing the size of the array and the overhead of small strings.
I didn't want to compact too often as this would add more processing overhead. I realized that the obvious time to compact was when the array was full. If there were contiguous small strings that could be merged, then you could avoid growing the array.
I was disappointed to discover that this new approach was about 1/2 as fast as the old approach (when building a 1mb string one character at a time). On the positive side, it resulted in a heap about 1/4 the size (~200mb versus ~800mb). It was a classic space/time tradeoff - I could make it faster by doing less merging, but then it used more memory.
Interestingly, the test suite (a more typical workload) ran about 10% faster. I can't explain that, but I'm not complaining!
It still nagged me that the new approach was slower building huge strings. I tried running it longer (more repetitions) to see if garbage collection would have more of an effect, but that didn't make much difference. Nor did building a 100kb string instead of 1mb (and again doing more repetitions). Then I thought that since my new approach was specifically aimed at building huge strings, maybe I should try increasing the size. Sure enough, building a 10mb string was 4 times faster with the new approach. Finally, the results I was hoping for! Of course, in practice, it's rare to build strings that big.
I initially wrote special code for adding to the beginning of a concatenation and for joining two concatenations. But then I added some instrumentation and discovered that (at least in our test suite) these cases are rare enough (~1%) that they're not worth optimizing. So I removed the extra code - simpler is better.
There are a number of "thresholds" in the code. I played around a bit with different settings but performance was not particularly sensitive to their values.
I probably spent more time on this than was really justified. But it was an interesting problem!
As usual, the code is in Mercurial on SourceForge. Or go directly to this version of Concats.
We tracked it down to calling Base64.Encode on big strings (e.g. 1mb) (This came from sending email with large attachments.)
Base64.Encode was building the result string a character at a time by concatenating. In many languages this would be a bad idea for big strings. (e.g. In Java you would use StringBuilder instead.) But Suneido does not require you to switch to a different approach for big strings, the implementation is intended to handle it automatically.
Up till now, Suneido has handled this by deferring concatenation of larger strings. If the length of the result is greater than a threshold (64 bytes in cSuneido, 256 bytes in jSuneido) then the result is a "Concat" that simply points to the two pieces. This defers allocating a large result string and copying into it until some operation requires the value of the result. Basically, Suneido makes a linked list of the pieces.
For example, if you built a 1000 character string by adding one character at a time, Suneido would only allocate and copy the result once, not 1000 times. (It still has to create 1000 Concat instances but these are small.)
In practice, this has worked well for many years.
I thought I had implemented the same string handling in jSuneido, but actually I'd taken a shortcut, and that was what caused the stack overflows.
The obvious way to convert the tree of Concat's to a regular string is a recursive approach, first copying the left side of each concat, and then copying the right side, each of which could be either a simple string or another Concat. The problem is that the tree is not balanced. The most common case is appending on the end, which leads to a tree that looks like:
But with many more levels. If your tree has a million levels, then recursing on each side (in particular the left side) will cause stack overflow. In cSuneido I had carefully iterated on the left side and recursed on the right. (Basically doing manual tail-call elimination.) But when I ported the code to Java I had unknowingly simplified to recurse on both sides.
Once I knew what the problem was, it was easy enough to fix, although a little trickier due to using StringBuilder.
Thinking about the problem I realized in this case, there was a better way to write Base64.Encode. I could use the form of string.Replace that takes a function to apply to each match, and simply replace every three characters with the corresponding four characters in Base64. Since string.Replace uses a buffer that doubles in size as required (via StringBuilder in jSuneido), this is quite efficient. (In the process I discovered string.Replace in cSuneido choked on nul's due to remnants of nul terminated string handling. Easy enough to fix.)
But that left the problem of why cSuneido ran out of memory. I soon realized that the whole Concat approach was hugely wasteful of memory, especially when concatenating very small strings. Each added piece requires one memory block for the string, and another for the Concat. Most heaps have a minimum size for a heap block e.g. 32 bytes. So in the case of appending a single character, it was using 64 bytes. So building a 1 mb string a character at a time would use something like 64 mb. Ouch!
In theory, cSuneido could still handle that, but because it uses conservative garbage collection, spurious references into the tree could end up with large amounts of garbage accumulating.
Maybe my whole Concat scheme wasn't ideal. To be fair, it has worked well up till now, and still works well for moderate string sizes. It just wasn't designed for working with strings that are millions of characters long.
I dreamed up various complicated alternative approaches but I wasn't excited about implementing them. It seemed like there had to be a simpler solution.
Finally, I realized that if you only worried about the most common case of appending on the end, you could use a doubling buffer approach (like StringBuilder). The result of a larger concatenation would be a StrBuf (instead of a concat). Because strings are immutable, when you appended a string onto a StrBuf, it would add to the existing buffer (sharing it) if there was room, or else allocate a new buffer (twice as big).
Theoretically, this is slightly slower than the Concat approach because it has to copy the right hand string. But in the common case of repeatedly appending small strings, it's not bad.
This approach doesn't appear to be noticeably faster or slower, either in large scale (our entire application test suite) or in micro-benchmarks (creating a large string by repeatedly appending one character). But, as expected, it does appear to use significantly less memory (roughly half as much heap space).
One disadvantage of this approach is that it doesn't help with building strings in reverse order. (i.e. by repeatedly adding to the beginning of a string) That's unfortunate, but in practice I don't think it's too common.
A nice side benefit is that this approach is actually simpler to implement than the old approach. There is no tree, no recursion, and no need for manual tail-call elimination.
Unfortunately, in Java (where I've been working on this) you still have to convert the StringBuilder to a string when required, which makes yet another copy. In cSuneido I should be able to simply create a string wrapping the existing buffer.
Thinking about it some more, I wasn't happy with the extra copying - a lot more than the previous approach. I realized I could combine the two approaches - use a doubling array, but storing references to the pieces rather than the actual characters. This eliminates the copying while still reducing memory usage.
Note: Concats and Pieces cannot be combined because Pieces is shared - appending to a Concats will result in a new Concats that (usually) shares the same Pieces. Concats is effectively immutable, Pieces is mutable and synchronized. (It would be nice if Java let you combine a class and an array to make variable sized objects, but it doesn't.)
However, in the worst case of building a large string one character at a time, this would still end up requiring a huge array, plus all the one character strings. But given the array of strings, it's relatively easy to merge small strings and "compact" the array, reducing the size of the array and the overhead of small strings.
I didn't want to compact too often as this would add more processing overhead. I realized that the obvious time to compact was when the array was full. If there were contiguous small strings that could be merged, then you could avoid growing the array.
I was disappointed to discover that this new approach was about 1/2 as fast as the old approach (when building a 1mb string one character at a time). On the positive side, it resulted in a heap about 1/4 the size (~200mb versus ~800mb). It was a classic space/time tradeoff - I could make it faster by doing less merging, but then it used more memory.
Interestingly, the test suite (a more typical workload) ran about 10% faster. I can't explain that, but I'm not complaining!
It still nagged me that the new approach was slower building huge strings. I tried running it longer (more repetitions) to see if garbage collection would have more of an effect, but that didn't make much difference. Nor did building a 100kb string instead of 1mb (and again doing more repetitions). Then I thought that since my new approach was specifically aimed at building huge strings, maybe I should try increasing the size. Sure enough, building a 10mb string was 4 times faster with the new approach. Finally, the results I was hoping for! Of course, in practice, it's rare to build strings that big.
I initially wrote special code for adding to the beginning of a concatenation and for joining two concatenations. But then I added some instrumentation and discovered that (at least in our test suite) these cases are rare enough (~1%) that they're not worth optimizing. So I removed the extra code - simpler is better.
There are a number of "thresholds" in the code. I played around a bit with different settings but performance was not particularly sensitive to their values.
I probably spent more time on this than was really justified. But it was an interesting problem!
As usual, the code is in Mercurial on SourceForge. Or go directly to this version of Concats.
Wednesday, March 27, 2013
Ilford Marketing Abuse
Can you spot the "unsubscribe" link on this email fragment:
It's at the bottom in tiny dark gray text on a black background. Could they have made it any harder to find?
I can just imagine the conversation: "Ok, so we have to have an unsubscribe link. How about if we make it really hard to see."
The reason they got my email address was that it was required so I could download color profiles for their high end inkjet paper. It seems to me, if you want people to buy your paper, then you want to make it as easy as possible for them to get good results. Not make them jump through hoops and get spammed, just for the "privilege" of buying their products.
They didn't even have a way to opt out of emails when you register, as most registration forms do.
Needless to say, it doesn't make me inclined to buy any more of Ilford's products.
It's at the bottom in tiny dark gray text on a black background. Could they have made it any harder to find?
I can just imagine the conversation: "Ok, so we have to have an unsubscribe link. How about if we make it really hard to see."
The reason they got my email address was that it was required so I could download color profiles for their high end inkjet paper. It seems to me, if you want people to buy your paper, then you want to make it as easy as possible for them to get good results. Not make them jump through hoops and get spammed, just for the "privilege" of buying their products.
They didn't even have a way to opt out of emails when you register, as most registration forms do.
Needless to say, it doesn't make me inclined to buy any more of Ilford's products.
Tuesday, March 12, 2013
Geek Overthink
We rented a car when we were in Virginia and we managed to get a Toyota Prius. The radio was playing and I hate radio as much as I hate TV! I had my music on my iPhone but I didn't have a car adapter. But hey, the car supports Bluetooth audio. I poked around in the menus but I couldn't figure out how to connect. I pulled out the owners manual but there was no mention of audio? I found there was a whole separate manual for the audio system!
It was a little hard to follow the manual because it described multiple versions of the audio system, none of which seemed to match our vehicle. I figured it had to be possible since a bunch of iPhones were listed on the Bluetooth menu. It turned out that might have been part of the problem since the manual said you could link up to five devices, and there were already five. No problem, I'll just delete one. Except the options to delete that were described in the manual didn't appear on our car. I wonder if rental cars are locked down somehow so you can't mess with certain settings?
I gave up at this point. But then I remembered seeing something about USB in the manual, and I did have a USB cable (from the charger). Sure enough there was a place to plug in a USB cable and when I did my iPhone showed up and played perfectly.
The moral of the story is - don't get so wrapped up in the wonderful complexities of one approach that you forget that there might be other (simpler) solutions.
It was a little hard to follow the manual because it described multiple versions of the audio system, none of which seemed to match our vehicle. I figured it had to be possible since a bunch of iPhones were listed on the Bluetooth menu. It turned out that might have been part of the problem since the manual said you could link up to five devices, and there were already five. No problem, I'll just delete one. Except the options to delete that were described in the manual didn't appear on our car. I wonder if rental cars are locked down somehow so you can't mess with certain settings?
I gave up at this point. But then I remembered seeing something about USB in the manual, and I did have a USB cable (from the charger). Sure enough there was a place to plug in a USB cable and when I did my iPhone showed up and played perfectly.
The moral of the story is - don't get so wrapped up in the wonderful complexities of one approach that you forget that there might be other (simpler) solutions.
Friday, February 22, 2013
Service is Not a Joke
Some companies seem to have the idea that humour equates to great customer service. (e.g. WestJet and Amtrak)
I disagree.
There's nothing wrong with lightening things up and getting people smiling, but you also need to fix the underlying system. Telling someone a joke while they are on hold for an hour doesn't change the fact that they were on hold for a hour.
And not everyone makes a good comedian. Management can dictate "you shall tell jokes" but that doesn't mean it's always going to be funny. Especially the fifth time you hear it.
Unless you also fix the real issues you're just putting lipstick on a pig.
Of course, that leaves the question - if you can't fix the pig are you better off at least dressing it up?
If this was individuals trying to make the best of a situation then ok. But in most cases it seems to be a top down directive. In other words it's coming from exactly the people that could actually fix the system if they really wanted to.
I disagree.
There's nothing wrong with lightening things up and getting people smiling, but you also need to fix the underlying system. Telling someone a joke while they are on hold for an hour doesn't change the fact that they were on hold for a hour.
And not everyone makes a good comedian. Management can dictate "you shall tell jokes" but that doesn't mean it's always going to be funny. Especially the fifth time you hear it.
Unless you also fix the real issues you're just putting lipstick on a pig.
Of course, that leaves the question - if you can't fix the pig are you better off at least dressing it up?
If this was individuals trying to make the best of a situation then ok. But in most cases it seems to be a top down directive. In other words it's coming from exactly the people that could actually fix the system if they really wanted to.
Subscribe to:
Posts (Atom)












.jpg)


