markfinger / python-react

Server-side rendering of React components
MIT License
1.62k stars 116 forks source link

Slow component rendering #5

Closed WhackoJacko closed 9 years ago

WhackoJacko commented 9 years ago

Having simple component:

var React = require('react');
var Container = React.createClass({

    render: function () {
        return (
            <div>
                Simple container
            </div>
        );
    }
});

module.exports = Container;

_rendercomponent takes about 1 second. Is there any way to improve speed of rendering? Setting _djangowebpack.settings.DEBUG to True doesn`t seem to help.

markfinger commented 9 years ago

You can get a small improvement by adding DJANGO_REACT['DEBUG'] = False to your settings, this sets NODE_ENV=production.

I've generally wrapped the rendering in a DEBUG check...

if django_react.settings.DEBUG:
    markup = component.render_container()
else:
    markup = component.render_to_string()

Performance is fast for dev, and on prod - where the rendering typically matters most - you can usually cache the generated markup.

In any case, the rendering cost is high and definitely needs to be improved. I suspect - but haven't profiled - that a lot of the cost is booting up node and loading everything in.

I took a poke around the React gem for Rails and it looks like they spin up a number of JS processes so that they have a pool to utilise on demand (https://github.com/reactjs/react-rails#server-rendering-1). My major road block on pursuing this is a strong desire to avoid the complexity which comes from managing that pool. If there is a lib, or third party solution (supervisor..?) perhaps that would be of use.

markfinger commented 9 years ago

I'll leave this open as it's a large problem.

The priority is some sort of profiling suite which can indicate the bottlenecks.

WhackoJacko commented 9 years ago

Thanks again, Mark. Will keep my eye on this issue.

markfinger commented 9 years ago

Yeah, the overhead on a cold boot of the renderer was the big bottleneck. I got a ~50 fold increase in performance when I switched over to a prototype of a render server which sits alongside the python process and responds over HTTP.

The rendering performance problem is down to the issue that every call to render_component requires a cold boot of node before anything gets rendered. Switching to a persistent, standalone node process would alleviate that overhead.

The only issues I can see with a persistent process sitting alongside the python process are:

standalone render server performance as a percentage of django_react.render_component
--------------------------------------------------------------------------------
max: 8.61138243181%
min: 1.86210436925%
avg: 2.3553003031%
median: 2.05074496877%

django_react.render_component
--------------------------------------------------------------------------------
times: [0.21702003479003906, 0.21471405029296875, 0.2179570198059082, 0.21650290489196777, 0.21557211875915527, 0.21570301055908203, 0.21838903427124023, 0.21029996871948242, 0.21512484550476074, 0.21892595291137695, 0.21367788314819336, 0.21283388137817383, 0.21916508674621582, 0.21521496772766113, 0.21786785125732422, 0.21573996543884277, 0.2140181064605713, 0.22343897819519043, 0.2166588306427002, 0.21626591682434082, 0.2243211269378662, 0.214310884475708, 0.2212519645690918, 0.22001218795776367, 0.21599102020263672]
max: 0.224321126938
min: 0.210299968719
mean: 0.216839103699
median: 0.216265916824

render via standalone node instance
--------------------------------------------------------------------------------
times: [0.019317150115966797, 0.004524946212768555, 0.0044269561767578125, 0.005631208419799805, 0.004354953765869141, 0.004359006881713867, 0.004597902297973633, 0.004567861557006836, 0.005520820617675781, 0.00423884391784668, 0.004435062408447266, 0.004353046417236328, 0.004564046859741211, 0.004564046859741211, 0.0044438838958740234, 0.00471806526184082, 0.004436969757080078, 0.004353046417236328, 0.004261970520019531, 0.0042459964752197266, 0.004274129867553711, 0.004177093505859375, 0.004342079162597656, 0.004431009292602539, 0.004540205001831055]
max: 0.019317150116
min: 0.00417709350586
mean: 0.00510721206665
median: 0.00443506240845
markfinger commented 9 years ago

I'll update this when I've pushed a functional replacement for the current render process.

markfinger commented 9 years ago

@WhackoJacko

The latest codebase and PyPI version spins up a dedicated render server which offers some massive performance boosts.

When rendering a simple 'Hello, World' component, my machine is now averaging 3% of the time formerly required.

By default, there is an initial overhead when the first component is rendered, which is down to the render server being spun up, but every successive render call will be massively faster.

If you want to avoid the first render's overhead and start with a warm render server, define DJANGO_REACT['START_RENDER_SERVER_ON_INIT'] = True, this will cause the render server to boot during your python server's startup.

Here's some perf starts comparing the new render against the older one.

(django-react)~/Projects/django-react $ python django_react/tests/performance/run_tests.py

Running perf test with a warm render server...
DJANGO_REACT['RENDERER'] = 'django_react.render_server.ReactRenderServer' perf test
--------------------------------------------------------------------------------
Total time taken to render a component 25 times: 0.154497146606
Times: [0.06791496276855469, 0.00417780876159668, 0.003203868865966797, 0.0030939579010009766, 0.003473043441772461, 0.004724025726318359, 0.003371000289916992, 0.003133058547973633, 0.005461931228637695, 0.0033960342407226562, 0.0039010047912597656, 0.0035049915313720703, 0.004064083099365234, 0.0034401416778564453, 0.003448963165283203, 0.003170013427734375, 0.003443002700805664, 0.003534078598022461, 0.0033609867095947266, 0.003365039825439453, 0.003216981887817383, 0.0036149024963378906, 0.003699064254760742, 0.00333404541015625, 0.003450155258178711]
Max: 0.0679149627686
Min: 0.003093957901
Mean: 0.00617988586426
Median: 0.00344896316528

Running perf test with a render server booted on demand...
DJANGO_REACT['RENDERER'] = 'django_react.render_server.ReactRenderServer' perf test
--------------------------------------------------------------------------------
Total time taken to render a component 25 times: 0.473686933517
Times: [0.388322114944458, 0.004202127456665039, 0.0031290054321289062, 0.0032699108123779297, 0.0031850337982177734, 0.0045430660247802734, 0.0034651756286621094, 0.0034558773040771484, 0.005578041076660156, 0.003610849380493164, 0.003265857696533203, 0.0032219886779785156, 0.0037429332733154297, 0.0033538341522216797, 0.0035910606384277344, 0.003610849380493164, 0.0033957958221435547, 0.0032958984375, 0.0032989978790283203, 0.003326892852783203, 0.0033888816833496094, 0.0035238265991210938, 0.0032958984375, 0.003248929977416992, 0.003364086151123047]
Max: 0.388322114944
Min: 0.00312900543213
Mean: 0.0189474773407
Median: 0.00338888168335

Running perf test with a renderer which boots node on every render request...
DJANGO_REACT['RENDERER'] = 'django_react.renderer.ReactRenderer' perf test
--------------------------------------------------------------------------------
Total time taken to render a component 25 times: 5.52515673637
Times: [0.2518901824951172, 0.21484017372131348, 0.2172248363494873, 0.21650409698486328, 0.22143292427062988, 0.22011089324951172, 0.21518206596374512, 0.22161293029785156, 0.21700501441955566, 0.21958208084106445, 0.22993087768554688, 0.22188711166381836, 0.22236108779907227, 0.21548700332641602, 0.22033190727233887, 0.2214500904083252, 0.22034907341003418, 0.22076106071472168, 0.21924901008605957, 0.21874308586120605, 0.2151811122894287, 0.21937799453735352, 0.22022104263305664, 0.23113703727722168, 0.2133040428161621]
Max: 0.251890182495
Min: 0.213304042816
Mean: 0.221006269455
Median: 0.22011089325