Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Small clarification to perf results in README #8

Closed
weavejester opened this issue Jul 5, 2023 · 2 comments · Fixed by #10
Closed

Small clarification to perf results in README #8

weavejester opened this issue Jul 5, 2023 · 2 comments · Fixed by #10

Comments

@weavejester
Copy link

You have some very concise and clean code in Huff, and for that to result in a faster overall parser than Hiccup is impressive! So first, let me offer my congratulations for what looks like a great library.

However, you may want to change:

Performance: 29% faster than hiccup/hiccup

To:

Performance: 29% faster than hiccup/hiccup for runtime-generated HTML (i.e. without pre-compilation)

Or something to that effect. Hiccup uses pre-compilation when passed literal value, whereas Huff, I believe, does not. This means performance can differ significantly depending on how much literal data is passed. For example in Hiccup:

(defn greeting [name]
  (hiccup.core/html [:div.example "Hello " [:em name]]))

Is macro-expanded to:

(defn greeting [name]
  (str "<div class=\"example\">Hello <em>" name "</em></div>"))

(For hiccup2.core/html it gets a little more complex as it has to account for automatically escaping unknown strings, but overall it's the same principle.)

This is obviously very fast because it just uses string concatenation, and will outperform a runtime parser. That's not to say a faster runtime parser like Huff couldn't be more useful in general, but it is worth clarifying.

On the subject of things worth clarifying, you may also want to mention that Huff has support for both Reagent-style fragments and Hiccup-style fragments in the form of lists. i.e. it looks like:

(defn twins [x] (list
                 [:div.a x]
                 [:div.b x]))

(h/html [:span.parent [twins "elements"]])

Will work correctly in Huff, at least judging from the tests.

@escherize
Copy link
Owner

Hi James, I have been a big fan of hiccup for ages! thank you for the compliment, it means a great deal. I want to include notes about compilation with hiccup but I'm having trouble finding performance increases by pre-compiling. I'm trying to benchmark

(defn run-big-hiccup [] (hiccup/html (drop 1 big-page)) :done)

as well as simply

(hiccup/html (drop 1 big-page))

in the perf-info branch, which also has some timing info from my machine.

@weavejester
Copy link
Author

weavejester commented Jul 14, 2023

The expression (drop 1 big-page) cannot be pre-compiled, because Hiccup doesn't know what big-page is until runtime. Pre-compilation works only for literals, i.e. values that are known at compile-time. So

;; Fully pre-compiled, as all literals
(fn [_] (hiccup/html [:span "example"]))

;; Partially pre-compiled, as a mix of literals and symbols
(fn [x] (hiccup/html [:span x]))

;; No pre-compilation, as no literals
(fn [x] (hiccup/html x))

So in order to simulate this, we'd need to evaluate big-page as a literal:

(def run-big-hiccup
  (eval `(fn [] (hiccup/html ~(drop 1 big-page)))))

Except that won't work, because (eval big-page) doesn't work. I dug into this a little, first with medium-page:

user=> (eval huff.perf/medium-page)
Syntax error compiling at (REPL:1:1).
Unable to resolve symbol: falsetrue in this context

Looks like there's some bare symbols, so I'll quote them to make it literal:

user=> (eval (w/postwalk #(if (symbol? %) `(quote ~%) %) huff.perf/medium-page))
Syntax error (IndexOutOfBoundsException) compiling fn* at (REPL:1:1).
Method code too large!

So Clojure won't allow a literal as large as medium-page, and therefore big-page is probably out of the question as well. Due to limitations of the Clojure compiler, we can't deal with literal values this large directly.

But even if we could, I don't think it's worth evaluating the run-time performance of pre-compilation, because in the ideal case, there is no run-time code. In other words, these two examples are equivalent:

(def example-1
  (let [pre-compiled (hiccup/html some-data)]
    (fn [] pre-compiled)))

(def example-2
  (eval `(fn [] (hiccup/html ~some-data)))))

The most efficient Hiccup function is returning a literal string, with zero time spent on calculations at runtime, because that's all been handled at compile-time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging a pull request may close this issue.

2 participants