BorisMoore / jsrender

A lightweight, powerful and highly extensible templating engine. In the browser or on Node.js, with or without jQuery.
http://www.jsviews.com
MIT License
2.68k stars 340 forks source link

Performance issues #242

Closed Bnesme closed 10 years ago

Bnesme commented 10 years ago

http://help.infragistics.com/Doc/jQuery/2014.1/CLR4.0?page=igGrid_jsRender_Integration.html

Hi,

I am using JsRender with Infragistics grid, and it's quite powerfull, well done ! Now that it's working, it's time to look into performance. We are having massive slowdowns when updating the grid in realtime - when the user is typing text in a searchbox. -> All cells are redrawn, and it can be very laggy on some machines (or under IE8-11). The grid doesn't have that many cells : 20 rows x 3 columns, so it's anormally slow on a modern computer.

Using a profiler, I see that the methods renderTag, renderContext and $extend of jsviews.js takes most of the CPU time.

Below are the templates used. Are we using too many {{if}}, or {{properties}}, or {{includes}} ? What would be the best way to optimize this ?

The templates :

<script id="column0Template" type="text/x-jsrender">
  <div class='table nowrap'>
    <div class='table-row'>
      {{if #view.hlp('isListView')()}}
        <div class='table-cell fieldname'>
          Adresse :
        </div>
      {{/if}}
      <div class='table-cell '>
        <div class='siteName' style='white-space:nowrap;font-weight:bold'>
          {{include tmpl='#siteTitleTemplate' /}}
        </div>
      </div>
    </div>
    {{if #view.hlp('categoryImageFromStatus')(StatusId)}}
      <div class='table-row'>
        {{if #view.hlp('isListView')()}}
          <div class='table-cell fieldname'>
            Etat : 
          </div>
        {{/if}}
        <div class='table-cell'>
          <img class='imgProgression' style='width:16px' src='{{:#view.hlp("categoryImageFromStatus")(StatusId)}}'/>
          <span class='txtProgression' style='white-space:nowrap;vertical-align: middle'>{{:#view.hlp('statusTextFromStatus')(StatusId)}}</span>
        </div>
      </div>
    {{/if}}
    <div class='table-row'>
      {{if #view.hlp('isListView')()}}
      <div class='table-cell fieldname'>
        Actions : 
      </div>
      {{/if}}
      <div class='table-cell'>
        {{:#view.hlp('actionsFromStatus')(StatusId))}}
        {{if #view.hlp('isListView')()}}
          <a data-target='pdl={{>PDL}}' class='pdlDetailsLink spaLink' style='cursor:pointer''> <span>voir détails...</span></a>
          @{
            var url = Url.Action("AjaxDoSomething", "MyController", new {perimeterId = Model.PerimeterId});
            <a style='margin-left:25px' href='@(url)&pdl={{>PDL}}'>refresh state</a>
          }
        {{/if}}
      </div>
    </div>
  </div>
</script>

<script id="column1Template" type="text/x-jsrender">
  <div class="table nowrap">
    <div class="table-row">
      <div class="table-cell fieldname">Property 1 : </div>
      <div class="table-cell margin5 bold fixed-font">{{>SummaryInfos.Property1}}</div>
    </div>
    <div class="table-row">
      <div class="table-cell fieldname">Property 2 : </div>
      <div class="table-cell margin5 bold fixed-font">
        {{if SummaryInfos.Property2}}
          {{>SummaryInfos.Property2}}
        {{else}}
          {{>SummaryInfos.Property3}}
        {{/if}}
      </div>
    </div>
    <div class="table-row">
      <div class="table-cell fieldname">Property 4 : </div>
      <div class="table-cell margin5 bold fixed-font">{{>SummaryInfos.Property4}}</div>
    </div>
  </div>
</script>

<script id="column2Template" type="text/x-jsrender">
  <div style='white-space:nowrap;'>
    {{if LastEntries[0]}}
      {{if LastEntries[0].Date}}
        le {{:#view.hlp('formatDate')(LastEntries[0].Date)}}
      {{/if}}
      {{if LastEntries[0].Id}}
      &nbsp;&nbsp;&nbsp;n° {{>LastEntries[0].Id}}
      {{/if}}
      {{if LastEntries[0].SourceFormat}}
      <div class='siteDescription' style='margin-left:0px'>Format {{>LastEntries[0].SourceFormat}}</div>
      {{/if}}
    {{/if}}
  </div>
</script>

<script id="siteTitleTemplate" type="text/x-jsrender">
  {{if SiteName && SiteName != SiteAddressL1}}
    {{>SiteName}}
  {{/if}}
  {{>SiteAddressL1}}
  {{>SiteAddressL2}}
  {{>SiteAddressL3}}
  {{>SiteAddressL4}}
  {{>SiteAddressL5}}
  {{>SiteAddressL6}}
  {{if !SiteName && !SiteAddressL1 && !SiteAddressL2 && !SiteAddressL3 && !SiteAddressL4 && !SiteAddressL5 && !SiteAddressL6}}
    non disponible
  {{/if}}
</script>

Any help will be really appreciated, Benoit

BorisMoore commented 10 years ago

Certainly, the above does not look optimal. I haven't had time to look at it very closely. But I notice things like

        {{if SummaryInfos.Property2}}
          {{>SummaryInfos.Property2}}
        {{else}}
          {{>SummaryInfos.Property3}}
        {{/if}}

which you should be able to replace with

{{>SummaryInfos.Property2 || SummaryInfos.Property3}}

and

       <div class='siteName' style='white-space:nowrap;font-weight:bold'>
          {{include tmpl='#siteTitleTemplate' /}}
        </div>

could be replaced by something like:

<div class='siteName' style='white-space:nowrap;font-weight:bold'>
  {{if !SiteName && !SiteAddressL1 && !SiteAddressL2 && !SiteAddressL3 && !SiteAddressL4 && !SiteAddressL5 && !SiteAddressL6}}
    non disponible
  {{else}}
    {{>SiteName != SiteAddressL1 && SiteName}}
    {{>SiteAddressL1}}
    {{>SiteAddressL2}}
    {{>SiteAddressL3}}
    {{>SiteAddressL4}}
    {{>SiteAddressL5}}
    {{>SiteAddressL6}}
  {{/if}}
</div>

BTW for your syntax for helpers such as {{if #view.hlp('isListView')()}} I'm not sure why you didn't write {{if ~isListView()}}.

It seems you are calling this particular helper many times: {{if #view.hlp('isListView')()}}. - You may be better off using a completely different columnTemplate for the case when isListView() is true and the case when it is false - rather than including those smarts within the template.

There could well be some perf issues around that fact that your grid is re-rendering completely as the user types. You could make it update only once they have finished typing?

Using JsViews data-linking rather than JsRender can allow incremental updates, rather than re-rendering the whole grid, but that would probably require not using the Infragistics grid at all, or working on integration between the Infragistics grid and data-linking.

Bnesme commented 10 years ago

Thanks for your reply and hints. I didn't know the syntax for {{~myFunc()}} and {{>SummaryInfos.Property2 || SummaryInfos.Property3}}.

With IE11 profiler I could see that all cpu time was spent in jsRender.$extend. Redrawing all cells (20x3 cells) was taking 500ms on a I7 4770K.

I found a workaround that reduce the redrawing time to about nothing : If I move the "template code" inside a subtemplate and include this subtemplate in the parent template, the slowdowns disappear. For instance :

<script id="column0Template" type="text/x-jsrender">
  {{include tmpl='#siteTemplate' /}}
</script>
<script id="siteTemplate" type="text/x-jsrender">
  ... here all my templated properties ...
</script>

instead of

<script id="column0Template" type="text/x-jsrender">
  ... here all my templated properties ...
</script>

I suspect there is a precompilation stage in jsRender that will reuse the templates from the {{include ...}} whereas defining the template directly inside the column templates needed by Infragistics won't benefit from the precompilation stage.

But I didn't look enough in JsRender and Infragistics internals to be sure of this.

BorisMoore commented 10 years ago

That seems strange - that it should be faster when you use {{include ...}}. Maybe Infragistics are retriggering the compiling of the template every time.

Would it be possible for you to create a couple of jsfiddle samples showing the two cases - preferably simplified as much as possible just to show the performance hit difference when you don't use {{include}} and when you do? If you can do that I will then try to debug what is happening...

BorisMoore commented 10 years ago

@Bnesme: I didn't hear back from you, so I will close this issue. Let me know if you want to re-open, or if you are able to create jsfiddle examples for additional investigation. Thanks...

Bnesme commented 10 years ago

@BorisMoore: a jsfiddle example is on the way, I didn't have time to finish it yet.

BorisMoore commented 10 years ago

Oh - great. We'll re open this as appropriate, once we have looked at the example...