Some thoughts on performance

This started off as a reply to @stobot asking about performance related matters in the following thread: Help with listops and spaces - #5 by stobot

As such it might ramble a bit and this is mostly what I could think of off the top of my head, so please do correct and add as necessary.


Performance has been prominently on my mind recently as I have finally started the process of rewriting my Notation editor to use the new features from 5.1.23 and 5.2.0, many of which were directly inspired by that needs that arose while creating that editor. Now sometimes there are several new ways of doing things for which I previously used custom JavaScript, and I have to stop and consider the performance implications of each.

I will try to summarize some general thoughts on performance that might be helpful.

When thinking of performance, I tend to divide it into three areas:

  • render performance - related to how long it takes to display something the first time
  • refresh performance - related to how long it takes to refresh when underlying data (i.e. tiddlers) changes. Poorly implemented UI code can re-render entirely each time instead of selectively refreshing where needed.
  • action performance - related to performance in response to use interaction like clicking a button or a keyboard shortcut. The realm of action widgets.

Render and refresh performance

Considerations like using fewer widgets and having a less complex widget tree apply to both refresh and render, though often can be felt more so during refreshing. In general this is often the area that you want to optimize heavily as the impact is most acutely perceived by users. When I did some heavy refactoring of Streams earlier this year, there was an almost 5 fold performance benefit in render time for very large streams and it came from the following:

  • fewer widgets and DOM elements (DOM elements are also widgets)
    • filtered transclusions to apply classes to change presentation instead of hiding and showing different DIVs/Widgets.
  • more performant filter constructions
  • avoiding macros that perform substitution
  • avoiding the wikify widget. There are extremely few situations where the use of this widget is warranted, and using it to set variable values in most situations is an antipattern. Use filters and transclusions instead. If you need a good pattern to follow, the entire TiddlyWiki UI has been built with wikitext and wikify has been used only a tiny handful of times.

Note that even a relatively simple looking template can have a high render and refresh overhead if you are transcluding it many times, for example for many bullets.

Most widgets that create DOM nodes, such as Button or Reveal, accept tag and class attributes allowing you to control what type of HTML element they create and with which class name. Use this to avoid extra clutter in the HTML and the widget tree.

Instead of:

<$reveal type="match" state="$:/state/SampleReveal1" text="show">
<div class="myclass">
! This is the revealed content
And this is some text
</div>
</$reveal>

consider this:

<$reveal tag="div" class="myclass" type="match" state="$:/state/SampleReveal1" text="show">

! This is the revealed content
And this is some text

</$reveal>

Also worth noting here are techniques like the DynaView Widget to help with initial render times for very large data sets, and the new counter variable for the <$list> widget which you want to avoid for any lists where the number of items will change frequently.

Refresh performance

There are some extra considerations here that add more nuance to what was just discussed above.

Fewer vars widgets or more?
Above I mentioned using fewer widgets, so the temptation can be to set your variables at the very beginning of a template if they will be used in several places. The vars and set (and wikify but you don’t use that right?) widget will refresh, i.e. re-render their entire content, every time the calculated value of the variable changes. Therefore, often you will get vastly better refresh performance by using multiple widgets as needed to assign the same variable, placing those widgets as close to where they will be needed as possible.

Filtered transclusion for HTML element attributes.
All HTML elements in TW are widgets, that is why you can use transclusions and filtered transclusions for their attributes. Where possible, assign attributes directly from such transclusions instead of first assigning a variable and then using it. Attributes for all HTML elements are just updated as needed when the result of the transclusion changes, whereas any widget setting the variable will force a re-render of its contents when the variable changes.

So this:
<div class={{{ [...myfilter...] }}}></div>

instead of:

<$vars className={{{ [...myfilter...] }}}>
<div class=<<className>> ></div>
</$vars>

For widgets that generate DOM nodes like the ButtonWidget, I have slowly been optimizing the core code so that classes and similar attributes are simply updated instead of re-rendering the node and its contents. However, there are still some widgets where this isn’t true.

Macros to specify filters for list widget

Sometimes there can be a temptation to specify the filter for a list widget via a macro, something like:

<$list filter=<<getListFilter>> />

In such situations, if the output of the macro changes, the entire list is re-rendered since the filter attribute has changed. However, if your filter uses variables, the filter attribute does not change, only its output does and the list widget is heavily optimized to refresh its list items to match the new filter output.

Action performance

Always use the actions attribute of a widget to specify actions via a variable or macro, do not embed actions in the body of a widget. Not only does this open the door to using the new action refresh policy in a reliable manner - which makes for more intuitive code - but the resulting parse tree is much simpler and the action widgets aren’t part of the widget tree (and refresh cycle) when not being executed.

Of the three areas for performance consideration this is probably the least critical in that it will usually result in the least noticeable gains. I still haven’t gone back and improved the actions in Streams because there is no actual need, apart from a desire to tidy up and optimize code just for the sake of it.

Firstly, any widget tree created no matter how long or complex, is only transient. (It will be interesting to see if the new action refresh policy makes a difference here, though I doubt it will be noticeable in real world usage).

Secondly when dealing with a large list of items, a template needs to be rendered and refreshed for every single item, whereas actions often deal with a subset of the list only.

Thirdly, if there is a noticeable delay in response to a user action - which should only happen in the most complex of use cases - this is something users find less noticeable and are more accustomed to and accepting of, than render/refresh delays.

My recommendation is that especially during development, be very verbose with your action string to help with debugging and getting the logic right and don’t worry about using too many widgets. Optimize later if need be.

Set vs Vars widget
My general recommendation would be to always use <$vars> unless:

  • you need to return a list of titles from a filter and not just the first title. Vars will always return just the first title. Set returns a title list with all the titles returned from the filter.
  • you really need the extra flexibility offered by the set widget where you can assign values (potentially via filtered transclusions) depending on what the filter attribute returns. In most cases you can do the same thing with vars.

As of TiddlyWiki v5.2.1, $let should be used instead of $vars. You can assign a variable and then use that updated variable value to assign another variable in the same widget, further reducing the number of widgets needed.
<$let a="1" b={{{ [<a>add[1]] }}}/> => b will now have the value 2.

Eventcatcher and really long lists
When dealing with very long lists, using the $eventcatcher widget can help to vastly simplify the widget tree and improve both render and refresh performance.

Imagine a $list widget that generates a list with 500 items, and for each of them we are creating a list of buttons that invoke some actions when clicked. This means there are 500 $button widgets in our list that need to be rendered and refreshed. Using a single $eventcatcher widget around the list can allow us to replace these 500 $button widgets with the much lighter alternative of 500 DIVs, delegating the responsibility for intercepting clicks and triggering actions to the $eventcatcher widget.

As an example imagine a long list generated by a $list widget that creates a button for each list item:

<$list filter="....">
<$button tag="div" class="myclass" actions=<<myactions>> />
</$list>

Alternatively you can do something along these lines, which tells the $eventcatcher to listen for clicks inside DIVs with the class “myclass” and invoke actions in response.

<$eventcatcher $click=<<myactions>> selector="div.myclass">
<$list filter="....">
<div class="myclass" />
</$list>
</$eventcatcher>

Wikitext in CSS
TiddlyWiki allows us to use wikitext in CSS and the core already uses this for things like modifying the CSS based on whether the sidebar is hidden or shown, and to transclude colors from the palette. Overall, care should be taken with using wikitext in CSS as the stylesheets have to be recalculated on every keystroke to check if they need to be refreshed, and a simple list widget used in a stylesheet can result in a very large chunk of CSS.

However, in some situations this can be a powerful technique that if used properly can provide significant performance gains. Consider the above example of a list with 500 list items again. Now imagine that one of them should have a different class designating a “selected” state as specified by a state tiddler.

We could use a filtered transclusion to specify the class name, e.g:
<div class={{{ [[mystatetiddler]get[text]match<currentTiddler>then[activeClass]] }}}>.

However, as fast as this simple filter is to execute, in a very large list it adds up. Instead, using dynamic CSS that transcludes the title of the active tiddler from the state tiddler and targets the list item via a data- property can be significantly faster.
Example:
<div data-item-title=<<currentTiddler>>/>
and:

[data-item-title="{{{[{$:/temp/Notation/search/active}escapecss[]]}}}"] {
	 background-color: #5778d8;
	 color: #ffffff;
}
12 Likes

Wow, this is VERY appreciated @saqimtiaz , I won’t be back at desk for a while to start really digesting this, but I look forward to it!!

Well, if I wanted to make a single title list of all the values from a JSON data tiddler, how could I do that without a wikify wrapped around the output of nested list widgets?

I’m not sure, what a “single title list” is, but the keyvalues plugin will give you simple access to data-tiddler values without any wikify calls.

Firstly I’ll point out that I have mentioned that there can be some few situations in which using wikify is warranted. The question you pose is almost one of them. Also consider that there are potentially other performance considerations when using data tiddlers, which may negate the performance gains by avoiding wikify.

Most of the filter run prefixes introduced in the recent releases are geared towards facilitating exactly these kinds of needs.

This is how I would approach it (untested code since I never use data tiddlers and therefore cannot easily test):

Given a data tiddler called data:

[[data]indexes[]] :map[[data]getindex<currentTiddler>] :reduce[format:titlelist[]addprefix[ ]addprefix<accumulator>]

Note that you only need to reduce if you want to save the titlelist in a variable. If you want to use the values of the data tiddler in a list filter for example, you do not need the :reduce[] filter run.

Or if the reference to the data tiddler is in the variable currentTiddler:

[[<currentTiddler>]indexes[]] :map[<..currentTiddler>getindex<currentTiddler>] :reduce[format:titlelist[]addprefix[ ]addprefix<accumulator>]

The above constructions are perhaps easier to understand if one is unfamiliar with map and reduce, however they can be further simplified to omit the :map[] filter run.

For example:

[[data]indexes[]] :reduce[[data]getindex<currentTiddler>format:titlelist[]addprefix[ ]addprefix<accumulator>]
1 Like

This information is pure gold, thanks a lot ! I wish there was something like https://jsben.ch/ but for tiddlywiki … or maybe I just need to learn how to use the build-in performance instrumentation lol.

I have a question : it’s never clear to me when should I use a list widget vs a reveal widget to conditionally show/hide something. The reveal widget always create a dom element even when the condition is not matched, whereas a list widget with no item output does not generate a DOM element.

Does that mean a list widget is better for performances ?

E.g, I have a ViewTemplate that shows a macro for CSS Stylesheet instead of the tiddler body if a tag is set in order to enable syntax highlighting on tiddlers without the text/css type.

Which one would be better to use for performance :

<$list filter="[all[current]tag[$:/tags/Stylesheet]then<folded-state>get[text]match[show]]" variable=_>
<$macrocall $name="copy-code" language=css display-language=show code={{!!text}} />
</$list>

or

<$list filter="[all[current]tag[$:/tags/Stylesheet]]">
<$reveal tag="div" type="nomatch" stateTitle=<<folded-state>> text="hide">
<$macrocall $name="copy-code" language=css display-language=show code={{!!text}} />
</$reveal>
</$list>

Hah! As it would happen I have been pondering this very question just this week, looking at the code for both $list and $reveal side by side. As it turns out I don’t think there is a significant difference between the two performance wise.

My choice for which to use depends on whether I want the generated DOM node, and whether I may want to animate the hide/reveal of content.

For simple conditional display of content both are theoretically overkill, and there is an argument to be made for a very simple <$if> widget. However, without testing it is difficult to predict if the difference in performance would be noticeable enough to be worthwhile.

In your particular example, using the $list widget alone is definitely preferable to combining both $list and $reveal.

Hope that helps, busy week. Will revisit later when I have given it some more consideration.

1 Like

Thanks! I obviously didn’t understand the full potential of map !

Yes, I try to warn newbies away from data tiddlers, but am seldom heeded (there is a discussion right now on GG where participants are eagerly pursuing a data tiddler approach).

In this case, I wanted to analyze OriginalTiddlerPaths, so there is no choice but to use data tiddlers.

Which Evan Balster provided a while back. I much prefer it over <$reveal> (perhaps only for semantic reasons). And the fact he allowed <$else> to be used following a falsey <$reveal> is pretty cool, too, See: Condition Plugin

I have a nagging feeling I read somewhere that @joshuafontany took over the condition plugin along with the formula plugin GitHub - EvanBalster/TiddlyWikiFormula: Functional Formulas for TiddlyWiki, in the style of Excel and Google Sheets

1 Like

That make sense, thanks !

I thought that combining the two could be interesting for a ViewTemplate that needs the fold animation. If a reveal widget where to be used alone, all tiddlers would get a DOM element, even those who do not match… that’s why I sometimes use a reveal widget inside a list widget, but I never did proper performances testing.

From the core performance side, there wouldn’t be a big difference. BUT If you have a lot of tiddlers visible in the DOM there definitely is a difference. Drawing the DOM has the biggest impact on TW’s UI response time.

The main problem is, that by default TW draws big lists, even if the elements are way outside the viewport. …

But if you show eg: 100 tiddlers at a time you probably would have problems even with a decent mobile. …

I have added some thoughts on using $eventcatcher and the use of wikitext in stylesheets.

2 Likes

Great stuff @saqimtiaz - I think I understand the stylesheets piece, but having trouble with the eventcatcher one. How do you replace using buttons if you “think you” need buttons?

1 Like

In TiddlyWiki if you think you need $button widgets, it is because you want to be able to click on something to invoke actions.

As an example imagine a long list generated by a $list widget that creates a button for each list item:

<$list filter="....">
<$button tag="div" class="myclass" actions=<<myactions>> />
</$list>

Alternatively you can do something along these lines, which tells the $eventcatcher to listen for clicks inside DIVs with the class “myclass” and invoke actions in response.

<$eventcatcher $click=<<myactions>> selector="div.myclass">
<$list filter="....">
<div class="myclass" />
</$list>
</$eventcatcher>

Not only is having a bunch of DIVs faster than $button widgets, but this also opens the door to be able to use static pre-rendered lists in some situations.

Beautiful, that’s probably exactly what I need for the bullets plugin for selecting inactive lines!

Static/pre-rendered as in pre-declared?

<$eventcatcher $click= ...>
 <div ... />
 <div ... />
 <div ... />
  ...
 <div ... />
</$eventcatcher>

Interesting.

EDIT: That’s pure gold! THANK YOU @saqimtiaz !!!

:trophy:

Okay, so event delegation is almost trivial in JavaScript but to see it working in wikitext with a couple of widgets and a little wiring… wow. :sunglasses:

There is a way to go around that, with the subfilter operator :

<$vars list="A B C D E +[allbefore:include[C]]" > {{{ [subfilter<list>] }}}

This can also be used to use several variables in one filter :

<$vars a="1" b="[<a>add[1]]"> {{{ [subfilter<b>] }}} = 2

Is $vars more performant than the new $let ? If not, shouldn’t the var widget be entirely replaced by the new let widget ?

Seems like this might become the basis for a WYSIWYG editor view template (though it might work a little different than true WYSIWY editors).

It is worth keeping in mind that a lot of these performance implications only matter in practice when you reach sufficient complexity and scale with your code. The average user probably needs never think about this unless perhaps writing code for others, hence why this discussion is in the developer’s area.

If your list content will change rarely enough, you can use the change itself as the trigger to render and save it, and then re-use until the next time it changes. Wont be worthwhile unless you really are dealing with very large lists and then Dynaview would be an alternative way to approach it.

@telumire there are many ways to return multiple items from $vars or $let. The intent here is to provide guidance as to when it might make sense to use $set. $let is only more performant than $vars if it allows you to use fewer widgets, and is a lot more flexible in that sense. We cannot modify the behaviour of $vars due to backwards compatibility, therefore we have a new widget $let. $vars should really be considered deprecated.

@Mark_S yes as long as each “chunk” of content is wrapped in an identifying DOM node that is possible. On a macro scale that is what Streams does to know which node you want to edit.

Since the variables provided to actions by $eventcatcher also provide information on the location of the clicks, you could even try to implement something like an etch-a-sketch. This demo uses the $eventcatcher to know which list item a user has swiped on and animates it in response to the swipe:

1 Like

Note: As of 17/03/2022 this no longer an issue in TW 5.2.2 pre-release. The CSS is only updated when it has changed, regardless of underlying widgets used in stylesheets.

A quick note follows which I will try to elaborate on better when the opportunity presents itself:

A recommendation if using wikitext in stylesheets:
Avoid using filtered transclusions.

So instead of:

.myclass [data-item-title="{{{ [{active}escapecss[]] }}}"] {
	color: #fff;
}

do this instead:

<$let activeTitle={{{ [{active}escapecss[]] }}}>
.myclass [data-item-title="<$text text=<<activeTitle>>/>"] {
	color: #fff;
}
</$let>

The problem with a filtered transclusion is that you are creating a $list widget where the default item template is a link. Not only is that more overhead in terms of rendering, but links refresh more aggressively and this can lead to unexpected refreshing of the stylesheet. (If one part of the stylesheet refreshes, the entire stylesheet needs to be refreshed.)

For example in the code above with a filtered transclusion, if the tiddler whose name is saved in the tiddler “active” changes, this will force the entire stylesheet to be re-rendered which is not what one would expect. This is because under the covers a link is being generated to that tiddler whose name is stored in the tiddler “active” and this link needs to be refreshed every time that tiddler changes, in case the tiddler becomes/or stops being a missing tiddler. This in turn triggers the entire stylesheet needing to be refreshed.

However with the $let approach the stylesheet will only be refreshed when the value of the “active” tiddler changes, which is what one would expect.

Edit: the nuance here is that the entire stylesheet is replaced/updated - potentially on every keystroke in some situations - despite the CSS not having changed at all. The natural assumption is that the stylesheet is updated when the CSS has changed.

3 Likes