Feature: improved diffing #103

Closed
wants to merge 7,942 commits into
from

Conversation

Projects
None yet
@ramezrafla

See Issue #45

Summary: the diff-ing engine used by Meteor lets through content that has not changed, especially relevant when we are display arrays. We added a fast comparison function and used it in two key locations to prevent needless changes to the DOM.

martijnwalraven and others added some commits Feb 9, 2016

Merge pull request #4520 from dolgarev/devel
Added check of params for template's methods events and helpers
@ramezrafla

This comment has been minimized.

Show comment
Hide comment
@ramezrafla

ramezrafla Sep 19, 2016

The tests failed as it is unable to download Meteor properly grrr .. @mitar, could you handle this at your earliest. I'd like to make an announcement on the Meteor forums as it will give an impetus to Blaze that it is getting some serious improvement in performance.

The tests failed as it is unable to download Meteor properly grrr .. @mitar, could you handle this at your earliest. I'd like to make an announcement on the Meteor forums as it will give an impetus to Blaze that it is getting some serious improvement in performance.

@mitar

This comment has been minimized.

Show comment
Hide comment
@mitar

mitar Sep 19, 2016

Collaborator

So, this is in fact more related to the #68. It is known that comparison function can be improved, but it is unclear what is the good one for every case. Because there is a trade-off between doing comparisons (you need CPU cycles, especially on large objects, which can even potentially have cycles) and not, but then doing diffing at some other layer.

For example, doing diffing on data layer can be potentially much larger than doing diffing on DOM layer because results DOM often contains only a subset of initial data.

What exactly is the right approach is a question. For example, some projects are doing completely different thing, where they use reference equality for everything and have immutable data. So if data is immutable and reference is equal (very quick comparison), then you know that data is the same. If references are not equal, you see them as different data. Again, the idea here is that comparison on data are expensive so it might be better to do diffing on some other layer.

Blaze by default is pretty conservative here. It compares only primitive values and the rest leave to DOM layer (middle ground between deep comparison and reference comparison). #45 talks about the idea of improving the DOM layer. And #68 about the idea of improving the equality function.

What I think we should do in the big plan is (#75):

  • extend Blaze templates to be classes so it is easy to extend them
  • move equality function (or even data context creation function) to a method of this class, so that subclasses can override it
  • make it easy to use a subclass (<template is="subclass">)
  • even better, stop using data context, but allow custom reactive fields on the template instance, so that you can for each field configure what equality you want
  • in addition to that, allow different materializes based on templates
    • some which allow 3rd party modifications (jQuery)
    • some which do virtual/incremental DOM, but do not allow 3rd party modifications
    • some which are good for animations (it does not have to be independent category)

This would allow developers to tailor their Blaze use to their particular need. Maybe their optimization point is really data, so they can to diffing there. Maybe it is DOM, because it is too much data to diff. Or too many reactivity things which get triggered anyway.

BTW, besides diffing there is also caching. I use computed field for that. The issue is that if you have three reactive sources inside autorun and one of them changes, both of those three reactive sources are invalidated and recomputed, instead of two of them returning simply the cached previous value. This is again something I think developers could then do at any granularity they want with the above changes.

I think the current Blaze default is a good conservative equality. But I think we should approach the question of equality in a more general way than trying to modify the default to one or the other special cases.

Collaborator

mitar commented Sep 19, 2016

So, this is in fact more related to the #68. It is known that comparison function can be improved, but it is unclear what is the good one for every case. Because there is a trade-off between doing comparisons (you need CPU cycles, especially on large objects, which can even potentially have cycles) and not, but then doing diffing at some other layer.

For example, doing diffing on data layer can be potentially much larger than doing diffing on DOM layer because results DOM often contains only a subset of initial data.

What exactly is the right approach is a question. For example, some projects are doing completely different thing, where they use reference equality for everything and have immutable data. So if data is immutable and reference is equal (very quick comparison), then you know that data is the same. If references are not equal, you see them as different data. Again, the idea here is that comparison on data are expensive so it might be better to do diffing on some other layer.

Blaze by default is pretty conservative here. It compares only primitive values and the rest leave to DOM layer (middle ground between deep comparison and reference comparison). #45 talks about the idea of improving the DOM layer. And #68 about the idea of improving the equality function.

What I think we should do in the big plan is (#75):

  • extend Blaze templates to be classes so it is easy to extend them
  • move equality function (or even data context creation function) to a method of this class, so that subclasses can override it
  • make it easy to use a subclass (<template is="subclass">)
  • even better, stop using data context, but allow custom reactive fields on the template instance, so that you can for each field configure what equality you want
  • in addition to that, allow different materializes based on templates
    • some which allow 3rd party modifications (jQuery)
    • some which do virtual/incremental DOM, but do not allow 3rd party modifications
    • some which are good for animations (it does not have to be independent category)

This would allow developers to tailor their Blaze use to their particular need. Maybe their optimization point is really data, so they can to diffing there. Maybe it is DOM, because it is too much data to diff. Or too many reactivity things which get triggered anyway.

BTW, besides diffing there is also caching. I use computed field for that. The issue is that if you have three reactive sources inside autorun and one of them changes, both of those three reactive sources are invalidated and recomputed, instead of two of them returning simply the cached previous value. This is again something I think developers could then do at any granularity they want with the above changes.

I think the current Blaze default is a good conservative equality. But I think we should approach the question of equality in a more general way than trying to modify the default to one or the other special cases.

@mitar

This comment has been minimized.

Show comment
Hide comment
@mitar

mitar Sep 19, 2016

Collaborator

Moreover, if you are observing issues with how arrays are rendered inside #each, then you might want to check/improve this package.

Collaborator

mitar commented Sep 19, 2016

Moreover, if you are observing issues with how arrays are rendered inside #each, then you might want to check/improve this package.

@mitar

This comment has been minimized.

Show comment
Hide comment
@mitar

mitar Sep 19, 2016

Collaborator

And a lot of these optimizations you can already do by not using data context to pass data around. So data context does not (yet) allow to configure its equality function.

Collaborator

mitar commented Sep 19, 2016

And a lot of these optimizations you can already do by not using data context to pass data around. So data context does not (yet) allow to configure its equality function.

@ramezrafla

This comment has been minimized.

Show comment
Hide comment
@ramezrafla

ramezrafla Sep 20, 2016

So, this is in fact more related to the #68. It is known that comparison function can be improved, but it is unclear what is the good one for every case. Because there is a trade-off between doing comparisons (you need CPU cycles, especially on large objects, which can even potentially have cycles) and not, but then doing diffing at some other layer.

So here is the challenge. With the current approach, the 'changed' objects trickle down to be compared with what is actually in the DOM, field by field. The comparison is far from perfect and converts content to DOM elements to compare them.

So the current approach is actually very heavy on CPU cycles. Using a comparison function that does it at the object level is much faster (which may partly explain why I am seeing improvements in performance)

ramezrafla commented Sep 20, 2016

So, this is in fact more related to the #68. It is known that comparison function can be improved, but it is unclear what is the good one for every case. Because there is a trade-off between doing comparisons (you need CPU cycles, especially on large objects, which can even potentially have cycles) and not, but then doing diffing at some other layer.

So here is the challenge. With the current approach, the 'changed' objects trickle down to be compared with what is actually in the DOM, field by field. The comparison is far from perfect and converts content to DOM elements to compare them.

So the current approach is actually very heavy on CPU cycles. Using a comparison function that does it at the object level is much faster (which may partly explain why I am seeing improvements in performance)

@ramezrafla

This comment has been minimized.

Show comment
Hide comment
@ramezrafla

ramezrafla Sep 20, 2016

@mitar,

I am going to raise a concern here. You have very ambitious plans for Blaze (e.g. different materializers). Who is going to maintain all this. I strongly recommend we keep it simple-stupid (KISS principle) and focus on improving it, and adding higher level features useful to developers that you listed in the guide. This is what is needed to keep Blaze alive. If we have developer time and interest, we can go deeper into Blaze and start making improvements and allow overriding etc.

@mitar,

I am going to raise a concern here. You have very ambitious plans for Blaze (e.g. different materializers). Who is going to maintain all this. I strongly recommend we keep it simple-stupid (KISS principle) and focus on improving it, and adding higher level features useful to developers that you listed in the guide. This is what is needed to keep Blaze alive. If we have developer time and interest, we can go deeper into Blaze and start making improvements and allow overriding etc.

@ramezrafla

This comment has been minimized.

Show comment
Hide comment
@ramezrafla

ramezrafla Sep 20, 2016

Yes observe-sequence is where we need to work, except it looks like a big task at the moment (diff-ing engine). Will require more dedication. Not to mention it's a core package and we have to work with MDG developers who are super busy. Won't happen right away.

Yes observe-sequence is where we need to work, except it looks like a big task at the moment (diff-ing engine). Will require more dedication. Not to mention it's a core package and we have to work with MDG developers who are super busy. Won't happen right away.

@arist0tl3

This comment has been minimized.

Show comment
Hide comment
@arist0tl3

arist0tl3 Sep 20, 2016

@ramezrafla Out of curiosity, I'm wondering how you're measuring/seeing performance improvements? Benchmarking?

@ramezrafla Out of curiosity, I'm wondering how you're measuring/seeing performance improvements? Benchmarking?

@ramezrafla

This comment has been minimized.

Show comment
Hide comment
@ramezrafla

ramezrafla Sep 20, 2016

No, purely visual. Lagging on mobile then smooth scrolling on large list (same on desktop, but less noticeable unless you have a very large list).

I knew that if I made changes to an element it would freeze a bit, that no longer happens. I know it's not scientific, but technically, we can see the problem when tracing the code and observing results.

No, purely visual. Lagging on mobile then smooth scrolling on large list (same on desktop, but less noticeable unless you have a very large list).

I knew that if I made changes to an element it would freeze a bit, that no longer happens. I know it's not scientific, but technically, we can see the problem when tracing the code and observing results.

@ramezrafla

This comment has been minimized.

Show comment
Hide comment
@ramezrafla

ramezrafla Sep 21, 2016

@mitar, what is the status on this PR?

@mitar, what is the status on this PR?

@mitar

This comment has been minimized.

Show comment
Hide comment
@mitar

mitar Sep 21, 2016

Collaborator

So you made a pull request for a thing which was not discussed in advance, and which changes one of the fundamental properties of Blaze. As such you must realize that getting this change is a longer process. Moreover, as I described on the forum, it is also unclear that optimizing Blaze for one particular load improves it for all types of loads, or existing types of loads. So this can be potentially backwards incompatible change which will make some other apps slower. I have not seen any quantifiable data confirming that it works better than "it feels better on our apps" statement.

Let me quote from the forum:

Lol, you stopped half-way through. Just because you observed optimization you think you are done? Of course is faster if you improve reactivity. But it is just partial faster from what it could be.

Again, every code has much more data than DOM. Data >>> DOM. Because humans have to process DOM. And we are limited in how much information we can take in.

So diffing at DOM level instead of data level means that you have to diff much much less. React observed this fact and discovered that it is faster to just compute everything and diff at the end.

Blaze is great because it has also low-level data management. So we can have the best of both words and leave to developers to tweak things as they need. But diffing only at data level will never be as fast as React. But combining those two things, that could be the most powerful thing ever. Especially if you can tweak them to optimize for a particular use case.

I talked with Pete Hunt (creator of React) at length about this when we organized his talk at Berkeley.

So I understand that this fixes your particular need now, but I am trying to look for a larger picture of Blaze. Getting something into Blaze should work for all, and should be well architectured.

This is why I asked you, before you started working on this, to look if incremental DOM/virtual DOM can be integrated with Blaze. Because that type of optimization can be done independently from other changes in Blaze. For this change, I think the order of things should be different, as planned in the roadmap, and this is:

  • we first make template extendable
  • we allow developers to fine-tune what type of equality they want and need for their templates
  • we see what types of loads people have and how they tuned different things to respond to those loads
  • we used this to inform our decision to change defaults

So I completely agree that this is a problem. I wrote about it more than one year ago. What I do not believe is:

  • that there is crazy urgency to change this in the next release, we lived with this for years
  • there is a space for putting something in between to make this configurable, instead of just moving a knob left and right for everyone, with unclear understanding how this will influence everyone

I appreciate your work and that you investigated this well, but because it is not something which was planned, it will require also from me to look deeper into claims you are doing and try to understand them better. I also have to do some my own tests because I would like to understand better what is happening about things you are observing. So, I hear what you are saying, I am just not sure that this is the best solution to the problem you are observing. It might be, but it is still unclear to me. And it will take time to analyze this better.

Again, it is just something which is out of order and as such it requires more work.

If you want to help, you can in meantime investigate (maybe you already did) why looping in Minimongo does not work as expected. So we all agree that if one document is inserted in the middle, we would want only that DOM element to be added there and this is it. Why is this not happening? Could #each ... in help here? So instead of trying to mask a problem by diffing, I would like that we do not even trigger a change notification.

It would be great if we would have example instrumented app to all look into and use as a baseline.

Collaborator

mitar commented Sep 21, 2016

So you made a pull request for a thing which was not discussed in advance, and which changes one of the fundamental properties of Blaze. As such you must realize that getting this change is a longer process. Moreover, as I described on the forum, it is also unclear that optimizing Blaze for one particular load improves it for all types of loads, or existing types of loads. So this can be potentially backwards incompatible change which will make some other apps slower. I have not seen any quantifiable data confirming that it works better than "it feels better on our apps" statement.

Let me quote from the forum:

Lol, you stopped half-way through. Just because you observed optimization you think you are done? Of course is faster if you improve reactivity. But it is just partial faster from what it could be.

Again, every code has much more data than DOM. Data >>> DOM. Because humans have to process DOM. And we are limited in how much information we can take in.

So diffing at DOM level instead of data level means that you have to diff much much less. React observed this fact and discovered that it is faster to just compute everything and diff at the end.

Blaze is great because it has also low-level data management. So we can have the best of both words and leave to developers to tweak things as they need. But diffing only at data level will never be as fast as React. But combining those two things, that could be the most powerful thing ever. Especially if you can tweak them to optimize for a particular use case.

I talked with Pete Hunt (creator of React) at length about this when we organized his talk at Berkeley.

So I understand that this fixes your particular need now, but I am trying to look for a larger picture of Blaze. Getting something into Blaze should work for all, and should be well architectured.

This is why I asked you, before you started working on this, to look if incremental DOM/virtual DOM can be integrated with Blaze. Because that type of optimization can be done independently from other changes in Blaze. For this change, I think the order of things should be different, as planned in the roadmap, and this is:

  • we first make template extendable
  • we allow developers to fine-tune what type of equality they want and need for their templates
  • we see what types of loads people have and how they tuned different things to respond to those loads
  • we used this to inform our decision to change defaults

So I completely agree that this is a problem. I wrote about it more than one year ago. What I do not believe is:

  • that there is crazy urgency to change this in the next release, we lived with this for years
  • there is a space for putting something in between to make this configurable, instead of just moving a knob left and right for everyone, with unclear understanding how this will influence everyone

I appreciate your work and that you investigated this well, but because it is not something which was planned, it will require also from me to look deeper into claims you are doing and try to understand them better. I also have to do some my own tests because I would like to understand better what is happening about things you are observing. So, I hear what you are saying, I am just not sure that this is the best solution to the problem you are observing. It might be, but it is still unclear to me. And it will take time to analyze this better.

Again, it is just something which is out of order and as such it requires more work.

If you want to help, you can in meantime investigate (maybe you already did) why looping in Minimongo does not work as expected. So we all agree that if one document is inserted in the middle, we would want only that DOM element to be added there and this is it. Why is this not happening? Could #each ... in help here? So instead of trying to mask a problem by diffing, I would like that we do not even trigger a change notification.

It would be great if we would have example instrumented app to all look into and use as a baseline.

@ramezrafla

This comment has been minimized.

Show comment
Hide comment
@ramezrafla

ramezrafla Sep 21, 2016

Thanks @mitar, I was only asking as it was left in limbo on the forum https://forums.meteor.com/t/community-involvement-setup/29538/15

Essentially our goal was not incremental DOM per se, it was to improve rendering and avoid wasted calls to DOM. I don't think our solution is optimal, but it is very good with the constraints around it.

Look forward to you completing your review, in the mean time, I'll try to see if I can invest time in this:

If you want to help, you can in meantime investigate (maybe you already did) why looping in Minimongo does not work as expected. So we all agree that if one document is inserted in the middle, we would want only that DOM element to be added there and this is it. Why is this not happening? Could #each ... in help here? So instead of trying to mask a problem by diffing, I would like that we do not even trigger a change notification.

It's likely a Tracker / diff issue (something MDG seems to have known about, and so have you)

As far as urgency, there is a message to the community: "Blaze is getting love and it's getting better!"

This could be a temporary fix.

Thanks @mitar, I was only asking as it was left in limbo on the forum https://forums.meteor.com/t/community-involvement-setup/29538/15

Essentially our goal was not incremental DOM per se, it was to improve rendering and avoid wasted calls to DOM. I don't think our solution is optimal, but it is very good with the constraints around it.

Look forward to you completing your review, in the mean time, I'll try to see if I can invest time in this:

If you want to help, you can in meantime investigate (maybe you already did) why looping in Minimongo does not work as expected. So we all agree that if one document is inserted in the middle, we would want only that DOM element to be added there and this is it. Why is this not happening? Could #each ... in help here? So instead of trying to mask a problem by diffing, I would like that we do not even trigger a change notification.

It's likely a Tracker / diff issue (something MDG seems to have known about, and so have you)

As far as urgency, there is a message to the community: "Blaze is getting love and it's getting better!"

This could be a temporary fix.

@mitar

This comment has been minimized.

Show comment
Hide comment
@mitar

mitar Sep 21, 2016

Collaborator

It's likely a Tracker / diff issue (something MDG seems to have known about, and so have you)

No, this is not what is known. It is known that if a document for a particular entry changes at all, that one will be rerun completely, because default equality for data context is conservative. But this for insertion is something new, at least to me. And is not how it was documented in the past. So if this is happening, I would see this as a bug, maybe even regression. And I would like to know where it is coming from. It should work both for arrays with _id and Minimongo.

What I think we have to get to is:

  • rerendering is triggered anytime anything in data context changes (then, we can optimize how data context changes are detected, with similar approach to yours)
  • but not if some other documents in Minimongo are changed, or some other ones are inserted somewhere else, that should be fixed on Minimongo/array tracking level

As far as urgency, there is a message to the community: "Blaze is getting love and it's getting better!"

I think nobody in Meteor community expects "be quick and break things" approach to development. In any case, I do not see that as a good reason to not do things properly. I have also not seen 1000 upvotes or comments to quickly merge on this pull request, so I do not see any urgency from others either.

I know that when you make a pull request you feel amazing and would love to get things merged. But again, when you make a pull request which was not discussed before, then you have to take responsibility to hold your own horses. This is on you.

Collaborator

mitar commented Sep 21, 2016

It's likely a Tracker / diff issue (something MDG seems to have known about, and so have you)

No, this is not what is known. It is known that if a document for a particular entry changes at all, that one will be rerun completely, because default equality for data context is conservative. But this for insertion is something new, at least to me. And is not how it was documented in the past. So if this is happening, I would see this as a bug, maybe even regression. And I would like to know where it is coming from. It should work both for arrays with _id and Minimongo.

What I think we have to get to is:

  • rerendering is triggered anytime anything in data context changes (then, we can optimize how data context changes are detected, with similar approach to yours)
  • but not if some other documents in Minimongo are changed, or some other ones are inserted somewhere else, that should be fixed on Minimongo/array tracking level

As far as urgency, there is a message to the community: "Blaze is getting love and it's getting better!"

I think nobody in Meteor community expects "be quick and break things" approach to development. In any case, I do not see that as a good reason to not do things properly. I have also not seen 1000 upvotes or comments to quickly merge on this pull request, so I do not see any urgency from others either.

I know that when you make a pull request you feel amazing and would love to get things merged. But again, when you make a pull request which was not discussed before, then you have to take responsibility to hold your own horses. This is on you.

@mitar

This comment has been minimized.

Show comment
Hide comment
@mitar

mitar Sep 22, 2016

Collaborator

With the current approach, the 'changed' objects trickle down to be compared with what is actually in the DOM, field by field. The comparison is far from perfect and converts content to DOM elements to compare them.

Yes, this is why I am proposing that we do comparison with previous value of HTMLJS for that object. So instead of converting to DOM elements, we compare HTMLJS itself. And only apply those changes which we find different between HTMLJS values.

Again, this is what I am saying that could be an interesting optimization which would work similar to virtual DOM, now that you investigated incremental DOM and are saying that it is not a good match.

And I agree that we should also optimize the whole pipeline for Tracker recomputations as well. But now imagine that we can make DOM rendering faster without Tracker recomputations optimization. Then when we have both, so great!

You have very ambitious plans for Blaze (e.g. different materializers).

No, this is a very simple change, for this particular approach to materializing with comparing HTMLJS, because you are interested in this. Or was.

Yes observe-sequence is where we need to work, except it looks like a big task at the moment (diff-ing engine). Will require more dedication. Not to mention it's a core package and we have to work with MDG developers who are super busy. Won't happen right away.

It would still be great if we can solve the issue there as well. I do not think we should worry too much about pull requests not getting in, they are pretty efficient on merging pull requests recently. And even if it goes late on, it goes. But somebody has to make, to even go in at all.

Collaborator

mitar commented Sep 22, 2016

With the current approach, the 'changed' objects trickle down to be compared with what is actually in the DOM, field by field. The comparison is far from perfect and converts content to DOM elements to compare them.

Yes, this is why I am proposing that we do comparison with previous value of HTMLJS for that object. So instead of converting to DOM elements, we compare HTMLJS itself. And only apply those changes which we find different between HTMLJS values.

Again, this is what I am saying that could be an interesting optimization which would work similar to virtual DOM, now that you investigated incremental DOM and are saying that it is not a good match.

And I agree that we should also optimize the whole pipeline for Tracker recomputations as well. But now imagine that we can make DOM rendering faster without Tracker recomputations optimization. Then when we have both, so great!

You have very ambitious plans for Blaze (e.g. different materializers).

No, this is a very simple change, for this particular approach to materializing with comparing HTMLJS, because you are interested in this. Or was.

Yes observe-sequence is where we need to work, except it looks like a big task at the moment (diff-ing engine). Will require more dedication. Not to mention it's a core package and we have to work with MDG developers who are super busy. Won't happen right away.

It would still be great if we can solve the issue there as well. I do not think we should worry too much about pull requests not getting in, they are pretty efficient on merging pull requests recently. And even if it goes late on, it goes. But somebody has to make, to even go in at all.

@@ -352,13 +352,16 @@ Blaze._materializeView = function (view, parentView, _workStack, _intoArray) {
view._isInRender = false;
if (! c.firstRun) {
+ // added by ramezrafla@zegenie to skip affecting DOM for unchanged content
+ if (Blaze._deepCompare(lastHtmljs,htmljs)) {

This comment has been minimized.

@mitar

mitar Sep 22, 2016

Collaborator

Oh, you are comparing last and new HTMLJS already. Why you wrote (or I understood) that we are talking about data layer comparison.

So yea, this is similar to what I had in mind with virtual DOM. To compare old and new HTMLJS.

@mitar

mitar Sep 22, 2016

Collaborator

Oh, you are comparing last and new HTMLJS already. Why you wrote (or I understood) that we are talking about data layer comparison.

So yea, this is similar to what I had in mind with virtual DOM. To compare old and new HTMLJS.

This comment has been minimized.

@mitar

mitar Sep 22, 2016

Collaborator

So this part of the change is something I like.

I do think we can do better though. :-)

First, why are you not simply using EJSON.equals? If you think that one can be optimized, than I would suggest make pull requests to it. Hm, but do we want to introduce a new dependency for Blaze?

So, one thing I think we can improve is that it seems that HTMLJS can contain not-rendered templates. See this code snippet. This means that if you are comparing any such lastHtmljs and htmljs they will not be equal, yes? So maybe it would be better if we make one pass over the HTMLJS tree, converting it into a pure HTMLJS tree. And then compare. The issue with that is that this might confuse how rendered callback is called. We would have to mark which segments of HTMLJS require their view 'rendered' to be called. But my worry is that now with any more complicated HTMLJS segment we are not improving much. What do you think?

Next idea: now we are all or nothing. We compare HTMLJS, if it is equal, great. But if there is one attribute difference, we still go in and then create DOM elements for whole HTMLJS tree and compare them, no, inserting/updating them? Can you point me to where DOM elements are compared?

@mitar

mitar Sep 22, 2016

Collaborator

So this part of the change is something I like.

I do think we can do better though. :-)

First, why are you not simply using EJSON.equals? If you think that one can be optimized, than I would suggest make pull requests to it. Hm, but do we want to introduce a new dependency for Blaze?

So, one thing I think we can improve is that it seems that HTMLJS can contain not-rendered templates. See this code snippet. This means that if you are comparing any such lastHtmljs and htmljs they will not be equal, yes? So maybe it would be better if we make one pass over the HTMLJS tree, converting it into a pure HTMLJS tree. And then compare. The issue with that is that this might confuse how rendered callback is called. We would have to mark which segments of HTMLJS require their view 'rendered' to be called. But my worry is that now with any more complicated HTMLJS segment we are not improving much. What do you think?

Next idea: now we are all or nothing. We compare HTMLJS, if it is equal, great. But if there is one attribute difference, we still go in and then create DOM elements for whole HTMLJS tree and compare them, no, inserting/updating them? Can you point me to where DOM elements are compared?

This comment has been minimized.

@mitar

mitar Sep 22, 2016

Collaborator

So currently I worry that this prevents only the innermost HTMLJS trees from being rerendered.

@mitar

mitar Sep 22, 2016

Collaborator

So currently I worry that this prevents only the innermost HTMLJS trees from being rerendered.

This comment has been minimized.

@ramezrafla

ramezrafla Sep 22, 2016

Can you point me to where DOM elements are compared?

In the past, the _isContentEqual did just that. We are no longer doing that now, we are comparing data before it becomes DOM.

So currently I worry that this prevents only the innermost HTMLJS trees from being rerendered.

If the deepcompare finds the objects to be the same, it makes sense to skip re-rendering, unless I missed something.

@ramezrafla

ramezrafla Sep 22, 2016

Can you point me to where DOM elements are compared?

In the past, the _isContentEqual did just that. We are no longer doing that now, we are comparing data before it becomes DOM.

So currently I worry that this prevents only the innermost HTMLJS trees from being rerendered.

If the deepcompare finds the objects to be the same, it makes sense to skip re-rendering, unless I missed something.

This comment has been minimized.

@mitar

mitar Sep 25, 2016

Collaborator

In the past, the _isContentEqual did just that. We are no longer doing that now, we are comparing data before it becomes DOM.

I mean, where it is comparing does it have to replace a DOM element with a new one. It seems to me that it does not do a new DOM element and then compares with current DOM, if necessary. But it removes a DOM element if previous view is invalided, it then creates a DOM element for new state of the view, and adds it. Even if comparing previous and new DOM element would show that it is almost the same, or even exactly the same.

If the deepcompare finds the objects to be the same, it makes sense to skip re-rendering, unless I missed something.

Yes, but I see those checks to be run only for very leaf rendering, nothing before. :-(

@mitar

mitar Sep 25, 2016

Collaborator

In the past, the _isContentEqual did just that. We are no longer doing that now, we are comparing data before it becomes DOM.

I mean, where it is comparing does it have to replace a DOM element with a new one. It seems to me that it does not do a new DOM element and then compares with current DOM, if necessary. But it removes a DOM element if previous view is invalided, it then creates a DOM element for new state of the view, and adds it. Even if comparing previous and new DOM element would show that it is almost the same, or even exactly the same.

If the deepcompare finds the objects to be the same, it makes sense to skip re-rendering, unless I missed something.

Yes, but I see those checks to be run only for very leaf rendering, nothing before. :-(

-// Are the HTMLjs entities `a` and `b` the same? We could be
-// more elaborate here but the point is to catch the most basic
-// cases.
-Blaze._isContentEqual = function (a, b) {

This comment has been minimized.

@mitar

mitar Sep 22, 2016

Collaborator

Why you didn't just improve this method?

@mitar

mitar Sep 22, 2016

Collaborator

Why you didn't just improve this method?

This comment has been minimized.

@ramezrafla

ramezrafla Sep 22, 2016

I could have simply renamed the new function as ._isContentEqual, except this function is meant for DOM comparison, and we are comparing objects / primitives, so to avoid confusion I broke with the past.

@ramezrafla

ramezrafla Sep 22, 2016

I could have simply renamed the new function as ._isContentEqual, except this function is meant for DOM comparison, and we are comparing objects / primitives, so to avoid confusion I broke with the past.

@@ -226,6 +226,11 @@ Blaze.Each = function (argFunc, contentFunc, elseFunc) {
});
},
changedAt: function (id, newItem, oldItem, index) {

This comment has been minimized.

@mitar

mitar Sep 22, 2016

Collaborator

But this is at the data level, no?

@mitar

mitar Sep 22, 2016

Collaborator

But this is at the data level, no?

This comment has been minimized.

@ramezrafla

ramezrafla Sep 22, 2016

Yes, we have two additional diff levels right (see my original PR comment)

  1. Data level
  2. Field level in htmljs

I think 1 will being gone once we improve the original diff-ing which calls the observe handlers

@ramezrafla

ramezrafla Sep 22, 2016

Yes, we have two additional diff levels right (see my original PR comment)

  1. Data level
  2. Field level in htmljs

I think 1 will being gone once we improve the original diff-ing which calls the observe handlers

@mitar

This comment has been minimized.

Show comment
Hide comment
@mitar

mitar Sep 22, 2016

Collaborator

Can you check difference for the 01-dbmon example between current Blaze version and one with this pull request?

Collaborator

mitar commented Sep 22, 2016

Can you check difference for the 01-dbmon example between current Blaze version and one with this pull request?

@@ -352,13 +352,16 @@ Blaze._materializeView = function (view, parentView, _workStack, _intoArray) {
view._isInRender = false;
if (! c.firstRun) {
+ // added by ramezrafla@zegenie to skip affecting DOM for unchanged content
+ if (Blaze._deepCompare(lastHtmljs,htmljs)) {

This comment has been minimized.

@mitar

mitar Sep 22, 2016

Collaborator

So I looked into this change using this example and I could not get it to in fact come to this if (! c.firstRun) { condition with any other view than lookup:something.

If I did:

Collection.update({}, {$set: {value: 'a'}})

Change was from aa to a0.

And then if I did:

Collection.update({}, {$set: {value: 'a', test: 'b'}})

Change was from aa to aa.

So, I think that Blaze._isContentEqual moving up is a great idea for cases like above, where I am not minimizing my fields selector well in Minimongo. But I am not sure if there is any benefit of changing the function to deep-comparison? Can you please adapt my example to the case where you get something else than those simple values Blaze._isContentEqual is already checking? I could imagine only very extreme cases where you would be using {{value}} where value is not a simple variable, but an object, and you are using its stringified value to be shown in the template. But is anyone really doing that?

@mitar

mitar Sep 22, 2016

Collaborator

So I looked into this change using this example and I could not get it to in fact come to this if (! c.firstRun) { condition with any other view than lookup:something.

If I did:

Collection.update({}, {$set: {value: 'a'}})

Change was from aa to a0.

And then if I did:

Collection.update({}, {$set: {value: 'a', test: 'b'}})

Change was from aa to aa.

So, I think that Blaze._isContentEqual moving up is a great idea for cases like above, where I am not minimizing my fields selector well in Minimongo. But I am not sure if there is any benefit of changing the function to deep-comparison? Can you please adapt my example to the case where you get something else than those simple values Blaze._isContentEqual is already checking? I could imagine only very extreme cases where you would be using {{value}} where value is not a simple variable, but an object, and you are using its stringified value to be shown in the template. But is anyone really doing that?

This comment has been minimized.

@ramezrafla

ramezrafla Sep 22, 2016

Right, we had deep variables. I think for a complex App, it is to be expected.

Here is your example adapted to our test case:
var obj = { a: { b : 1}}; Collection.update({}, {$set: obj});

@ramezrafla

ramezrafla Sep 22, 2016

Right, we had deep variables. I think for a complex App, it is to be expected.

Here is your example adapted to our test case:
var obj = { a: { b : 1}}; Collection.update({}, {$set: obj});

This comment has been minimized.

@mitar

mitar Sep 23, 2016

Collaborator

No, it does not happen. So what I am saying is that you will never really do in your app:

{{value}}

Where:

value = {a: {b: 1}};

You might do:

{{value.a.b}}

But what I am saying is that in the comparison, Blaze just compares then values of bs, so primitive values.

So can you give me a realistic example where the value of lastHtmljs and htmljs is something else than what it is checked by Blaze._isContentEqual? I have updated my example with complex objects to test this.

@mitar

mitar Sep 23, 2016

Collaborator

No, it does not happen. So what I am saying is that you will never really do in your app:

{{value}}

Where:

value = {a: {b: 1}};

You might do:

{{value.a.b}}

But what I am saying is that in the comparison, Blaze just compares then values of bs, so primitive values.

So can you give me a realistic example where the value of lastHtmljs and htmljs is something else than what it is checked by Blaze._isContentEqual? I have updated my example with complex objects to test this.

This comment has been minimized.

@ramezrafla

ramezrafla Sep 23, 2016

Oh, I just used deepCompare for the sake of convenience and because I haven't gone through all the options. deepCompare short circuits comparison if it's a primitive so I am reusing the same function for risk mitigation as the data level.

But I did see somewhere in the code that htmljs can be views. Also, we skipped DOM comparison altogether that was there before, which was my biggest concern. If I was too cautious, then we can simply simplify the compare function. But, why would the original developers convert to DOM if it was always primitives?

@ramezrafla

ramezrafla Sep 23, 2016

Oh, I just used deepCompare for the sake of convenience and because I haven't gone through all the options. deepCompare short circuits comparison if it's a primitive so I am reusing the same function for risk mitigation as the data level.

But I did see somewhere in the code that htmljs can be views. Also, we skipped DOM comparison altogether that was there before, which was my biggest concern. If I was too cautious, then we can simply simplify the compare function. But, why would the original developers convert to DOM if it was always primitives?

This comment has been minimized.

@mitar

mitar Sep 25, 2016

Collaborator

But I did see somewhere in the code that htmljs can be views. Also, we skipped DOM comparison altogether that was there before, which was my biggest concern. If I was too cautious, then we can simply simplify the compare function.

Yes, but at a higher level. These views which have !computation.firstRun seems to be very simple ones.

Which is also a problem probably, that higher-levels are recreated.

So can you make in your codebase (on which you are doing these optimizations here) a check to print out when computation.firstRun is false and htmljs is something complicated.

But, why would the original developers convert to DOM if it was always primitives?

I have no idea why they convert at all before the if. The fact that you moved if out is great, but I have no idea why they did it like this. Does converting to DOM has some side-effects we do not know?

@mitar

mitar Sep 25, 2016

Collaborator

But I did see somewhere in the code that htmljs can be views. Also, we skipped DOM comparison altogether that was there before, which was my biggest concern. If I was too cautious, then we can simply simplify the compare function.

Yes, but at a higher level. These views which have !computation.firstRun seems to be very simple ones.

Which is also a problem probably, that higher-levels are recreated.

So can you make in your codebase (on which you are doing these optimizations here) a check to print out when computation.firstRun is false and htmljs is something complicated.

But, why would the original developers convert to DOM if it was always primitives?

I have no idea why they convert at all before the if. The fact that you moved if out is great, but I have no idea why they did it like this. Does converting to DOM has some side-effects we do not know?

@@ -226,6 +226,11 @@ Blaze.Each = function (argFunc, contentFunc, elseFunc) {
});
},
changedAt: function (id, newItem, oldItem, index) {

This comment has been minimized.

@mitar

mitar Sep 22, 2016

Collaborator

In my example I could not get to this issue. If I instrument changedAt method to print out every time is called, and then I click "add 5" button, this is not called at all, not for 5 or any other value in the Minimongo database.

@mitar

mitar Sep 22, 2016

Collaborator

In my example I could not get to this issue. If I instrument changedAt method to print out every time is called, and then I click "add 5" button, this is not called at all, not for 5 or any other value in the Minimongo database.

This comment has been minimized.

@mitar

mitar Sep 22, 2016

Collaborator

I tried even to limit my fields selector to {value: 1, test: 1}. Then:

Collection.update({}, {$set: {value: 'a'}})
newItem, oldItem: Object {value: "a", _id: "s84f5D5bwFgPzwvmk"} Object {value: 0, _id: "s84f5D5bwFgPzwvmk"}
Collection.update({}, {$set: {value: 'a', test: 'b'}})
newItem, oldItem: Object {value: "a", _id: "s84f5D5bwFgPzwvmk", test: "b"} Object {value: "a", _id: "s84f5D5bwFgPzwvmk"}
Collection.update({}, {$set: {value: 'a', test: 'b', test2: 'c'}})

The last change changes a field outside of the projection. And changedAt is not called.

So, I could not reproduce that changedAt would be called unnecessary if a value was not really changed.

@mitar

mitar Sep 22, 2016

Collaborator

I tried even to limit my fields selector to {value: 1, test: 1}. Then:

Collection.update({}, {$set: {value: 'a'}})
newItem, oldItem: Object {value: "a", _id: "s84f5D5bwFgPzwvmk"} Object {value: 0, _id: "s84f5D5bwFgPzwvmk"}
Collection.update({}, {$set: {value: 'a', test: 'b'}})
newItem, oldItem: Object {value: "a", _id: "s84f5D5bwFgPzwvmk", test: "b"} Object {value: "a", _id: "s84f5D5bwFgPzwvmk"}
Collection.update({}, {$set: {value: 'a', test: 'b', test2: 'c'}})

The last change changes a field outside of the projection. And changedAt is not called.

So, I could not reproduce that changedAt would be called unnecessary if a value was not really changed.

This comment has been minimized.

@ramezrafla

ramezrafla Sep 22, 2016

Right, our test case was as per above comment:
var obj = { a: { b : 1}}; Collection.update({}, {$set: obj});

@ramezrafla

ramezrafla Sep 22, 2016

Right, our test case was as per above comment:
var obj = { a: { b : 1}}; Collection.update({}, {$set: obj});

This comment has been minimized.

@mitar

mitar Sep 23, 2016

Collaborator

So I could not reproduce that changedAt when data projected using fields didn't really change.

For example, if I have the following cursor over which I am doing #each:

Collection.find({}, {sort: {'value.a': 1}, fields: {'value.a': 1, test: 1}});

And values are inserted like:

Collection.insert({value: {a: 1}});

When I do:

Collection.update({}, {$set: {'value.b': 'b'}})

changedAt is not called at all.

And if I insert one one document in the middle, only Blaze for that one document is called, and no changedAt at all, for nothing.

@mitar

mitar Sep 23, 2016

Collaborator

So I could not reproduce that changedAt when data projected using fields didn't really change.

For example, if I have the following cursor over which I am doing #each:

Collection.find({}, {sort: {'value.a': 1}, fields: {'value.a': 1, test: 1}});

And values are inserted like:

Collection.insert({value: {a: 1}});

When I do:

Collection.update({}, {$set: {'value.b': 'b'}})

changedAt is not called at all.

And if I insert one one document in the middle, only Blaze for that one document is called, and no changedAt at all, for nothing.

This comment has been minimized.

@ramezrafla

ramezrafla Sep 23, 2016

The only difference with what I did is that it was server side update with method. If you can replicate it, it means that the problem is in the publish / diff-ing.

@ramezrafla

ramezrafla Sep 23, 2016

The only difference with what I did is that it was server side update with method. If you can replicate it, it means that the problem is in the publish / diff-ing.

This comment has been minimized.

@mitar

mitar Sep 25, 2016

Collaborator

Could you please make an example showing what you observed?

We need tests for these changes anyway.

@mitar

mitar Sep 25, 2016

Collaborator

Could you please make an example showing what you observed?

We need tests for these changes anyway.

@mitar

This comment has been minimized.

Show comment
Hide comment
@mitar

mitar Sep 22, 2016

Collaborator

(I wanted to make this an inline reply to a inline comment, but I am realizing it is pretty valuable, so I am moving it top-level.)

Yes, sorry about that. I understood something else under "data level". For me this is more data context level, which has its own issues. Sorry for misunderstanding.

Maybe we should define terminology here better. To my knowledge we have issues at:

  • data context level, equality being what it is, which triggers reruns on any non-primitive value change:
    • possible solutions:
      • get rid of data context
      • allow custom data context with custom equality function
  • tracker level, which is similar to data context level, but means that in general any reactive dependency might rerun more often than necessary
    • possible solutions:
      • using something like computed field to be precise about dependency changes
  • no tracker caching level: if you have autorun with multiple dependencies, when one of those change, also other are recomputed, instead of previously returned value just cached and returned
  • minimongo and array processing level: we want changes to be triggered only when data really changes, and only for those changes
  • blaze data level (what you are talking about here, I think): that propagation of data through blaze might be ineffective and be done when data has not really changed
  • HTMLJS materialization level: when HTMLJS does not really change, but Blaze still builds DOM elements and does diff against DOM
  • interacting with DOM level: querying DOM for state is often expensive

Did I miss anything?

Collaborator

mitar commented Sep 22, 2016

(I wanted to make this an inline reply to a inline comment, but I am realizing it is pretty valuable, so I am moving it top-level.)

Yes, sorry about that. I understood something else under "data level". For me this is more data context level, which has its own issues. Sorry for misunderstanding.

Maybe we should define terminology here better. To my knowledge we have issues at:

  • data context level, equality being what it is, which triggers reruns on any non-primitive value change:
    • possible solutions:
      • get rid of data context
      • allow custom data context with custom equality function
  • tracker level, which is similar to data context level, but means that in general any reactive dependency might rerun more often than necessary
    • possible solutions:
      • using something like computed field to be precise about dependency changes
  • no tracker caching level: if you have autorun with multiple dependencies, when one of those change, also other are recomputed, instead of previously returned value just cached and returned
  • minimongo and array processing level: we want changes to be triggered only when data really changes, and only for those changes
  • blaze data level (what you are talking about here, I think): that propagation of data through blaze might be ineffective and be done when data has not really changed
  • HTMLJS materialization level: when HTMLJS does not really change, but Blaze still builds DOM elements and does diff against DOM
  • interacting with DOM level: querying DOM for state is often expensive

Did I miss anything?

@ramezrafla

This comment has been minimized.

Show comment
Hide comment
@ramezrafla

ramezrafla Sep 22, 2016

@mitar
You obviously know a lot more of the Blaze history than I do, I just jumped recently to resolve the needless re-rendering when no data has changed. I am sure I will learn all these issues as I go carefully through the code and the issues.

ignore 3rd party changes in DOM and just trust the previous HTMLJS state and apply it blindly (should probably be configurable per template)

We have an App that affects the DOM directly and had no issues. I think it has to do with carefully managing collisions. We do not with JS affect anything that is handled by Blaze. If that separation is done, we're good

blaze data level (what you are talking about here, I think): that propagation of data through blaze might be ineffective and be done when data has not really changed
HTMLJS materialization level: when HTMLJS does not really change, but Blaze still builds DOM elements and does diff against DOM

Right! So the first one has to do with the diff-ing algorithm in the compare-sequence lib you had talked about before. This is where the real fix should be applied, unless it breaks something else (is this used by Tracker?)

The second is where I believe we have a lot of savings. We don't build DOM elements to diff against DOM. This is bad design. We should always diff raw data, not display layer. This is what makes Blaze fast (like VirtualDOM) and is the reason Incremental DOM is slower than Virtual DOM.

Edit: And given so many unchanged objects can be fed through, that DOM element creation for diffing is really problematic

ramezrafla commented Sep 22, 2016

@mitar
You obviously know a lot more of the Blaze history than I do, I just jumped recently to resolve the needless re-rendering when no data has changed. I am sure I will learn all these issues as I go carefully through the code and the issues.

ignore 3rd party changes in DOM and just trust the previous HTMLJS state and apply it blindly (should probably be configurable per template)

We have an App that affects the DOM directly and had no issues. I think it has to do with carefully managing collisions. We do not with JS affect anything that is handled by Blaze. If that separation is done, we're good

blaze data level (what you are talking about here, I think): that propagation of data through blaze might be ineffective and be done when data has not really changed
HTMLJS materialization level: when HTMLJS does not really change, but Blaze still builds DOM elements and does diff against DOM

Right! So the first one has to do with the diff-ing algorithm in the compare-sequence lib you had talked about before. This is where the real fix should be applied, unless it breaks something else (is this used by Tracker?)

The second is where I believe we have a lot of savings. We don't build DOM elements to diff against DOM. This is bad design. We should always diff raw data, not display layer. This is what makes Blaze fast (like VirtualDOM) and is the reason Incremental DOM is slower than Virtual DOM.

Edit: And given so many unchanged objects can be fed through, that DOM element creation for diffing is really problematic

@mitar

This comment has been minimized.

Show comment
Hide comment
@mitar

mitar Sep 26, 2016

Collaborator

You obviously know a lot more of the Blaze history than I do, I just jumped recently to resolve the needless re-rendering when no data has changed. I am sure I will learn all these issues as I go carefully through the code and the issues.

Oh, maybe I do know about history, but about internals we are all learning. I think you looked much deeper than I ever did in details of rendering itself. So we are all learning here.

We have an App that affects the DOM directly and had no issues. I think it has to do with carefully managing collisions. We do not with JS affect anything that is handled by Blaze. If that separation is done, we're good

Yes, me too. Or you leave to Blaze, or you use something else. But do not mix. :-)

So the first one has to do with the diff-ing algorithm in the compare-sequence lib you had talked about before. This is where the real fix should be applied, unless it breaks something else (is this used by Tracker?)

But I have not been able to reproduce it so that I could look what is happening.

But I was using Minimongo and not arrays directly.

The second is where I believe we have a lot of savings. We don't build DOM elements to diff against DOM. This is bad design. We should always diff raw data, not display layer. This is what makes Blaze fast (like VirtualDOM) and is the reason Incremental DOM is slower than Virtual DOM.

Yes, but I could not really find where diffing is done. I have found diffing for attributes, but for DOM it seems like Blaze simply removes old element and adds a new one, when something changes. It does not try to diff and patch the old one?

Collaborator

mitar commented Sep 26, 2016

You obviously know a lot more of the Blaze history than I do, I just jumped recently to resolve the needless re-rendering when no data has changed. I am sure I will learn all these issues as I go carefully through the code and the issues.

Oh, maybe I do know about history, but about internals we are all learning. I think you looked much deeper than I ever did in details of rendering itself. So we are all learning here.

We have an App that affects the DOM directly and had no issues. I think it has to do with carefully managing collisions. We do not with JS affect anything that is handled by Blaze. If that separation is done, we're good

Yes, me too. Or you leave to Blaze, or you use something else. But do not mix. :-)

So the first one has to do with the diff-ing algorithm in the compare-sequence lib you had talked about before. This is where the real fix should be applied, unless it breaks something else (is this used by Tracker?)

But I have not been able to reproduce it so that I could look what is happening.

But I was using Minimongo and not arrays directly.

The second is where I believe we have a lot of savings. We don't build DOM elements to diff against DOM. This is bad design. We should always diff raw data, not display layer. This is what makes Blaze fast (like VirtualDOM) and is the reason Incremental DOM is slower than Virtual DOM.

Yes, but I could not really find where diffing is done. I have found diffing for attributes, but for DOM it seems like Blaze simply removes old element and adds a new one, when something changes. It does not try to diff and patch the old one?

@ramezrafla

This comment has been minimized.

Show comment
Hide comment
@ramezrafla

ramezrafla Sep 26, 2016

From view.js

var rangesAndNodes = Blaze._materializeDOM(htmljs, [], view);
if (! Blaze._isContentEqual(lastHtmljs, htmljs)) {
domrange.setMembers(rangesAndNodes);
Blaze._fireCallbacks(view, 'rendered');
}

So this creates the DOM elements before comparing

ramezrafla commented Sep 26, 2016

From view.js

var rangesAndNodes = Blaze._materializeDOM(htmljs, [], view);
if (! Blaze._isContentEqual(lastHtmljs, htmljs)) {
domrange.setMembers(rangesAndNodes);
Blaze._fireCallbacks(view, 'rendered');
}

So this creates the DOM elements before comparing

@mitar

This comment has been minimized.

Show comment
Hide comment
@mitar

mitar Sep 27, 2016

Collaborator

So this creates the DOM elements before comparing

Yes, but it compares HTMLJS, not DOM.

For DOM it seems (inside setMembers) that it simply removes old and adds new, I do not see any logic to really compare old DOM and new DOM to see the minimal change. Hm.

Collaborator

mitar commented Sep 27, 2016

So this creates the DOM elements before comparing

Yes, but it compares HTMLJS, not DOM.

For DOM it seems (inside setMembers) that it simply removes old and adds new, I do not see any logic to really compare old DOM and new DOM to see the minimal change. Hm.

@mitar

This comment has been minimized.

Show comment
Hide comment
@mitar

mitar Oct 4, 2016

Collaborator

BTW, this is waiting for tests/reproducible example for the changedAt issue.

The second change I like and I think we should add it to Blaze, but I will maybe add it with _isContentEqual, but also tests would be needed. This one seems pretty easy to do.

Collaborator

mitar commented Oct 4, 2016

BTW, this is waiting for tests/reproducible example for the changedAt issue.

The second change I like and I think we should add it to Blaze, but I will maybe add it with _isContentEqual, but also tests would be needed. This one seems pretty easy to do.

@mitar

This comment has been minimized.

Show comment
Hide comment
@mitar

mitar Nov 15, 2016

Collaborator

So I think this should be split into two pull requests. One for moving _isContentEqual, and one for data. And we need tests for both, to have a case when that code is match at all.

Collaborator

mitar commented Nov 15, 2016

So I think this should be split into two pull requests. One for moving _isContentEqual, and one for data. And we need tests for both, to have a case when that code is match at all.

@mitar

This comment has been minimized.

Show comment
Hide comment
@mitar

mitar Dec 30, 2016

Collaborator

I cleaned master branch to remove all Meteor history (#112). This means the pull request should be also updated to new history to not base it on old commits. Sorry for this extra work.

Collaborator

mitar commented Dec 30, 2016

I cleaned master branch to remove all Meteor history (#112). This means the pull request should be also updated to new history to not base it on old commits. Sorry for this extra work.

mitar added a commit that referenced this pull request Dec 31, 2016

@mitar

This comment has been minimized.

Show comment
Hide comment
@mitar

mitar Dec 31, 2016

Collaborator

Based on this pull request, I made a change in e17991a, which prevents unnecessary materialization of DOM. This is what I have able to make a reproduction myself. For the other part, checking a change in data layer I have not been able to make a reproduction.

I am closing this pull request. If we find a good reproduction for that other case, we could look into optimizing that as well.

For more ideas how to optimize Blaze see #111.

Collaborator

mitar commented Dec 31, 2016

Based on this pull request, I made a change in e17991a, which prevents unnecessary materialization of DOM. This is what I have able to make a reproduction myself. For the other part, checking a change in data layer I have not been able to make a reproduction.

I am closing this pull request. If we find a good reproduction for that other case, we could look into optimizing that as well.

For more ideas how to optimize Blaze see #111.

@mitar mitar closed this Dec 31, 2016

@arggh

This comment has been minimized.

Show comment
Hide comment
@arggh

arggh Dec 31, 2016

@mitar Did you benchmark this change on any of your test cases?

arggh commented Dec 31, 2016

@mitar Did you benchmark this change on any of your test cases?

@mitar

This comment has been minimized.

Show comment
Hide comment
@mitar

mitar Dec 31, 2016

Collaborator

Sadly not, I do not have any real case where this is happening. I am relaying on the report in this pull request. Feel free to make a test case/benchmark and help here.

Collaborator

mitar commented Dec 31, 2016

Sadly not, I do not have any real case where this is happening. I am relaying on the report in this pull request. Feel free to make a test case/benchmark and help here.

@arggh

This comment has been minimized.

Show comment
Hide comment
@arggh

arggh Dec 31, 2016

I might have something useful in one of my projects for a benchmark.

arggh commented Dec 31, 2016

I might have something useful in one of my projects for a benchmark.

@ramezrafla

This comment has been minimized.

Show comment
Hide comment
@ramezrafla

ramezrafla Dec 31, 2016

Beautiful @mitar! Thanks!

@arggh, we caught this as we outputted to the console every re-render. This can improve performance dramatically by skipping DOM manipulation if we have a very dynamic page. I am guessing this change is going to be very noticeable for a lot of people. Look forward to your benchmark.

There is also room for improvement in the actual diffing but that's a bigger job.

Beautiful @mitar! Thanks!

@arggh, we caught this as we outputted to the console every re-render. This can improve performance dramatically by skipping DOM manipulation if we have a very dynamic page. I am guessing this change is going to be very noticeable for a lot of people. Look forward to your benchmark.

There is also room for improvement in the actual diffing but that's a bigger job.

@mitar

This comment has been minimized.

Show comment
Hide comment
@mitar

mitar Jan 1, 2017

Collaborator

One more thing. I was unable to get to not c.firstRun for anything else besides primitives for which Blaze._isContentEqual is good at checking equality. If anyone can get there with something which Blaze._isContentEqual is not comparing well, we might want to upgrade Blaze._isContentEqual to something which checks values in depth.

Collaborator

mitar commented Jan 1, 2017

One more thing. I was unable to get to not c.firstRun for anything else besides primitives for which Blaze._isContentEqual is good at checking equality. If anyone can get there with something which Blaze._isContentEqual is not comparing well, we might want to upgrade Blaze._isContentEqual to something which checks values in depth.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment