36

I would like to use PHP 7. However there seems to be no option for key-value memory caching.

APC is discontinued.

XCache will not be available for PHP 7 for some time.

Is there an available alternative for PHP 7?

Or is there a way to use PHP7's Opcache to implement key-value memory caching?

jwpfox
  • 5,124
  • 11
  • 45
  • 42
Frodik
  • 14,986
  • 23
  • 90
  • 141
  • 2
    tried with memcached? – Thamilhan May 09 '16 at 06:26
  • @Thamizhan Well, memcached and redis are little bit something else. I am asking for PHP memory cache, which has much less latency than using those two (even on localhost). – Frodik May 09 '16 at 06:27
  • What's the difference in latency? Have you checked it? It doesn't look like a thing that you should worry about. – Jakub Matczak May 09 '16 at 06:34
  • @dragoste There is neglible difference in latency if you are retrieving few values. But this difference grows much bigger if it is tens or hundred of values per request. – Frodik May 09 '16 at 06:36
  • 1
    That's not what I asked about. I know that difference grows with amount of requests to the cache. That's simple math. ;-) But still I've never heard that being an issue at a project on any scale. And still I'm not sure if it's true at all. APC and Redis or memcached work pretty the same in case of key/value storage. Therefore, benchmark or it's a lie. ;-) – Jakub Matczak May 09 '16 at 06:49
  • @dragoste Sure I benchmarked it a lot. Redis-type cache and APC-type cache have latency on different scale. Redit-type is around millisecond when on localhost, APC-type is fraction of millisecond, about 1/10 -1/50 of millisecond depending on systems we had tried. – Frodik May 09 '16 at 07:08
  • 1
    This is a micro optimization and it is not worth it. As it was said, Redis and memcached are the normal ways to go. I would better refactor the code that makes so many requests asking about "hundred of values" so often. – Axalix May 15 '16 at 14:57
  • @Axalix No, it's not micro-optimization. Key-value local-memory PHP based cache has different purpose than server based distributable memory cache. Well, you are right, that it doesn't matter when running Wordpress site ... – Frodik May 15 '16 at 19:43
  • @Frodik It absolutely is a micro-optimisation. If your concern is "how do I save a millisecond of response time", I suggest switching to C. – Matt Prelude May 16 '16 at 12:09
  • @MattPrelude Sorry, but I have to disagree. Yes, you are right that one millisecond doesn't matter. However if you have hundreds of milliseconds, that's totally different story. And I am sure you can do the math and see the difference of using PHP-based local memory cache vs. network-based memcache with all it's roundtrips and latency (which is tiny, but when you do that hundred times a request, it is huge difference). – Frodik May 16 '16 at 12:35
  • 1
    @Frodik If it's hundreds of times per request, is it possible you could handle them in a batched manner? Memcached has setMulti/getMulti functions, so you'd only have the overhead of one network request instead of hundreds. – Matt Prelude May 17 '16 at 11:05
  • @MattPrelude Yes, this is what we are planning as future improvement. – Frodik May 17 '16 at 11:26
  • 2
    Another optimisation you could do for Memcached (or Redis) is to connect through sockets, not TCP/IP if on localhost. You mention you use network-based memcached which should theoretically be slower than socket-base connections as you have to add the networking protocol handling. – Robbie May 18 '16 at 05:25
  • all of the above is worth consideration. That and memcache is currently not supported in PHP7!!! https://bugs.php.net/bug.php?id=16321&edit=1 – eggmatters Dec 19 '16 at 20:59

4 Answers4

39

I'd suggest using Memcached, especially if you're concerned about performance.

Whilst you are correct that APC(u) is a lot faster than Memcache, you're not taking into the account that by the time you're worrying about these metrics, you will be running across multiple servers and APC(u) cannot be shared across nodes.

You can use a single Memcache instance or cluster to serve as many application servers as you want. Scalability is a greater concern in modern application development than "how much performance can I squeeze out of one server?"

Having said that, your alternative is APCu, which has all of the functionality that you're used to from APC. It is marked as stable with PHP7 but I wouldn't recommend this because of its single-node nature & inability to work correctly with fastcgi.

Matt Prelude
  • 912
  • 5
  • 13
  • 1
    Thanks for your answer. Our application is very specialized, we are already using memcache and for the reasons you habe mentioned. However, as I said, we also need to locally cache hundreds of key-value data for which is memcached and alike not very suitable. But I agree with you and the answer will be useful for someone running "ordinary" website. – Frodik May 16 '16 at 12:32
  • 3
    @Frodik In that case, APCu is probably the best you will get (at least without knowing more about your specific usage). It's worth a mention that Memcache has a bunch of [set multi](http://php.net/manual/en/memcached.setmultibykey.php) and [get multi](http://php.net/manual/en/memcached.getmultibykey.php) functions, which may be useful to you if you're setting those hundreds all at once. This would save you the overhead of multiple requests. – Matt Prelude May 16 '16 at 14:04
  • What if you're on Windows? There doesn't appear to be a dll for Memcached on Windows. Does this mean anyone developing using PHP with a memory cache requirement must develop on a unix environment or is there something I'm missing? – Kosi Apr 18 '19 at 15:48
20

APCU is literally APC without the code caching (they took the APC code, removed the byte-code cache and released it as APCU). It's a drop-in replacement. Exactly as APC's user-cache, it keeps the data in the same process as the PHP runtime, and so using the value is like much like retrieving an ordinary variable, hence the speed.

Alister Bulman
  • 34,482
  • 9
  • 71
  • 110
  • Well, yeah, but APC on PHP 5.3 or above was everything but stable :-( So even it might run with PHP 7, I am very afraid it will suffer from unstability as well. – Frodik May 11 '16 at 19:04
  • Fear not, but try it. APCu is all You might need beside native built-in zend opcache, and it is very stable. – Spooky May 15 '16 at 23:26
8

Another approach (which I haven't tried, but sounds very interesting) is to take advantage of the opcache as a key value cache. This graphiq post contains more details but no real code unfortunately (and note the comments from Kerry Schwab).

The gist of it is to ensure that the opcache is enabled and has enough memory allocated for the data that you need to cache and then something along the lines of the following (lifted from the article, check it out in full). Cache expiration (aside from manual deletion) would need to be handled too, but wouldn't be hard to add (e.g. wrap your data in a containing object with an expiry time and check it in your cache_get, deleting and ignoring the record if it's expired).

function cache_set($key, $val) {
   $val = var_export($val, true);
   // HHVM fails at __set_state, so just use object cast for now
   $val = str_replace('stdClass::__set_state', '(object)', $val);
   // Write to temp file first to ensure atomicity
   $tmp = "/tmp/$key." . uniqid('', true) . '.tmp';
   file_put_contents($tmp, '<?php $val = ' . $val . ';', LOCK_EX);
   rename($tmp, "/tmp/$key");
}

function cache_get($key) {
    @include "/tmp/$key";
    return isset($val) ? $val : false;
}

Because of the opcache this functions as an in memory cache, but it avoids the overhead of serialization and deserialization. I guess that the cache_set should also call opcache_invalidate when writing (and in the cache_delete function which doesn't exist in their examples) but otherwise it seems sound for a cache which doesn't need to be shared between servers.

Edit: An example implementation with cache expiry (only accurate to a second, could use microtime(true) if more accuracy is required) below. Minimal testing actually done, and I dropped the HHVM specific replacement, so YMMV. Suggestions for improvements welcome.

class Cache {                                                                   
    private $root;                                                              
    private $compile;                                                           
    private $ttl;                                                               

    public function __construct($options = []) {                                
        $this->options = array_merge(                                           
            array(                                                              
                'root' => sys_get_temp_dir(),                                   
                'ttl'  => false,                                                
            ),                                                                  
            $options                                                            
        );                                                                      
        $this->root = $this->options['root'];                                   
        $this->ttl = $this->options['ttl'];                                     
    }                                                                           

    public function set($key, $val, $ttl = null) {                              
        $ttl = $ttl === null ? $this->ttl : $ttl;                               
        $file = md5($key);                                                      
        $val = var_export(array(                                                
            'expiry' => $ttl ? time() + $ttl : false,                           
            'data' => $val,                                                     
        ), true);                                                               

        // Write to temp file first to ensure atomicity                         
        $tmp = $this->root . '/' . $file . '.' . uniqid('', true) . '.tmp';     
        file_put_contents($tmp, '<?php $val = ' . $val . ';', LOCK_EX);         

        $dest = $this->root . '/' . $file;                                      
        rename($tmp, $dest);                                                    
        opcache_invalidate($dest);                                              
    }                                                                           

    public function get($key) {                                                 
        @include $this->root . '/' . md5($key);                                 

        // Not found                                                            
        if (!isset($val)) return null;                                          

        // Found and not expired                                                
        if (!$val['expiry'] || $val['expiry'] > time()) return $val['data'];    

        // Expired, clean up                                                    
        $this->remove($key);                                                    
    }                                                                           

    public function remove($key) {                                              
        $dest = $this->root . '/' . md5($key);                                  
        if (@unlink($dest)) {                                                   
            // Invalidate cache if successfully written                         
            opcache_invalidate($dest);                                          
        }                                                                       
    }                                                                           
}      
El Yobo
  • 14,823
  • 5
  • 60
  • 78
5

PHP 7 cache / accelerator list

Listo of dead/outdated PHP accelerators: XCache, APC, memoize, ZendOpcache, chdb, hidef (they are not supporting PHP 7)

We can find a list of PHP Accelerators on PECL website, but as i menthion some of them are discontinued or not up to date.

Currently developed (with support for PHP 7.3) are :

You will find all installation instruction in a downloaded tgz/zip file.

APCu WINDOWS USERS: Download APCu and APCu_bc DLL files that matches your system specification x64 (64 bits) OR x86 (32 bits windows) select TS or UTS version and of course the right PHP version. Paste .DLL into your php/ext directory You can determine thread mode by looking at your php directory. Find at DLL file name (for example: php7ts.dll). Notice the TS or UTS in file name.

php -v will show you your current PHP CLI installation version. Just make sure your PHP version is same with the one you are using in your server. If not change update the windows environment path for PHP.

If you have difficulties read this: How to install apcu in windows https://kapilpatel84.wordpress.com/2016/06/15/install-xdebug-apcu-on-windows-xampp-for-php7/

FOR XAMPP:

1) Down load compatible APCu with following link http://pecl.php.net/package/apcu

2) APCu need backword compatabily with APC so you have to download it with following link. http://pecl.php.net/package/apcu_bc

3) Extact DDL file and move DDL file named : php_apc.dll and php_apcu.dll and copy that files to your PHP ext Directory. i.e C:\xampp\php\ext

4) Open php.ini file and copy the following code at the bottom of the file

[apcu]
extension="C:\xampp\php\ext\php_apcu.dll"
apc.enabled=1
apc.shm_size=32M
apc.ttl=7200
apc.enable_cli=1
apc.serializer=php
extension="C:\xampp\php\ext\php_apc.dll"

When you unzip the file. Copy DLL file to your PHP extenstion folder np: .../php/ext. and configure php.ini (conf instruction is included in the INSTALL text file).

Symfony 4

PS. If by any chance you using Symfony framework dont forget to anable APCu in config>packages>cache.yaml

framework:
    cache:
        app: cache.adapter.apcu

Use builtin server with:

php bin/console server:run
DevWL
  • 17,345
  • 6
  • 90
  • 86
  • 2
    OPCache is not dead, it is de-facto standard PHP accelerator and is shipped with every distribution since PHP 5.6 more or less as PHP integral part. Moreover, it's a must-use extension in regards to performance. – Danila Vershinin Aug 14 '19 at 22:16
  • @DanilaVershinin You are right. Thanks for pointing this out. "This extension is bundled with PHP 5.5.0 and later, and is » available in PECL for PHP versions 5.2, 5.3 and 5.4." I missed the "and later" part. – DevWL Aug 16 '19 at 13:35