Spin-off from #438570: Field API conversion leads to lots of expensive function calls (comments #52 and below)
Attached patch:
- introduces hook_field_formatter(), instead of previously theme_field_formatter_*() functions. The hook returns a render array. Basic formatters just return some '#markup' = $output, while more advanced formatters can still go through a theme call if ant to provide ease of overridability.
Better performance (less theme calls), and also makes formatters and widgets much more alike.
- does not remove hook_field_sanitize(), there will be use cases for that. Only, this is not where formatters sanitize their data anymore (the hook was lame at that task anyway, since its a field hook, not a formatter hook)
- Side gain: streamlines field_default_view() and the overall structure of the render array returned by field_attach_view() / field_default_view() - see the PHPdoc of f_a_view().
- while breaking formatter APIs, rename hook_field_formatter_prepare_view() to hook_field_formatter_prepare(). For formatters, 'view' is implied.
- changes existing core formatters to the new API.
Still a couple documentation @todos, but posting what I have so far for bot and peer review.
| Comment | File | Size | Author |
|---|---|---|---|
| #45 | field_formatter_render-652834-45.patch | 52 KB | yched |
| #41 | ab-formatters-39a.txt | 3.1 KB | effulgentsia |
| #41 | ab-formatters-39b.txt | 3.43 KB | effulgentsia |
| #41 | ab-formatters-39c.txt | 3.14 KB | effulgentsia |
| #39 | field_formatter_render-652834-39.patch | 50.6 KB | effulgentsia |
Comments
Comment #1
yched commentedNotes about the last item: "changes existing core formatters to the new API"
- @quicksketch: the choices I made for image and file field formatters could use an approbation from you, regarding "which formatters are simple enough to return a simple #markup value / which formatters deserve to go through a theme function". Probably not a blocker, though.
- @bangpound/quicksketch: I left a remark in a @todo note in taxonomy_field_formatter(), needs your feedback.
Comment #2
yched commentedComment #5
yched commented"Invalid PHP syntax in locale.field.inc."
No way.
Rerolled.
Comment #6
effulgentsia commentedSee #538164-127: Comment body as field: at least for comment body as field, it's not promising yet, but let's keep exploring.
Comment #7
yched commenteddoh ? degradation ?
Hmmm. I could use confirmation on a frontpage with 10 nodes (see #438570-60: Field API conversion leads to lots of expensive function calls)
Comment #9
effulgentsia commenteddevel_generate is failing to add node body, so i manually created 10 article nodes (title = "Article #", body = "Body").
HEAD: 99.5ms
Patch 5: 98.2ms
Comment #10
catchSubscribing.
Comment #11
effulgentsia commentedAnd for anyone else wanting to help with benchmarks, here's the fix to devel_generate: #652898: Small fix so nodes can be generated with field data
Comment #12
effulgentsia commentedStatus change was unintentional.
Comment #14
yched commentedSlave #32 insists to report a PHP syntax error in locale.field.inc that I cannot see with eyes wide open.
Rerolled, re-upload, hoping I'll get a different slave ?
Comment #16
yched commentedHeh, embarrassing that DamZ had to put my nose on it... no function calls inside empty()...
New patch.
Additionally: #538164-127: Comment body as field reported fairly disappointing benches for this on 'comment body as field' (slight *degradation* ?), while an earlier proof-of-concept patch showed encouraging results in #438570-60: Field API conversion leads to lots of expensive function calls.
So sorry to ask for a confirmation benchmark...
Test scenario:
1) 10 article nodes with just a title and a body - devel's 'generate' feature should be just fine, just be sure not to add taxo terms to the created nodes.
2) disable page cache
3) view the frontpage once to fill in field cache (to benchmark the 'display' side more accurately)
4) benchmark 500 page views of the front page as an anonymous user
5) apply patch
6) devel 'clear cache'
7) repeat steps 3) and 4)
Comment #17
effulgentsia commented@yched: Not the exact sequence you outlined, but #9 has my system's benchmarks for home page with 10 article nodes.
Comment #19
yched commentedAttached patch fixes some but not all failures yet.
Leaving to CNR for peer review of the code, though.
Comment #21
yched commenteddoh, forgot the .txt extension so that bot leaves it to CNR.
Comment #22
yched commentedDries left us til tomorrow to get this straight.
I'm working on the last test failures, but I could use some help with code reviews and benchmarks...
Comment #23
yched commentedStrange thing in forum tests where the taxonomy formatter is called without field_attach_prepare_view() being called (raises warnings), while it doesn't happen in HEAD right now. Beats me for now, so I added temporary workaround code to handle this case in the taxonomy_field_formatter().
+ Fixed the other fails / warnings.
I won't be able to push this further today, so reviews + benches (see #16) really welcome.
Comment #25
yched commentedLast exception, similar reason - taxonomy formatter is called without field_attach_prepare_view() being called.
Trying to figure out why current HEAD doesn't raise those, but this is an occurrence of the more general "single node_build() call doesn't trigger field_attach_prepare_view()" (#493314: Add multiple hook for formatters)
Comment #26
yched commentedGot it. When adding a comment to a node, comment_reply() does a node_build(), but the render array is never actually rendered, because we are redirected to the node page - I'm not sure why that is, BTW, buidling a node to ditch the result sounds like CPU waste. Separate issue, though, created #654854: Clean up conditions in comment_reply(), avoid node_view() when unnecessary..
So:
- In HEAD, the formatter (theme function for an element in the array) is not executed since the array is not rendered, and hook_formatter_prepare_view() not having run has no actual consequence.
- with patch, the formatter (code returning a render array) is executed when building the node, and thus bumps on hook_formatter_prepare_view() not having run beforehands.
So the workarounds in the patch (let the formatter load the missing bits) are correct for the current situation, and "node_build() doesn't trigger field_attach_prepare_view()" should be tackled in #493314: Add multiple hook for formatters - or #636992: Entity loading needs protection from infinite recursion, which touches the area.
Comment #27
yched commentedAs I said in the OP, patch still has a couple documentation @todos, but is ready for review.
Comment #28
sunLooks pretty cool.
However, I don't really understand why it's called 'formatter'. A hook_field_format(), or better yet, hook_field_view() would make much more sense to me. I guess that back then when this was introduced, CCK wanted to be extra cool and figured that, hey, all the other shit is old shit and we have quite a lot new shit, so let's call it "formatter", because "viewing" stuff is lame and in general associated with old-school shit, and we are absolutely new-school shit, so we don't view, no, we implement auto-formatting robots that are totally different than viewing a field, errr, are they? Anyway, who cares, this is cool.
Probably a separate issue. ;) If you fill out the @todos, then we're probably set. :)
Comment #29
sunerrrrr. Now I even have to realize that it would have to be
hook_field_build()andhook_field_build_multiple()in D7, because that's the common name for such functions. Ah well, it all takes some time to get used to. ;)Comment #30
catchGenerated content with devel, each just has a body field, hit the front page with ten of them.
I could only measure around a 2% improvement. However if that equates to a ten percent improvement with 5 fields per-node instead of one, then that's really good to have. 5 fields isn't many at all.
HEAD:
Patch:
Comment #31
catchTried a single node and manually added a bunch of different fields for it, then stuck some dummy content in. Two term fields, two images, 4 text fields etc.
In this case it actually comes out slower though... There are ten less calls to theme, and one less call to drupal_render() in the patched version according to cachegrind.
HEAD
Patch:
Comment #32
moshe weitzman commented@catch - there are fewer calls to theme() with the patch and still it came out slower? can you tell why patched is slower? this is a bit disheartening.
@sun - formatter deserves a new word, IMO. It refers to a function that transforms raw database values for viewing, and can be shared across different fields. modules can declare the availability of these functions and admins choose which they want to use on the Display fields tab or whatever it is called.
Comment #33
catch@moshe, no I didn't get very far working out why it's slower. I just repeated the benchmarks with ten nodes on front page and it came out slightly slower consistently. effulgentsia also got degradation testing an earlier version of the patch at http://drupal.org/node/538164#comment-2346970 - however it'd be great to get benchmarks by a third person to verify this.
Comment #34
yched commentedre @sun #28 - "formatters".
- No 1-1 relationship between field types and formatters. Right now, hook_field_*() are field-type hooks, hook_field_widget_*() are widget hooks... Thus hook_field_formatter_*()
- Agreed that a new word might be more accurate given the approach in the patch where a 'formatter' just builds a field for viewing. Having a specific word however was just not for hype, it reflected the fact that it's a pluggable component, next to (and independant from) the notions of 'field types', 'widgets', (and in D7 'storage backends'). We need an unambiguous terminology (preferably single word). 'widget' has long been controversial too, BTW, but no better term emerged.
- In Views terminology (and if Field API was OO), it would be a 'build handler' ? 'Formatter' has years of usage, has the advantage of being one single word, and makes more sense than other, more technically accurate terminologies.
More generally, I don't think it's the appropriate timing to change terminology now :-(. I vote for the status quo, and rethinking names in D8 when (hopefully) moving to a OO architecture.
re @catch: many thanks for the benches.
#30 is consistent with moshe's early benches in #438570-60: Field API conversion leads to lots of expensive function calls. Performance gains should IMO scale more or less linearly with the # of fields on each node or # of nodes in the list.
#31 is indeed confusing, beats me. Profiling data could be helpful, but I won't be able to look into it tonight.
Also, might be worth testing a single node with 10 *text* fields, to check that the slowdown is not caused by my conversion of a specific file or taxonomy formatter.
*Theoretically*, it should show a similar speedup than '10 nodes with one body each' (at least in terms of ms, maybe not in terms of %)
Comment #35
yched commentedfrom the end of #34 above "...check that the slowdown is not caused by my conversion of a specific file or taxonomy formatter"
Regarding the 'taxonomy_term_link' formatter:
see taxonomy_field_formatter() / rdf_field_attach_view_alter() in the patch - this translates the current theme_field_formatter_taxonomy_term_link() and rdf_preprocess_field_formatter_taxonomy_term_link() :append the term RDF data within displayed value, unlike 'regular' RDF properties that are assigned to fields as a whole but do not depend on the actual field values.
The new form using hook_f_a_view_alter() is possibly more CPU costly.
IMO this is a case where we could save CPU if the RDF properties were handled directly in the formatter - besides, the current only works for 'taxonomy_term_link' formatter, but there's no reason other taxo formatters would not benefit from this as well. Different issue I guess.
More generally, I think this is patch is a win if benches prove that the architecture change saves CPU for a given, simple formatter (like text), which are the most common case. Improving the performance of a specific formatter is then a separate task.
Also, possibly we should bench with RDF module disabled ?
Comment #36
effulgentsia commentedNot encouraging. Here's benchmarks with overlay and rdf modules disabled. I added 2 text fields to the page type, made each multi-valued, and put 30 items into each, so the patch saves on 60 formatter theme() calls.
HEAD: 66.7ms
Patch: 68.8ms
Unfortunately, I don't have time today to help investigate this. But, I'd like to point out that a theme() call isn't as expensive as one might think. I don't have up to date microbenchmarks, but from previous testing, it's about 10us on my computer (including the calling of a theme implementation function that's fast, such as theme('links', array('links' => array())). Replacing each of these items that has a #theme of a formatter with an item that has a #markup, means saving a theme() call, but still invoking drupal_pre_render_markup() as a variable function name from within drupal_render(), so it's probably a net savings of roughly 5us per item, or for 60 items, 0.3ms. However, this patch is large and does a lot of other stuff, so it clearly includes something that's causing a degradation that's much larger than this savings.
On the other hand, drupal_render() is slow (roughly 20us even for a non-item such as the 'items' array that's in HEAD, and that this patch removes by placing the numeric items directly inside the field without the extra 'items' layer), so I like that part of what this patch does, and that might be what accounts for a chunk of the savings in the 10 teaser nodes scenario.
By far the biggest slowdown in field rendering, however, is theme('field'). Just removing that from the comment body conversion to a field (#538164-133: Comment body as field) resulted in the patch moving from having a net 6% - 10% impact (depending on system used to benchmark) to having a net -1% - 0.5% impact. theme('field') is slow because field.tpl.php is a template, and because there's quite a bit of preprocessing logic. Seems like we need to focus our time on that, but I can't help with that in time for tomorrow's deadline. On the other hand, improvements to that might not be as sweeping an API change, and could maybe happen after that deadline.
To be honest, I just don't think removing formatter theme functions is gonna result in much savings, but if someone can figure out what in this patch is causing degradation larger than those savings and fix that, then it still might be a good architectural thing to do. But I do think when we put some effort into optimizing theme('field'), then we'll see some nice rendering speed improvements.
Comment #37
yched commentedWell, the proof of concept patch in #438570: Field API conversion leads to lots of expensive function calls didn't include that change, and moshe's initial bench over there for the '/node' case were pretty similar to the one in #30.
These savings should also be seen similarly on the 'node/%nid' case [edit: well, OK, not in the case of your bench, since it used a node with 2 fields instead of 10]
The patch is big because it updates all core formatters, but I can't think of anything in there that would add extra burden on a single node view using text fields.
Sorry to ask but: are we sure the field cache was pre-populated with a preliminary page view when benching both HEAD and the patch ?
Comment #38
effulgentsia commentedYes. For each test, I empty cache, then view the page in Firefox as the root user, and in Safari as an anonymous user, then run "ab" for 100 iterations, and then run "ab" for 500 iterations (that's the one attached in #36). The first 100 iteration run helps any other caches (like APC) get well primed.
It does seem odd that there's an improvement for 10 teasers, but a degradation for a single node with many fields or many field items. Seems worth exploring. However, since there is improvement for 10 teasers, maybe it makes sense to have this land, and then we can find the mysterious trouble spot and optimize that after the deadline.
Sorry I can't be of any more help on this today. Best of luck to you, yched, and anyone else who keeps working on it up until the deadline. I think the basic approach makes sense and is sound, and that remaining performance anomalies are findable and fixable.
Comment #39
effulgentsia commentedThis is a re-roll of #25, plus the following changes:
1. In template_preprocess_field(), NOT changing HEAD's use of strtr() with drupal_html_class(), as #25 does. We probably should make this change, but in a separate issue. Doing it here affects benchmarks.
2. In field_default_view(), calling the formatter function directly instead of with module_invoke(), because module_invoke() is slow.
Will follow up with benchmarks, but wanted to get this into bot's queue.
Comment #40
bleen commentedsuperscribe
Comment #41
effulgentsia commentedBenchmarks with rdf and overlay modules disabled:
A: Home page with 10 article teasers, each article node has a body field:
HEAD: 94.2ms
Patch: 91.9ms
B: Home page with 10 article teasers, each article node has a body field plus 3 single-valued plain-text single-line textfields:
HEAD: 113.5ms
Patch: 111.7ms
C: Single page node with a multi-valued plain-text single-line text field with 40 items populated:
HEAD: 65.5ms
Patch: 65.7ms
Conclusion: This patch doesn't do performance harm. It gives a nice boost to home page with 10 articles, which is a nice scenario to get a boost on. The boost doesn't seem to scale as more fields get added. There seems to be a no-op with just the formatter change alone (page node with 40 items). The no-op in case C makes sense to me. See my comment on #36. We're still invoking the formatter function for as many times as there are items, and as a variable function name. Just instead of doing it from theme(), we're doing it from field_default_view(). I don't see how we gain anything performance-wise from that. There must be something else that's giving us the nice benefit in case A.
So, I think the decision in front of us is whether we think the change to formatters returning render arrays instead of being theme implementations is a good change conceptually. Or does it open the door to further optimization in a way that having it as a theme implementation doesn't? If we think either of those is true, let's do it.
Otherwise, if it's a no-op performance wise, and has no other benefit, let's not do it, and instead try to find what the real cause for case A improvement is, and create a patch that has that, but without changing the formatter architecture.
Comment #42
effulgentsia commentedUnless Dries changed his mind from what he wrote on #438570-62: Field API conversion leads to lots of expensive function calls, the deadline for making this decision is today. I have no strong preference either way (I just haven't thought deeply enough on the architectural implications of formatters as theme functions vs. not). But for anyone with insight on this, today's the day to share it.
Comment #43
effulgentsia commentedSetting to RTBC because sun approved it (except for needing more comments) in #28, and to get it in front of Dries (seems like this is a big and deep enough issue / patch, that he'll need to do his own review and analysis of it).
Comment #44
yched commentedeffulgentsia: *Many* thanks for pushing this.
Performance considerations aside, the architectural change makes sense for the following reasons:
- the 'sanitize displayed data' step can be merged back into hook_field_formatter(), since this is out of reach of the theme layer. Each formatter gets to do the exact amount of data massaging it needs, instead of currently the *field type* module doing some 'reasonable' (= extra careful at best, insufficient at worst) sanitation without knowing what the formatter actually does or need.
- displaying simple stuff like flat markup strings or links without going through theme() is the exact purpose for the introduction of #type 'markup' and 'link'. Most commonly used formatters will be of this kind,
- More generally, D7 is largely "render array + alter hooks" oriented. Was not so true in D6 or back when the Field API initially landed. Making everything themeable just so that it can be modified is an outdated approach.
I'll try to finish the doc @todos later tonight.
Comment #45
yched commented- updated API docs
- reverted the "hook_field_formatter_prepare_view() -> hook_field_formatter_prepare()" change done in earlier iterations. 'view' is a bit redundant when dealing with formatters, but #636992: Entity loading needs protection from infinite recursion is making 'prepare_view' a little more official, so it's probably better to stick to that.
- introduced a small change in the arguments for hook_field_formatter_prepare_view(): takes $display settings instead of a $build_mode pointing to display settings within the $instance definition. This will be needed by #612894: field_format() is just a pile of code that doesn't work (allow one-off field display using custom display settings - needed for Views and Panels). The patch over there is not fully ready yet, but making the API change here is harmless and lets us break APIs only once.
Additionally, hook_field_sanitize() is now useless in its current form for 'sanitize field values' (this is now in the hands of formatters), no current core field type uses it. Such a hook still has value at the field-type level, for instance for 'do not display values the current user has no permissions to see' (e.g nodereference), but
- it would be more accurate and consistent if renamed hook_field_prepare_view() : 'prepare_view' at field-type level.
- it would be more efficient as a 'multiple object' hook (like other 'prepare_view' stuff)
Not done in this patch, feedback on this welcome from Dries (and catch).
Comment #46
yched commentedoops, crossposts with #43.
Comment #47
yched commentedJust thought of this: we might also gain a couple cycles by calling the formatter hooks on the whole values array and have them return a render array keyed by value delta, instead of currently doing
This could even possibly let us get rid of the distinction between 'single value' formatters and 'multiple value formatters'.
Comment #48
effulgentsia commentedRe: #47: +1 from me. Also, please re-read #29 and #32. If we simplify formatters in the way you're suggesting, it really starts looking pretty indistinguishable from other *_build() functions.
Comment #49
sun+1 from me as well. Removing the distinction between single value formatters and multiple value formatters would be awesome for DX. I recall how I got stuck understanding that bit in CCK when it got introduced.
Comment #50
Anonymous (not verified) commentedsubscribe. i see it's RTBC, but i need to read it all anyway.
Comment #51
catchHas anything changed since #41 which requires new benchmarks?
Comment #52
dries commentedGreat. Let's do this -- it is a nice win for the main page. Committed to CVS HEAD.
Comment #53
yched commented"Has anything changed since #41 which requires new benchmarks?"
No. I'll try to add the change mentioned in #47 later today. Then new benchmarks could be worth. But patch #45 is ready as is.
Comment #54
catch#47 sounds good but let's do a new issue for that.
Comment #55
yched commentedAgreed - #53 crossposted with Dries commit message. Yay !
Comment #56
yched commentedPatch with the followup adjustments described in #47: #657828: Make hook_field_formatter() act on all field values. API break too, so we might want to be quick :-/
Comment #57
yched commentedLast followup (see the end of #47): #658302: repurpose hook_field_sanitize() into hook_field_prepare_view()
Comment #58
effulgentsia commentedIn response to sun's #28/#29 comments: #658364: Does build/view/formatter terminology make sense?