Wednesday, May 20, 2009

Monoculture == good?

Being a performance-minded person, I wanted to compare the speed of my dependency injection / inversion of control container, dconstructor, to the speed of other popular IoC containers. Since I am most familiar with C#, my first thought was to try Castle Windsor.

My day job involves programming in C# on Windows, but at home, I use Linux exclusively. So this means using Mono. Perhaps unsurprisingly, Castle Windsor does not work on Mono. It has dependencies on C#3.5 libraries that are only partially implemented in Mono.

I began to consider, at this point, the relative situation between Mono and D. In the past, I have gotten internal compiler errors in both, and much more frequently than I would expect with a production-quality compiler. In Mono, these usually related to libraries -- perhaps with Castle, sometimes; never with any library I thought might typically be developed with Mono. In D, it was usually my code, though I'll grant that my D code involves fewer libraries in many cases.

The primary issue is that C# libraries can target two platforms, one of which is significantly more stable and has a more extensive standard library and much larger installation base. There was a time, I am told, when C# libraries would contain workarounds for differences and errors present in Mono and not in .NET, but this attitude is no longer prevalent.

In D, there is only DMDFE. All libraries target it.

Another helpful element is that the primary way to distribute a library with D is by source. It's quite doable to fix up problems in a library that come about as a result of compiler and stdlib differences. There's not much you can do with an opaque DLL. This makes problems seem more insurmountable than they might be.

All told, though, I think that D's relative monoculture is a good thing. There are two or three teams independently working on the same open source codebase, so there's less worry about D suddenly going away if Walter ceases to distribute it. But I can assume that any maintained library in D will continue to work with up-to-date compilers.

That might change when Dil comes around, but I still look forward to Dil. I've seen DMDFE, after all.

Tuesday, May 19, 2009

Lifecycle support in dconstructor

Dconstructor now has lifecycle support.

What does this mean? It means your objects will die a fiery death.

Previously, dconstructor supported two lifecycles: Instance and Singleton. Instance indicated that an object was entirely transient and had to be rebuilt each time it was required. Singleton indicated that an object was permanent and could be safely reused from the time it was first created onward to the end of time.

This has been generalized. Each object builder is now associated with a lifecycle. When it builds, it gets a lifecycle ID from the lifecycle. The next time it builds, it asks the lifecycle if its ID is still valid. If the ID is valid, then the builder returns the same instance; otherwise, it creates a new instance.

When registering a type, you can specify a lifecycle. You can also set a default lifecycle, and create LifecycleProviders to determine policy for particular types.

This is a part of dconstructor.build2, of course.

Dconstructor updates

Just a quick note about dconstructor: I've added dconstructor.build2, which is nearly a drop-in replacement for dconstructor.build. (Interceptors require modification.)

This reduces the executable bloat significantly -- in one mid-size example, dconstructor previously was responsible for a 75% increase in executable size (4.8MB to 8.3MB) and is now only responsible for about 100KB. The new version is less than 5% slower.

This also greatly decreases compilation times. examples/speed.d compiles in 3.4s rather than 14s.

The update is highly recommended, unless you need templated interceptors. Additionally, default_builder will soon be changed to use build2, since default_builder does not allow you to inject interceptors.

Thursday, May 14, 2009

The Visitor Pattern and Extensibility

I've been dealing with Dil lately. It's a compiler project for the D programming language, written in D. It makes use of the visitor pattern to provide semantic analysis. (The visitor pattern is a way of achieving dynamic dispatch via strongly typed overloads. It was a clever hack when it was first created.)

One issue with the visitor pattern is that it requires a lot of boilerplate code. If you want to have multiple semantic passes, you need to use an interface, and that interface requires a method for each type. If your visitor only cares about ten types and the interface supports fifty, you have a problem.

This doesn't much matter for dil -- very few types will not matter for any given visitor.

But let's look at a different problem. Let's say we want to log all visitor actions. Where do we do this? There are two choices: every single visitor method, or every single visited class. Similarly if we want to filter visited items, or set a breakpoint, or anything interesting like that.

I'm working on semantic analysis in Dil right now. For cleanness, I would like to split semantic analysis into possibly many phases. However, I would also like to combine semantic passes when possible for efficiency. To do this, I need to create a visitor that will coordinate between several visitors. This is an unreasonably large task with the current Visitor pattern implementation.

What else could I use, though?

In the past, I've used this concept:


void delegate(Node)[ClassInfo] handlers;
void visit(Node node)
{
if (auto ptr = node.classinfo in handlers)
{
auto dg = *ptr;
dg(node);
}
}


This design is sufficiently minimal that it's easy to do a fair bit with it:

  • Log each visited node

  • Use multiple handlers per node type

  • Use the same handler for multiple node types

  • Filter or preprocess nodes based on some criterion available in the base class

  • Ignore various node types by not writing any code for them



There is one problem with it, though: you have to do a lot of casting. In C#, you can use reflection to invoke the methods without caring about their types. In D, you can write a little wrapper:


class Invoker(T : Node)
{
void delegate(T) dg;
void invoke(Node node)
{
debug
{
// safety: cast and check
auto theNode = cast(T)node;
assert (theNode !is null, "expected: " ~ T.stringof ~
"but was " ~ node.classinfo.name);
}
else
{
// efficiency: force cast and assume
auto theNode = *cast(T*)&node;
}
return dg(theNode);
}
}
handlers[type] = &(new Invoker(dg)).invoke;


A slightly simpler version is possible with D2's closures.

There is one problem with our solution: it uses associative arrays, which could be slow.

If you know what types you support in advance, you can map each to an index to get a very fast lookup. This requires a fast means of getting this index, however. I'm generally inclined to just use the associative array; it's going to be small in any case, so it will not incur a significant penalty in most cases.

Wednesday, May 13, 2009

Metacity hacks

I've been using Enlightenment 17 as a window manager, but decided to go back to GNOME/Metacity since they're more polished. Overall a good move.

There are three issues I have with GNOME/Metacity. The first, and least solvable, is resources: GNOME is not lightweight. But I have sufficient RAM to ignore that issue for the most part.

The second problem is switching workspaces. In Enlightenment, going left from the leftmost workspace yields the rightmost workspace. Similarly for the rest of the directions. This is not the case for Metacity; you cannot go left from the leftmost workspace, or right from the rightmost.

Lastly, window placement. ANYTHING would be better than Metacity's policy, which is tiling all new windows from the top left. Open up four terminals on an empty workspace, you get what looks like one terminal with four titlebars, plus a lot of empty screen.

workspace_switch.py has sane workspace switching behavior. You can map Ctrl+Alt+Right and Ctr+Alt+Left to custom commands using gconf-editor (/apps/metacity/global_keybindings and /apps/metacity/keybinding_commands). Check workspace_switch.py --help for details.

I had to modify metacity to get reasonable behavior for its window locations -- metacity is renouned for its lack of configuration options.

In the metacity sources, go to src/core/place.c. Nuke all existing functions and add the following:


void
meta_window_place (MetaWindow *window,
MetaFrameGeometry *fgeom,
int x,
int y,
int *new_x,
int *new_y)
{
MetaRectangle work_area;
const MetaXineramaScreenInfo* xinerama =
meta_screen_get_current_xinerama (
window->screen);
meta_window_get_work_area_for_xinerama (window,
xinerama->number, &work_area);
int xrange = work_area.width - x;
int yrange = work_area.height - y;
*new_x = (rand() % xrange);
*new_y = (rand() % yrange);
}
As you can see, this positions the new window at some random location, ensuring that no portion of the window is off the screen (unless perhaps the window is larger than the screen).

Perhaps I'll work on this -- certain things that would ideally be centered or center-parent are positioned randomly, such as the GNOME run dialog or modal dialogs. But for now, it's a huge improvement.