question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Extreme inefficiency of String operations in the JavaScript runtime

See original GitHub issue

I’ve created a simple helper TagOutput to generate well-formed and well-escaped XML/HTML content. In the test shown below, I’m using this class to generate a relatively small (4k) HTML fragment in memory and append it to the browser DOM:

import ceylon.collection {

    MutableList,
    ArrayList
}
interface State of elementContent | tagOpen | attributeOpen {}

object elementContent satisfies State {}
object tagOpen satisfies State {}
object attributeOpen satisfies State {}

shared class TagOutput() {

    variable State state = elementContent;

    MutableList<String> openTags = ArrayList<String>();

    StringBuilder buffer = StringBuilder();

    shared actual String string => buffer.string;

    shared TagOutput tag(String tagName) {
        closeStart();
        buffer.append("<");
        buffer.append(tagName);
        openTags.add(tagName);

        state = tagOpen;
        return this;
    }

    shared TagOutput openAttribute(String name) {
        assert (state == tagOpen);

        buffer.append(" ");
        buffer.append(name);
        buffer.append("=\"");

        state = attributeOpen;
        return this;
    }

    shared TagOutput closeAttribute() {
        assert (state == attributeOpen);

        buffer.append("\"");

        state = tagOpen;
        return this;
    }

    shared TagOutput attribute(String name, String? val) {
        assert (state == tagOpen);

        if (exists val) {
            openAttribute(name);
            attributeValue(val);
            closeAttribute();
        }

        return this;
    }

    shared TagOutput end(String? expectedTagName = null) {
        String? tagName = openTags.deleteLast();
        assert (exists tagName);

        if (exists expectedTagName) {
            assert (tagName.equals(expectedTagName));
        }

        closeStart();
        buffer.append("</");
        buffer.append(tagName);
        buffer.append(">");
        return this;
    }

    shared TagOutput endEmpty() {
        assert (state == tagOpen);

        String? tagName = openTags.deleteLast();
        assert (exists tagName);

        buffer.append("/>");

        state = elementContent;
        return this;
    }

    shared TagOutput text(String text) {
        closeStart();
        quoteText(text);

        return this;
    }

    void closeStart() {
        switch (state) 
        case (elementContent) {
            // Ignore
        }
        case (tagOpen) {
            buffer.append(">");
            state = elementContent;
        }
        case (attributeOpen) {
            "Missing call to `closeAttribute`."
            assert (false);
        }
    }

    void quoteText(String val) {
        val.each((ch) {
            switch (ch)
            case ('<') {
                buffer.append("&lt;");
            }
            case ('>') {
                buffer.append("&gt;");
            }
            case ('&') {
                buffer.append("&amp;");
            }
            else {
                buffer.appendCharacter(ch);
            }
        });
    }

    shared TagOutput attributeValue(String val) {
        assert (state == attributeOpen);

        val.each((ch) {
            switch (ch)
            case ('<') {
                buffer.append("&lt;");
            }
            case ('>') {
                buffer.append("&gt;");
            }
            case ('&') {
                buffer.append("&amp;");
            }
            case ('"') {
                buffer.append("&quot;");
            }
            else {
                buffer.appendCharacter(ch);
            }
        });

        return this;
    }

}

"Run the module `tagout`."
shared void run() {
    value output = TagOutput();

    for (n in 0..20) {
        output.tag("div").attribute("id", "c" + n.string).attribute("class", "foo");
        for (x in 0..4) {
            output.tag("span").attribute("class", "bar");
            output.text("some text ");
            output.end("span");
        }
        output.end("div");
    }

    dynamic {
        dynamic div = window.document.createElement("div");
        div.innerHTML = output.string;
        window.document.body.appendChild(div);
    }
}

In the UI, I experienced a long delay for that operation. Using the browser’s profiler, the problem is obvious: For generating this 4k document, 6M calls to String.charCodeAt and 6M calls to String.countCodepoints took almost all the time:

profiling-ceylon

I’m almost sure, that the problem is not in my code… any thoughts?

Issue Analytics

  • State:closed
  • Created 8 years ago
  • Comments:28 (21 by maintainers)

github_iconTop GitHub Comments

1reaction
haumachercommented, Jan 5, 2016

… ok the problem are my glasses, not the machine… I just confused the rows…

0reactions
chochoscommented, Jul 24, 2016

So, was the problem really just with append? If so, I ran a test to append 4000 1-char strings to a StringBuilder and it takes under 3s on my shitty laptop.

Anyway, the code in the original issue description runs really fast now, so I’m closing this.

Read more comments on GitHub >

github_iconTop Results From Across the Web

What is the efficiency of javascript .toUpperCase method?
1 Answer 1 ... The algorithm described in String.prototype.toLowerCase ( ) of the specification is O(n). Each codepoint of the original String (or ......
Read more >
Adventures in the land of substrings and RegExps.
Whenever you see a string value in your JavaScript code - it can actually be backed by any of those representations, runtime is...
Read more >
Performance with JavaScript String Objects - Mozilla Hacks
This article aims to take a look at the performance of JavaScript engines towards primitive value Strings and Object Strings.
Read more >
Let's talk about Javascript string encoding | Kevin Burke
First, some very basics about string encoding. ... It would be inefficient to waste 4 bytes on every "a" in the document -...
Read more >
JavaScript Tutorial: The Basics
Basic JavaScript Tutorial for the beginners.
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found