As loading a display plugin is pretty heavy and some views have a lot of views we should make the display plugin
lazy-loading.

What does this mean:

* Replace $view->displayHandlers with an arrayAccess object, so if anything is accessing the data we will load the plugin, initialize
and no other code has to be changed.

CommentFileSizeAuthor
#88 views-1817582-88.patch7.63 KBdawehner
#84 no-patch.png36.1 KBKars-T
#84 patch.png7.22 KBKars-T
#84 vdc.tar_.gz155.78 KBKars-T
#81 vdc-1817582-81.patch16.52 KBtim.plunkett
#78 drupal-1817582-78.patch15.96 KBdawehner
#75 drupal-1817582-75.patch11.53 KBdawehner
#71 drupal-1817582-71.patch15.85 KBdawehner
#69 drupal-1817582-69.patch15.67 KBdawehner
#69 interdiff.txt2.2 KBdawehner
#65 durpal-1817582-65.patch15.2 KBdawehner
#65 interdiff.txt1.05 KBdawehner
#62 1817582-62.patch15.21 KBdamiankloip
#60 drupal-1817582-60.patch15.21 KBdawehner
#60 interdiff.txt1.75 KBdawehner
#58 drupal-1817582-58.patch15.16 KBdawehner
#57 drupal-1817582-57.patch15.16 KBdawehner
#55 drupal-1817582-55.patch13.91 KBdawehner
#51 lazy-load.png25.33 KBKars-T
#48 1817582-46.patch8.33 KBdamiankloip
#44 drpal-1817582-43.patch8.29 KBdawehner
#44 interdiff.txt757 bytesdawehner
#42 drupal-1817582-42.patch8.33 KBdawehner
#42 interdiff.txt605 bytesdawehner
#40 interdiff.txt1.43 KBdawehner
#39 drupal-1817582-39.patch8.32 KBdawehner
#36 drupal-1817582-36.patch8.23 KBdawehner
#34 core-1817582-34.patch8.18 KBdawehner
#31 core-1817582-31.patch8.35 KBdawehner
#28 core-1817582-28.patch8.36 KBdawehner
#25 views-1817582-24.patch8.08 KBdamiankloip
#22 views-1817582-22.patch8.04 KBdamiankloip
#16 views-1817582-16.patch8.04 KBdawehner
#16 interdiff.txt3.48 KBdawehner
#13 views-1817582-13.patch7.93 KBdawehner
#13 interdiff.txt539 bytesdawehner
#12 views-1817582-12.patch8.03 KBtim.plunkett
#12 interdiff.txt3.11 KBtim.plunkett
#10 views-1817582-10.patch7.59 KBdawehner
#10 interdiff.txt2.87 KBdawehner
#8 views-1817582-8.patch5.89 KBdawehner
#8 interdiff.txt2.73 KBdawehner
#5 views-1817582-5.patch5.26 KBdawehner
#2 views-1817582-2.patch5.25 KBdawehner
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

tim.plunkett’s picture

Title: lazyload display plugins » Lazy load display plugins

+1

dawehner’s picture

Status: Active » Needs review
FileSize
5.25 KB

Let's see what the testbot tells us.

dawehner’s picture

I'm wondering whether we actually could backport this change.

Status: Needs review » Needs work

The last submitted patch, views-1817582-2.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
5.26 KB

This one should be better :)

Status: Needs review » Needs work

The last submitted patch, views-1817582-5.patch, failed testing.

tim.plunkett’s picture

It doesn't make sense to me that initializeDisplay has a return value.

public function offsetGet($offset) {
  if (!isset($this->display[$offset])) {
    $this->initializeDisplay($offset);
  }
  return $this->display[$offset];
}

Then initializeDisplay() could also be simplified.

dawehner’s picture

Status: Needs work » Needs review
FileSize
2.73 KB
5.89 KB

So this fixes many issues.

Status: Needs review » Needs work

The last submitted patch, views-1817582-8.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
2.87 KB
7.59 KB

Now finally.

Status: Needs review » Needs work

The last submitted patch, views-1817582-10.patch, failed testing.

tim.plunkett’s picture

Status: Needs work » Needs review
FileSize
3.11 KB
8.03 KB

This is what I had in mind.

dawehner’s picture

FileSize
539 bytes
7.93 KB

Oh i like those changes.

+++ b/lib/Drupal/views/DisplayArray.phpundefined
@@ -2,7 +2,7 @@
- * Definition of Drupal\views\DisplayArray.
+ * Contains Drupal\views\DisplayArray.

The drop is always moving.

+++ b/lib/Drupal/views/DisplayArray.phpundefined
@@ -56,26 +56,23 @@ public function __destruct() {
-      // This assumes that the 'default' handler is always first. It always
-      // is. Make sure of it.

So i think we should initialize the default display in the constructor.

damiankloip’s picture

YESSSSS!

+++ b/lib/Drupal/views/DisplayArray.phpundefined
@@ -0,0 +1,159 @@
+  /**
+   * Stores the actual display instances in an array.
+   *
+   * @var array
+   */
+  protected $display = array();

Would it be better if this was $displays or $display_storage or something instead?

+++ b/lib/Drupal/views/DisplayArray.phpundefined
@@ -0,0 +1,159 @@
+   * Initializes a single display and stores the result in self::display.

Does this make you think of static properties?

+++ b/lib/Drupal/views/DisplayArray.phpundefined
@@ -0,0 +1,159 @@
+    $this->display[$display_id] = views_get_plugin('display', $this->view->storage->display[$display_id]['display_plugin']);
+    $this->display[$display_id]->init($this->view, $this->view->storage->display[$display_id]);

Would be nice if there was a nicer way to get this stuff. Not really a part of this issue though :)

+++ b/lib/Drupal/views/DisplayArray.phpundefined
@@ -0,0 +1,159 @@
+  /**
+   * Implements \Iterator::current().
+   */
+  public function next() {

next()

catch’s picture

This looks great!

dawehner’s picture

FileSize
3.48 KB
8.04 KB

Thanks for the review!

Here is a rerole which fixes the stuff mentioned above.

damiankloip’s picture

Status: Needs review » Reviewed & tested by the community

Looks great, I guess this will pass but you never know... :)

Status: Reviewed & tested by the community » Needs work
Issue tags: -VDC

The last submitted patch, views-1817582-16.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review

#16: views-1817582-16.patch queued for re-testing.

Status: Needs review » Needs work
Issue tags: +VDC

The last submitted patch, views-1817582-16.patch, failed testing.

tim.plunkett’s picture

+++ b/lib/Drupal/views/DisplayArray.phpundefined
@@ -42,31 +42,31 @@
+    foreach ($this->$displayHandlers as $display_id => $display) {

$this->$displayHandlers has an extra $

damiankloip’s picture

Status: Needs work » Needs review
FileSize
8.04 KB

Just to be clear; I only removed a '$' character.

Status: Needs review » Needs work

The last submitted patch, views-1817582-22.patch, failed testing.

tim.plunkett’s picture

+++ b/lib/Drupal/views/DisplayArray.phpundefined
@@ -0,0 +1,159 @@
+    return current($this->display);
...
+    return next($this->display);
...
+    return key($this->display);
...
+    $display_id = key($this->display);
...
+    reset($this->display);

These are all still the old variable

+++ b/lib/Drupal/views/DisplayArray.phpundefined
@@ -0,0 +1,159 @@
+  public function valid() {
+    $display_id = key($this->display);
+    return isset($this->displayHandlers[$display_id]);

This looks weird. Wouldn't you just need to do return key($this->displayHandlers) !== NULL;

damiankloip’s picture

Status: Needs work » Needs review
FileSize
8.08 KB

A few other instances of $this->display weren't changed.

catch’s picture

OK one question though:

+  /**
+   * Implements \Iterator::current().
+   */
+  public function next() {
+    // If we do an actual iteration over the elements, let's initialize all
+    // displays.
+    $this->initializeAllDisplays();
+    return next($this->display);
+  }

When views is figuring out the display for a view, it iterates over the displays until one passes an access check, then returns from within the foreach() - that seems like one of the main places that'd benefit from his but initializing all displays won't help with it.

damiankloip’s picture

We talked about this briefly on IRC; It's a good point. One idea is to store a list of the displays on the class, maybe in the conststructor so we don't have to get them from $this->view->storage->display each time. Not sure if that's a good trade off or not?

Something like :

// ...

  /**
   * Stores an array of display ID's
   *
   * @var array
   */
  protected $displayIds = array();

// ...

  public function __construct(ViewExecutable $view) {
    $this->view = $view;

    $this->displayIds = array_keys($this->view->storage->display);

    $this->initializeDisplay('default');
  }

// ...

Just an idea.

dawehner’s picture

Project: Views (for Drupal 7) » Drupal core
Version: 8.x-3.x-dev » 8.x-dev
Component: Code » views.module
FileSize
8.36 KB

Okay implemented the idea mentioned by damian.
Additional i corrected the implementation of valid(), do you think this is okay now?

tim.plunkett’s picture

+++ b/core/modules/views/lib/Drupal/views/DisplayArray.phpundefined
@@ -0,0 +1,160 @@
+    return $this->offsetGet(    $display_id = next($this->displayIDs);

This is broken copy/paste?

+++ b/core/modules/views/lib/Drupal/views/DisplayArray.phpundefined
@@ -0,0 +1,160 @@
+    return $display_id !== FALSE;

return ($display_id !== NULL && $display_id !== FALSE);

Status: Needs review » Needs work

The last submitted patch, core-1817582-28.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
8.35 KB

OH!

Thank you! It is amazing that this issue already has 31 comments.

Status: Needs review » Needs work

The last submitted patch, core-1817582-31.patch, failed testing.

damiankloip’s picture

+++ b/core/modules/views/lib/Drupal/views/DisplayArray.phpundefined
@@ -0,0 +1,159 @@
+    return $display_id !== NULL && $display_id !== FALSE;

Wont !empty() give you the same result? empty will evaluate TRUE for both NULL and FALSE.

dawehner’s picture

Status: Needs work » Needs review
FileSize
8.18 KB
Wont !empty() give you the same result? empty will evaluate TRUE for both NULL and FALSE.

Well actually $display_id should be named $key here, so it might be 0, which is a reference to 'default', so it's for sure valid.

Status: Needs review » Needs work

The last submitted patch, core-1817582-34.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
8.23 KB

Let's see whether the tests are running now.

Status: Needs review » Needs work

The last submitted patch, drupal-1817582-36.patch, failed testing.

damiankloip’s picture

+++ b/core/modules/views/lib/Drupal/views/DisplayArray.phpundefined
@@ -0,0 +1,156 @@
+    return isset($this->displayHandlers[$offset]) || isset($this->view->storage->display[$offset]);

return isset($this->displayHandlers[$offset]) || in_array($offset, $this->displayIDs); Instead?

+++ b/core/modules/views/lib/Drupal/views/DisplayArray.phpundefined
@@ -0,0 +1,156 @@
+    return count($this->view->storage->display);

count($this->displayIDs) ?

dawehner’s picture

Status: Needs work » Needs review
FileSize
8.32 KB

Thanks for that additions, i implemented it slightly different, so we don't have to in_array but just use isset().

dawehner’s picture

FileSize
1.43 KB

Forgot the interdiff

Status: Needs review » Needs work

The last submitted patch, drupal-1817582-39.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
605 bytes
8.33 KB

The rewind() has been wrong.

damiankloip’s picture

+++ b/core/modules/views/lib/Drupal/views/DisplayArray.phpundefined
@@ -130,7 +130,8 @@
-    return key($this->displayIDs);
+    $key = key($this->displayIDs);
+    return $key;

Change this back, and this is RTBC.

dawehner’s picture

FileSize
757 bytes
8.29 KB

Just remove some temporary variables.

damiankloip’s picture

Status: Needs review » Reviewed & tested by the community

This looks pretty awesome now.

xjm’s picture

Issue tags: -VDC

#44: drpal-1817582-43.patch queued for re-testing.

Status: Reviewed & tested by the community » Needs work
Issue tags: +VDC

The last submitted patch, drpal-1817582-43.patch, failed testing.

damiankloip’s picture

Status: Needs work » Reviewed & tested by the community
FileSize
8.33 KB
catch’s picture

Issue tags: +needs profiling

It'd be good to profile this before it goes in on a view with say 5 or 10 displays, to see how much difference it actually makes.

xjm’s picture

@catch We are actually in the process of doing that already. :)

Kars-T’s picture

FileSize
25.33 KB

Hi

I did benchmark this a lot of times with different settings. The point is there is no real difference. The lazy loading script might be a bit slower. For the test I created a view with > 20 displays that use 14 fields. Than I profiled the call of views_get_view_result(). dawehner told me that we should get a real view that is known for having performance problem. Otherwise I can't show any performance problem or gain.

lazy-load.png

dawehner’s picture

So one problem why we don't see any benefit is that all displays are allowed to attach additional information to existing displays.

This is primarily used at the moment to show the feed icon on a page display.
To be able to do this, we have a foreach loop and so we have lost: http://drupalcode.org/project/drupal.git/blob/refs/heads/8.x:/core/modul...

tim.plunkett’s picture

Status: Reviewed & tested by the community » Needs work

Removing from RTBC until we improve it.

dawehner’s picture

The question is whether we should do it here or in another issue, as this issue is just about actually lazy loading display plugins.

One idea we came up with is to store the displays which want to attach to an existing one.

dawehner’s picture

Status: Needs work » Needs review
FileSize
13.91 KB

This patch actually changes the logic and not initializes all the displays all the time by introducing an helper function.
This one also got a patch.

Status: Needs review » Needs work

The last submitted patch, drupal-1817582-55.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
15.16 KB
dawehner’s picture

Status: Needs review » Needs work
Issue tags: -VDC

The last submitted patch, drupal-1817582-58.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
1.75 KB
15.21 KB

Rerolled against the UI changes.

Status: Needs review » Needs work

The last submitted patch, drupal-1817582-60.patch, failed testing.

damiankloip’s picture

Status: Needs work » Needs review
Issue tags: +VDC
FileSize
15.21 KB

Just fixed the fatal from DisplayPluginBase.

Status: Needs review » Needs work

The last submitted patch, 1817582-62.patch, failed testing.

dawehner’s picture

One current problem of the patch is that now that we have getters/setters, we don't have $display as reference anymore, so
the value on the storage points to another location then on the actual display instance.

dawehner’s picture

Status: Needs work » Needs review
FileSize
1.05 KB
15.2 KB

Oh there is getDisplay nice! Patch, please apply!

Status: Needs review » Needs work
Issue tags: -needs profiling, -VDC

The last submitted patch, durpal-1817582-65.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
Issue tags: +needs profiling, +VDC

#65: durpal-1817582-65.patch queued for re-testing.

damiankloip’s picture

Status: Needs review » Needs work

I think this is perilously close now ...

+++ b/core/modules/views/lib/Drupal/views/DisplayArray.phpundefined
@@ -0,0 +1,159 @@
+    // If the display was initialize before just return.

Maybe 'If the display was initialized before, just return.'?

+++ b/core/modules/views/lib/Drupal/views/DisplayArray.phpundefined
@@ -0,0 +1,159 @@
+    $display = &$this->view->storage->getDisplay($display_id);

It's always slightly annoying that we have to declare by reference on the method itself and the call to it, but hey, that's php :)

+++ b/core/modules/views/lib/Drupal/views/DisplayArray.phpundefined
@@ -0,0 +1,159 @@
+    $this->displayHandlers[$display_id] = views_get_plugin('display', $display['display_plugin']);
+    $this->displayHandlers[$display_id]->init($this->view, $display);

Is there any sense in checking we have a display before continuing here? Not sure.

+++ b/core/modules/views/lib/Drupal/views/ViewExecutable.phpundefined
@@ -2204,7 +2185,7 @@ public function setItemOption($display_id, $type, $id, $option, $value) {
    * @return Drupal\views\Plugin\views\display\DisplayPluginBase
    *   A reference to the new handler object.
    */
-  public function &newDisplay($id) {
+  public function newDisplay($id) {

The doc still says we are returning a reference. Hang on, why do we want to change this again? :)

The attached display change looks good too, I think that is the only way we can do this without actually loading displays.

dawehner’s picture

FileSize
2.2 KB
15.67 KB
Maybe 'If the display was initialized before, just return.'?

Perfect.

Is there any sense in checking we have a display before continuing here? Not sure.

Why not ... let's use the strategy to use a default display plugin so it doesn't break completly.

The doc still says we are returning a reference. Hang on, why do we want to change this again? :)

Updated the doc ... well the thing is, it's an object so you just don't have to specify whether to return a reference or not.

dawehner’s picture

Status: Needs work » Needs review

Update

dawehner’s picture

FileSize
15.85 KB

Synced the @return between the two newDisplay methods.

Status: Needs review » Needs work
Issue tags: -needs profiling, -VDC

The last submitted patch, drupal-1817582-71.patch, failed testing.

damiankloip’s picture

Status: Needs work » Needs review

#71: drupal-1817582-71.patch queued for re-testing.

Status: Needs review » Needs work
Issue tags: +needs profiling, +VDC

The last submitted patch, drupal-1817582-71.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
11.53 KB

Reroll the patch.

Jelle_S’s picture

Had a quick glance over the patch... I think the file containing the DisplayArray class is not included. Testbot will probably confirm, unless I totally missed something :-)

Status: Needs review » Needs work

The last submitted patch, drupal-1817582-75.patch, failed testing.

dawehner’s picture

Status: Needs work » Needs review
FileSize
15.96 KB

Oh you are absolute right, thank you!
used patch -p1 for the rerole, and failed to readd the file.

damiankloip’s picture

Status: Needs review » Reviewed & tested by the community

This looks good now, the attach code and tests look fine too.

catch’s picture

Looks like there hasn't been profiling since #51, could we do that again to see the impact on a view with a few displays?

tim.plunkett’s picture

Status: Reviewed & tested by the community » Needs review
FileSize
16.52 KB

That's a good idea.

Also, rerolled.

dawehner’s picture

Do you know why exactly this new patch got 500 byte bigger?

tim.plunkett’s picture

+++ b/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayTest.phpundefined
+++ b/core/modules/views/lib/Drupal/views/Tests/Plugin/DisplayTest.phpundefined
@@ -19,7 +19,7 @@ class DisplayTest extends PluginTestBase {

@@ -19,7 +19,7 @@ class DisplayTest extends PluginTestBase {
    *
    * @var array
    */
-  public static $testViews = array('test_filter_groups');
+  public static $testViews = array('test_filter_groups', 'test_get_attach_displays');
 
   /**
    * Modules to enable.

Yes, this did it mostly.

+++ b/core/modules/views/lib/Drupal/views/ViewExecutable.phpundefined
@@ -1489,13 +1475,11 @@ public function attachDisplays() {
     $this->is_attachment = TRUE;
-    // Give other displays an opportunity to attach to the view.
-    foreach ($this->displayHandlers as $id => $display) {
-      if (!empty($this->displayHandlers[$id])) {
-        // Create a clone for the attachments to manipulate. 'static' refers to the current class name.
-        $cloned_view = new static($this->storage);
-        $this->displayHandlers[$id]->attachTo($cloned_view, $this->current_display);
-      }
+    // Find out which other displays attach to the current one.
+    foreach ($this->display_handler->getAttachedDisplays() as $id) {
+      // Create a clone for the attachments to manipulate. 'static' refers to the current class name.
+      $cloned_view = new static($this->storage);
+      $this->displayHandlers[$id]->attachTo($cloned_view, $this->current_display);
     }
     $this->is_attachment = FALSE;

And this

Kars-T’s picture

Status: Needs review » Reviewed & tested by the community
FileSize
155.78 KB
7.22 KB
36.1 KB

It is hard to create a view that really shows any difference to get some numbers. I created a really big view with many displays. And if you load it uncached you can find this:

no-patch.png

With the patch added an uncached load shows only this:

patch.png

I'd say it works and only loads the one needed display.

In the .gz you can find the view_from_hell.yml and the xhprof results from the runs.

+1 for the patch.

dawehner’s picture

Issue tags: -needs profiling

Thank you for the profiling!

catch’s picture

Category: feature » task
Status: Reviewed & tested by the community » Fixed
Issue tags: +Performance

OK that's great. Committed/pushed to 8.x.

Wondering if this could ever be backported to 7.x-3.x, but marking 'fixed' for now.

dawehner’s picture

Project: Drupal core » Views (for Drupal 7)
Version: 8.x-dev » 7.x-3.x-dev
Component: views.module » Code
Status: Fixed » Patch (to be ported)

Let's see, would be for sure interesting and maybe worth in terms of performance.

dawehner’s picture

Assigned: dawehner » Unassigned
FileSize
7.63 KB

I really fear it will be impossible to backport this patch without breaking stuff.

But yeah here is a try which fails in many cases like the actual page :)

johnv’s picture

Status: Patch (to be ported) » Needs review

I'm happy to test this with my 10-display view.

johnv’s picture

Status: Needs review » Patch (to be ported)

Back to original status after triggering testbot.

xjm’s picture

Status: Patch (to be ported) » Needs work

NW would be the correct status now that there's a start at a patch. :)

dawehner’s picture

Status: Needs work » Patch (to be ported)

Even if this would be a real start, let's set it to the other status, so it's easier to find.

xjm’s picture

Okay, different rules from core then. ;)

killua99’s picture

Hi,

I'm not 100% sure if this fatal error I have it's related with this issue, but I find this issue cause the qa drupal test. In the test it have the same fatal error.

Fatal error: Call to a member function ensure_my_table() on a non-object in /var/lib/drupaltestbot/sites/default/files/checkout/sites/default/modules/views/handlers/views_handler_filter_combine.inc on line 60

I get this fatal error when I have an exposed filter with combined fields. I'm trying to figure out what cause this error, but it's hard to find.

johnv’s picture

I tried to implement and test this patch on D7. I keep getting "reference to non-object" errors.

In the end, I have the following additions to patch #88

   function destroy() {
     foreach (array_keys($this->display) as $display_id) {
-      if (isset($this->display[$display_id]->handler)) {
+      if ($this->display[$display_id]->handler_inited) {
         $this->display[$display_id]->handler->destroy();
         unset($this->display[$display_id]->handler);
+        $this->display[$display_id]->handler_inited = FALSE;
       }
     }

The variable handle_inited must be reset, too.

+  /**
+   * Overrides the magic __get function to lazy initialize the display handler.
+   */
+  function &__get($name) {
+
+    // Lazy initialize the handler.
+    if ($name == 'handler') {
+//      if ($this->handler_inited) {
+      if (!$this->handler_inited) {
+        $this->handler = views_get_plugin('display', $this->display_plugin);
+        $this->handler_inited = TRUE;

The check must be inversed.

+  function __isset($name) {
+    if ($name == 'handler') {
+      if (!$this->handler_inited) {
+       return FALSE;
+       $foo = 123;
+      }
+      return TRUE;
+    }
+    else {
+      return TRUE;
+    }
+  }

Added the 'return FALSE' line.

Also, function fix_missing_relationships() seems to load all handlers and objects. It is already called when building the menu, not when building the page itself.

boreg’s picture

Status: Patch (to be ported) » Needs review

88: views-1817582-88.patch queued for re-testing.

Status: Needs review » Needs work

The last submitted patch, 88: views-1817582-88.patch, failed testing.

The last submitted patch, 88: views-1817582-88.patch, failed testing.

Chris Charlton’s picture

Issue summary: View changes

Is this abandoned? Or just dormant?