Haskell Snap Framework templating 3000x faster with new release
snapframework.com
snapframework.com
But the numbers before the improvement. Ouch. Those were bad.
In my experience micro-optimizing code, the majority of time is spent waiting for memory (bandwidth waits or even worse: latency costs).
Caches play a role as well, of course. L1 is around 1ns sometimes even less. if you hit L2 it is in the vicinity of 5-10ns. It easily translates to some 30-40 insns on modern hardware.
It also tells you that hunting for faster execution by compressing instructions down is not going to cut you that much extra speed nowadays. The key to fast programs is data representation. Good data representation.
> However, we realized that a lot of the transformations could be done at load time and preprocessed to an intermediate representation.
Lazy computation has its disadvantages. Still, impressive gain.
After reading the article I don't see anything about lazy evaluation. What am I missing?
It's similar to moving something from runtime to compiletime (although those distinctions don't quite apply).
Lazy evaluation: waiting until a computation needs to be done to perform it.
Problem here: it was inefficient to do a particular computation at the very moment before it was needed.
So, how is this not a lazy evaluation problem?
Unless I am mistaken, they didn't change the template language evaluation strategy from call-by-name to call-by-value.
They did change the implementation from an interpreter to a compiler, though.
* A space leak: The deferred computation holds lots of data alive for when it will be needed, whereas computing it would reduce all that data into a small result.
* A latency problem (sometimes called a "time leak") where we may idle around for a while, and only when some value is desperately needed, start computing it. We could preemptively compute the result to hide its latency.
These were not the problem here. The problem here was that part of the computation of the result is redone each time.
Sharing parts of the computation between the invocations was not trivial, and to do this, they found it easier to move the computation to the template loading time ("compile time").
I ask because I like the idea of a stateless xmlish template language, but I wonder what this offers over the zillion existing solutions.
"Separates view and business logic" and "enables DRY design" are valuable goals, but most template languages have them.
1. Heist allows you to define your own HTML/XML tags in the host language (Haskell in this case). This means you're only dealing with (an extended) XML document when doing layout and design so all the normal XML tools still work.
2. Some popular template engines try to separate logic and design but end up letting you cheat a little. Any time you want/have to cheat and put logic in the template, it really was a shortcoming of the template engine. In Heist, you can't cheat but you never want to.
3. The reason #1 and #2 work is because Heist's "recursively applied splices" is just the right abstraction. My HTML templates end up looking just as pretty as well factored Haskell code. Heist makes the perfectionist in me happy.
In short, I would say you're right here: '"Separates view and business logic" and "enables DRY design" are valuable goals, but most template languages have them.' But just because most template languages have these goals doesn't mean they've achieved their goals. Heist, in my experience and opinion, does achieve these goals.
There are two things that compiled Heist loses: the ability to bind new splices on the fly at runtime and splice recursion/composability.
I haven't checked or read the doc thoroughly, but if it's what I think it means - all we get is hierarchal splices. Which is still a lot, but it's not quite as magical.
[1]: http://snapframework.com/docs/tutorials/compiled-splices
At this point it seems to me that this structure also ends up being a desirable one for organizational reasons. But the jury is still out as far as whether there will still be reason to want more. We're aware that there might be good reasons to support this extra power and I have a pretty good idea of how it would be implemented. But I want to get more people using it in the real world before we address that issue.
And as much as it may be possible to separate logic from presentation in a typical PHP/ASP/JSP style template, I've never actually seen it done. When something is made awkward, people tend to choose the more convenient approach, so you see an unfortunate amount of nested loops and conditionals in most templates. Being able to have designers write templates by simply telling them "anywhere you want dynamic content, just make up an appropriately named tag for it and pretend it is part of html" is really nice.
Generating HTML is really the whole point of a web framework so it better be awesome at doing so. Heist does this well.
I would be led to understand, given your comments, that Heist does not allow control structures in the templates, but looking at some snap code[0] it would seem iteration is right there. Which makes sense I guess.
Or am I missing something, and there is a fundamental difference between Heist's
<posts:reverseChronological>
<a href="${post:url}">stuff</a>
</posts:reverseChronological>
and, say, Genshi's <a py:for="post in reverseChronological" href="${post:url}">stuff</a>
Is the difference, and thus your preference, in the fact that posts:reverseChronological works more like a function call taking the content as argument, rather than a "classic" loop?[0] https://github.com/snapframework/snap-website/blob/master/bl...
I don't think it is. To me it is just the haskell template engine in that style (which is the style I prefer). That style of template engine is the minority, so most comparisons are vs either mixed style (php/asp/jsp/rails) or vs custom syntaxes (mustache, haml, etc). What is good about heist certainly applies to similar template engines like lifts, zopes, etc.
"When we originally wrote Heist, speed was not our goal." from the link submitted here
Feel like a contradiction, couldn't they just say that it turned out fast enough on the first try?
not trying to bash haskell, but i think there's an interesting q about how well it (or any other language) can hide changing implementation details (particularly major ones like a compilation phase) behind an unchanging api.
or maybe that would have been possible, but the api changed for other reasons (the general cleanup)?
really interesting article btw. would have loved more detail... an explanation of introducing compilation in haskell with example would be pretty cool (pretty sure either pg or norvig has written one - with lisp - that i vaguely remember reading years ago).
We actually did preserve the old API, so you can actually migrate without making significant changes to your code. Most of those changes are because of the general cleanup. So maybe my statement about big breaking changes was misleading. They're big breaking changes IF you want the performance increase. Otherwise things still work the way they did before. In fact, the process of implementing this refactoring impressed upon me that the old paradigm was even more important that I initially realized.
If you're interested in more detail, check out the rest of the docs linked at the end. They describe the concepts in more detail with a focus on how to use them. In January I will also be giving a presentation to the New York Haskell Users Group (http://www.meetup.com/NY-Haskell/) about some of the things I learned while implementing this new approach and merging it back into the original Heist code base.