v3 jsrender performance



  • After upgrading to v3 I noticed a performance degradation of about 4x in rendering a html template with jsrender. How should I go about investigating this?

    I am looking at the total time reported at the end e.g. "Rendering request 43 finished in 338 ms"

    On a docker test environment
    v2 ~90ms
    v3 ~340ms

    On a virtual machine test environment
    v2 ~250ms
    v3 ~1000ms

    Most time is spend at the "Executing recipe html" step

    nodejs: 16.13.2
    jsreport: 3.4.1



  • Would you be able to provide us with a simple demo (template and input data)?
    And what config for templatingEngines.strategy in v2 do you use?



  •   "templatingEngines": {
        "strategy": "http-server"
      },
    

    I have tested an empty template and no data, which results in the same numbers.



  • I've tried a code like this

    const client = require('jsreport-client')('http://localhost:5488')
    const data = JSON.parse(require('fs').readFileSync('test.json'))
    
    console.time('run')
    client.render({
        template: {
            content: '{{for items}}{{#data}}{{/for}}',
            engine: 'jsrender',
            recipe: 'html'
        },
        data
    }).then(() => console.timeEnd('run')).catch(console.error)
    

    With 20mb input data and 10000 items:

    **
    jsreport v2 ~ 500ms
    jsreport v3 ~ 320ms
    **

    For the empty template:
    **
    jsreport v2 ~ 50ms
    jsreport v3 ~ 80ms
    **

    There is a constant additional overhead in v3, but it should be typically faster for bigger data inputs.
    I'm not sure how have you done the measurement, but note the v3 uses lazy compile, so you need to always run render several times first before measuring.



  • It seems to be caused by the global shared helper when evaluating require lines

    e.g.

    const datefns = require('date-fns')
    const datefnslocale = require('date-fns/locale')
    const datefnstz = require('date-fns-tz')
    

    jsreport-assets/lib/worker.js

              await runInSandbox(userCode, {
                filename: a.name,
                source: userCode,
                entitySet: 'assets',
                entity: {
                  ...a,
                  content: asset.content.toString()
                }
              })
    


  • I've tried to add the global helpers with the same require calls but this didn't change much for me.
    However, with some better measurements, I see a significant slowdown on "empty" simple HTML templates.
    The problems were located and I'm currently analyzing how we could improve this.



  • Could you share more details about what you are seeing with simple html templates?

    For me with html or chrome-pdf templates the jsrender step is slower with the require lines vs those lines commented out.

    on my local machine for a html template
    without ~100ms
    with ~300ms

    when I add some logging on this part of the code in runInSandbox.js, it seems that each require takes about 70ms

          requireMap: (moduleName) => {
            const m = reporter.options.sandbox.modules.find((m) => m.alias === moduleName || m.path === moduleName)
    
            if (m) {
              return require(m.path)
            }
    
            if (moduleName === 'jsreport-proxy') {
              return jsreportProxy
            }
    
            if (onRequire) {
    // added logging
              return onRequire(moduleName, { context })
            }
          }
    

    sandbox config

      "sandbox": {
        "allowedModules": [
          "date-fns",
          "date-fns/locale",
          "date-fns-tz"
        ],
    


  • The reason here is that the jsreport v2 https-server sandboxing doesn't work properly.
    You can require a module, modify something there and the next request will get the modified module on require.
    This was insecure and problematic so the v3 works differently.
    It caches the required module just for the lifetime of the requests and the following requests always get the fresh module on require. This is a bit slower because it needs to perform the module load on every request. We can consider making this behavior optional, however, it is for sure the correct default.



  • What is the difference in this require of the code

           if (m) {
              return require(m.path)
            }
    

    vs this?

            if (onRequire) {
              return onRequire(moduleName, { context })
            }
    


  • fyi i kinda resolved the performance impact of loading the modules each render, not sure if this is the right way but i saw some other plugins do the same thing

      jsreport.afterConfigLoaded(reporter => {
    
        if (reporter.options.sandbox.allowedModules) {
          reporter.options.sandbox.modules = reporter.options.sandbox.modules || []
    
          reporter.options.sandbox.allowedModules.forEach(moduleName => {
            reporter.options.sandbox.modules.push({
              alias: moduleName,
              path: require.resolve(moduleName)
            })
          });
        }
      })
    

Log in to reply
 

Looks like your connection to jsreport forum was lost, please wait while we try to reconnect.