Home › Forums › Feature Requests › Improve ACF performance – implement a simple caching (an example provided)
Hi,
I’ve noticed a performance issue when getting a value of a repeater field or a gallery field (generally speaking: a field that returns a large array of data).
Example:
if (get_field('gallery')) {
foreach (get_field('gallery') as $image) {
// Do something;
}
}
The reason is simple: get_field('gallery')
runs twice.
There’s a simple workaround:
$gallery = get_field('gallery');
if ($gallery) {
foreach ($gallery as $image) {
// Do something;
}
}
But I don’t like this solution because it requires a global variable. Global variables are considered as a bad practice and I try to avoid them in my templates.
here’s a simple test I run. A single call to get_field('gallery')
field containing about 300 images.
$start = microtime(true);
get_field('gallery');
$end = microtime(true);
var_dump($end - $start);
Ther result was ~3.22s
I’ve increased the number of calls to get_field('gallery')
:
$start = microtime(true);
get_field('gallery');
get_field('gallery');
$end = microtime(true);
var_dump($end - $start);
Ther result was ~6.44s
I also tried with more calls to get_field('gallery')
. Each call to get_field('gallery')
function multiplied the end result by ~2. So when there were 4 calls the result was 12.8s.
So I came up with an idea to cache the values returned by the get_field()
function. The caching class is very simple. I’ve assumed that the result of get_field()
function call is determined only by the arguments passed to the get_fiedl()
function. So if the arguments are the same the result will be the same as well.
Here’s the implementation of the Cache()
class. It’s a singleton, because I need only one instance of this class.
class Cache
{
private static $instance;
private $cachedFields = [];
private function __construct()
{
}
private function __clone()
{
}
public static function getInstance()
{
if (self::$instance === null) {
self::$instance = new Cache();
}
return self::$instance;
}
public function get($serializedArguments)
{
return $this->cachedFields[$serializedArguments];
}
public function exists($serializedArguments)
{
if (!array_key_exists($serializedArguments, $this->cachedFields)) {
return false;
}
return true;
}
public function add($serializedArguments, $fieldValue)
{
$this->cachedFields[$serializedArguments] = $fieldValue;
}
}
and here’s the implementation of the get_cached_field()
function:
function get_cached_field($fieldName, $postId = false, $formatValue = true)
{
// Get an instance of Cache class.
$cache = Cache::getInstance();
// Get valid post ID using acf core function
$validPostId = acf_get_valid_post_id($postId);
// Serialize function arguments. I can't simply 'use serialize(func_get_args())' because of $postId which if not passed as an argument defaults to current $post->ID
$serializedArguments = serialize([$fieldName, $validPostId, $formatValue]);
// Check if the value is already cached and return the cached value if the condition is evaluates to <code>true</code>
if ($cache->exists($serializedArguments)) {
return $cache->get($serializedArguments);
}
// Otherwise use get_field() function to retrieve the vale
$fieldValue = get_field($fieldName, $validPostId, $formatValue);
// Save the value to cache
$cache->add($serializedArguments, $fieldValue);
// And return the value
return $fieldValue;
}
And the benchmark:
$start = microtime(true);
get_cached_field('gallery');
get_cached_field('gallery');
$end = microtime(true);
var_dump($end - $start);
Ther result was ~3.22s
I tried to add more calls to get_cached_field('gallery');
. The result was pretty much the same. No matter how many calls there were. For example, when I increased the number of calls to 25 the result was 3.3s.
I think this kind of optimization could make into ACF core. I can’t see any side effects (correct me if I’m wrong). Of course, I don’t suggest adding a new function, I’d like it to be implemented in the get_field()
function.
PS
I didn’t implement it for the get_sub_field()
function yet, but it shouldn’t be complicated either.
Hi @wube
Thanks for sharing the code.
I’ll pass it to the plugin author so he can check it and implement it if it is suitable.
Thank you very much 🙂
Hi guys
This is a great topic.
Currently, ACF will cache any value loaded from the DB, but after loading, the $value is sent to another function that formats it based on the field type.
When dealing with the repeater / gallery field, this results in a lot of extra logic / DB calls.
I have caching setup for the loading of values, but not for the formatting of values.
I wonder if we adding caching to the format function, this will fix the issue.
The following assumes you are using ACF PRO (or ACF v5 in the future):
Open the file api/api-value.php and finc the acf_get_value function. Below this function is the acf_format_value function. Note that the format function contains no caching, but the load function does.
Try changing the format function to this:
/*
* acf_format_value
*
* This function will format the value for front end use
*
* @type function
* @date 3/07/2014
* @since 5.0.0
*
* @param $value (mixed)
* @param $post_id (mixed)
* @param $field (array)
* @return $value
*/
function acf_format_value( $value, $post_id, $field ) {
// try cache
$found = false;
$cache = wp_cache_get( "format_value/post_id={$post_id}/name={$field['name']}", 'acf', false, $found );
if( $found ) return $cache;
// apply filters
$value = apply_filters( "acf/format_value", $value, $post_id, $field );
$value = apply_filters( "acf/format_value/type={$field['type']}", $value, $post_id, $field );
$value = apply_filters( "acf/format_value/name={$field['name']}", $value, $post_id, $field );
$value = apply_filters( "acf/format_value/key={$field['key']}", $value, $post_id, $field );
// update cache
wp_cache_set( "format_value/post_id={$post_id}/name={$field['name']}", $value, 'acf' );
// return
return $value;
}
Does it work? I’ll do some testing too, but would be great to hear back from you @wube
Hey guys
I’ve done some testing and can confirm this cache addition to the acf_format_value function solves this issue completely!
I’ve tested using the above double get_field, and the output microtime is halved!
I’ll include this in the next version
Hi @elliot,
Sorry for not responding to your previous message. I didn’t notice your post.
It’s a great news! Thank you for implementing this feature in the ACF core. Your solution is much simpler than mine. I’ve completely forgotten about the native wp_cache and tried to re-invent the wheel introducing my own caching class 🙂
Hi guys
This simple caching idea has been added into recent versions of ACF PRO!
The topic ‘Improve ACF performance – implement a simple caching (an example provided)’ is closed to new replies.
Welcome to the Advanced Custom Fields community forum.
Browse through ideas, snippets of code, questions and answers between fellow ACF users
Helping others is a great way to earn karma, gain badges and help ACF development!
We use cookies to offer you a better browsing experience, analyze site traffic and personalize content. Read about how we use cookies and how you can control them in our Privacy Policy. If you continue to use this site, you consent to our use of cookies.