For some use cases, there is a performance gain to be had by statically caching the output of views_arg_load(), which I've implemented in the attached patch.

As an example, The National Interest has 17 menu links on the frontpage which point to views. 5 are in the main navigation (Security, Society, Economics, Politics, Global Governance). The remainder are in the footer of the website, under the 'Explore by Topic' and 'Explore by Region' headers. I would also like you to note that the menu items under the 'Explore by Topic' header are identical to the 5 in the main navigation.

In this instance, views_arg_load() is called 17 times, of which 5 of the calls are repeating work done earlier.

I've attached two screenshots from webgrind showing the performance gain by statically caching the output of this function. You will note that the total inclusive time spent in views_arg_load() drops from 29.06% to 22.84% of the total pageload time.

Not shown in the attached screenshots but also relevant is that the time spent in $view->unpack_object() is reduced from 79.47% inclusive time to 71.43% inclusive.

I confirmed these numbers with several profiling runs before and after the patch, and the numbers are fairly consistent, give or take .5% between all the runs I did.

Comments

brianV’s picture

Oops - attached is a revision without the excess whitespace.

locomo’s picture

subscribe

drewish’s picture

subscribing.

longwave’s picture

Status: Needs review » Reviewed & tested by the community

Patch works for me and similarly reduces time spent in views_arg_load() and unpack_options(). Also applies to 6.x-2.x with minor fuzz.

merlinofchaos’s picture

Status: Reviewed & tested by the community » Needs review

I like this patch.

The method of generating the cache key looks like it could lead to false positives because value and index look like they could accidentally come together to form numbers.

I know it is *highly* unlikely to happen, but that's what edge cases are for.

I recommend:

$key = $name . ':' . $display_id . ':' . $value . ':' . $index;

Then use $key in all the places the calculation is used.

The only place that : is a valid value is $value so you shouldn't get any weird combinations that can masquerade as other combinations.

longwave’s picture

StatusFileSize
new1.54 KB

The $key change is included in this updated patch, plus a correction for the function comment block.

merlinofchaos’s picture

Status: Needs review » Reviewed & tested by the community

Definitely RTBC now. Sadly out of gas for committing for the day, will try to get this one in my next burst.

merlinofchaos’s picture

Status: Reviewed & tested by the community » Fixed

Committed to all branches.

Status: Fixed » Closed (fixed)

Automatically closed -- issue fixed for 2 weeks with no activity.

hanoii’s picture

Version: 6.x-3.x-dev » 6.x-2.x-dev
Component: Miscellaneous » Code
Status: Closed (fixed) » Needs review

I was profiling a site on my own and while going through different points of where a lot of time was spent on views_arg_load().

Let me explain first my scneario:

It's an ecommerce site with a somehow big menu structure populated by taxonomy using taxonomy_menu, and because each of those taxonomy/term/xxx that gets into the menu a views_arg_load is executed.

By applying this patch, the keys I get are as follows:

taxonomy_term:page:108:2
taxonomy_term:page:105:2
taxonomy_term:page:114:2
taxonomy_term:page:115:2
taxonomy_term:page:119:2
taxonomy_term:page:117:2
taxonomy_term:page:120:2
taxonomy_term:page:116:2
taxonomy_term:page:118:2
taxonomy_term_all:page:105:2

And a lot more of course. views_arg_load it's being executed 180 times collecting around 3.x seconds of execution time which is a lot.

This patch does nothing to me, because the key are too specific and for each of this menu the key is new so no real cache is being made. Also, although view::load() caches the views, views_get_view only clones an empty view, so on my case, each time the function is execution the same view and same display is initialized every time by:

    $view->set_display($display_id);
    $view->init_handlers();

which in my case it doesn't make much sense, because the display and the view is exactly the same, so why set the display and initialize it on each run?

I have changed the key to

$key = $name . ':' . $display_id;

And the performance boost is incredible, it was reduced to 88ms.

So, although I have worked a little bit through views entrails and also with some external modules, I am not sure of the implications of this, but for the logic that's around the loaded views on this specific function, I think a wider key makes a lot of sense.

Even the following key would do it:

$key = $name;

Because the set_display() will change it, but I think it's worth to cache each display on it's own.

EDIT: I even don't think this wider key would affect the cache in the way you were thinking it at the first point.

Thoughts?

I don't mind rolling a patch but it's really one line change, and while we are there, can we please commit this into 2.x as well, this is huge!

Thanks,
a.=

hanoii’s picture

Version: 6.x-2.x-dev » 6.x-3.x-dev

I guess I leave the discussion for 3.x first, but really, it's cheap to commit to 2.x at the same time.

dawehner’s picture

Status: Needs review » Needs work

Can you please provide a real patch?
This is not really usuable here.

hanoii’s picture

Status: Needs work » Needs review
StatusFileSize
new754 bytes

Ok, sorry, being a one line I though it was easy to review. Attached is the patch for 3.x, applies the same to 2.x. I didn't realize the first patch was actually committed to the 2.x,

EDIT: Also bear with me if this is not an appropriate patch. Kind of new to git, only followed the instructions on the create patch page.

brianV’s picture

Status: Needs review » Needs work

I think $value needs to remain in the key, since the value that is stored in $views[$key] is dependent on $value:

if (isset($indexes[$index])) {
  if (isset($view->argument[$indexes[$index]])) {
    $arg = $view->argument[$indexes[$index]]->validate_argument($value) ? $value : FALSE;
    $view->destroy();

    // Store the output in case we load this same menu item again.
    $views[$key] = $arg;
    return $arg;
  }
}
hanoii’s picture

You are right, but then we are in the same scenario, I didn't realize that I was doing something wrong, I don't really want to cache the argument value, what I want to cache is the views already initialized and with the display set, actually you can cache both things really. Will submit and improved patch in a short while.

brianV’s picture

Ah, I see where you are going with this. Not just caching function output, but also the initialized views. Gotcha - that would be a nice improvement.

hanoii’s picture

Status: Needs work » Needs review
StatusFileSize
new2.75 KB

Ok, attached is the improved patch. both things are cached.

With this approach, I realize I had to remove $views->destroy() in order for the cached initialized view makes sense. I wonder if there are implications with that but I really think not.

Of course I lost a few milliseconds as the previous patch was super fast because it was wrong, but still in the order of the 150ms, which is still a lot of performance gain.

hanoii’s picture

Probably an easier patch to review, just with git diff, I realized git format-patch includes every single commit as separate diffs, is that the proper way of submitting patches now?

brianV’s picture

Only see one potential issue:

+++ b/views.module
@@ -340,15 +340,31 @@ function views_menu_alter(&$callbacks) {
+  if ($view) {
     $view->set_display($display_id);
     $view->init_handlers();

Is it necessary to set the display and init the handlers again if that is done prior to the view getting placed in the cache?

Why the time we reach this point in the code, this has been done in all possible cases.

hanoii’s picture

I lost the changes I did on the first time and I left those there the second time by accident, of course, you are right.

brianV’s picture

Well, looks good to my eyes, but I'll let someone more knowledgeable about views internals set it to RTBC.

I think this would actually decrease performance in some scenarios, however. The use case you presented is an ideal scenario for this tweak, but on the flip side, consider one where the menu contains 20 menu links each pointing to a unique view / display combination. In that use case, all this patch does is increase memory usage (by holding 20 initialized views in memory since $view->destroy() is not called) without any corresponding speed increase.

So the real question is, in the typical use case, does the performance benefit from this justify the extra resource usage in worst-case type scenarios.

hanoii’s picture

I understand what you are saying, we could always make this optional by a configuration, but probably not worth adding complexity into it.

Not sure what to say, it definitely made an impact on my use case, if I were to chose between memory consumption and page load, I would go with the latter, although I know eventually one will hit the other in the but I still think this might be useful to have it.

hanoii’s picture

I give this a thought while I was out, and if this becomes and issue or a stopper to maybe accepting this, there might be a way in hook_menu_alter() to decide whether to cache the views or not, depending on how many times an argument for a specific view/display will be loaded, I believe this might be known there, but not 100% sure.

dawehner’s picture

Status: Needs review » Needs work
+++ b/views.moduleundefined
@@ -340,18 +340,31 @@ function views_menu_alter(&$callbacks) {
+  $key_views = $name . ':' . $display_id;

Short question, why do we store the same view, but for different displays?

The rest looks fine from my perspective! Great patch

Powered by Dreditor.

merlinofchaos’s picture

dereine: I think becuase args vary based upon things like the path in the display, so each view + display creates a unique combination.

dawehner’s picture

Status: Needs work » Needs review

Oh yes i was wrong, but i'm still wondering whether it would be possible to store just a single view for multiple displays.

donquixote’s picture

[slightly off track]
In an ideal world, we would not load the view, but just do what is minimally required to do the access check, or to load the argument.
This could either be via existing loaders like user_load() or taxonomy_term_load(), or new dedicated loaders like views_taxonomy_term_load(), OR
by loading just a small part of the view - not as much as would be necessary to actually render it.

This would be a lot more work, I'm afraid..

There might still be cases where it is required to load the complete view, if we want to show a "page not found" if the view is empty.
We can still load the view in views_access(), if it turns out to be necessary.
But otherwise, we could probably get away cheaper than that.
[/slightly off track]

mustanggb’s picture

Issue summary: View changes
Status: Needs review » Closed (won't fix)