• 2015-10-04 13:26:00

    Building the Phalcon Blog (i)

    This is the first of a series of posts, describing how we built the Phalcon Blog (and this one of course). The intention is to showcase some of the features of Phalcon and discuss the reasons behind implementing the code in such a way. I will amend this post with the links of the future posts once I post them.

    These series will focus initially on the Phalcon blog (Github) and will then expand on this blog (Github). In the very near future all the features available in this blog will be available in the Phalcon one :)

    As I mentioned in a previous post, Andres and I were not 100% satisfied with Tumblr, the blogging platform that we have used for a few years for the purposes of the Phalcon blog. So we decided that it would not only be beneficial for us to build something of our own, but also for the community, since the software is open sourced and available for everyone to use.

    Bootstrapping process

    In this post I am going to concentrate on bootstrapping the application. By bootstrapping I do not mean using the Bootstrap open source library, despite the probably misleading image on the right.

    Bootstrapping is the class (in our case) that handles pretty much everything that our application needs to run prior to executing actions. This entails

    • Conditional execution between the normal app and the CLI one
    • Application Paths
    • Configuration Files
    • Loader (and composer autoloader) setup
    • Error handling
    • Routes
    • Dispatcher
    • Url
    • Views
      • Main View
      • Simple View (for emails, RSS/Sitemap etc.)
    • Cache
    • Utils
    • Post Finder class

    Some of the above components are also registered in the DI container for further use in the application.

    Implementation

    In other applications we have open sourced such as Vokuro, we have pretty much always included a couple of files in our index.php; one for the loader and one for the services as demonstrated here.

    <?php
    
    error_reporting(E_ALL);
    
    try {
        /**
         * Define some useful constants
         */
        define('BASE_DIR', dirname(__DIR__));
        define('APP_DIR', BASE_DIR . '/app');
        /**
         * Read the configuration
         */
        $config = include APP_DIR . '/config/config.php';
        /**
         * Read auto-loader
         */
        include APP_DIR . '/config/loader.php';
        /**
         * Read services
         */
        include APP_DIR . '/config/services.php';
        /**
         * Handle the request
         */
        $application = new \Phalcon\Mvc\Application($di);
        echo $application->handle()->getContent();
    } catch (Exception $e) {
        echo $e->getMessage(), '<br>';
        echo nl2br(htmlentities($e->getTraceAsString()));
    }
    

    There is nothing wrong with the above approach. We did however consider the fact that the particular index.php file has 3 different file inclusions and if we wanted to tinker with the setup of the application we would have to open all three.

    We opted for one file containing all of our services and application bootstrap. In addition to that, we altered the design so that later on we can add a CLI application without much effort and heavy refactoring.

    NOTE: The CLI application has been implemented on this blog and will very soon be merged to the Phalcon repository. We will cover that functionality in a future post.

    index.php

    Having one file that performs all the necessary initialization tasks a.k.a. bootstrapping our application allows us to have a much smaller index.php file. (comments removed to preserve space)

    <?php
    
    use \Phalcon\Di\FactoryDefault as PhDI;
    use \Kitsune\Bootstrap;
    
    error_reporting(E_ALL);
    
    try {
        require_once '../library/Kitsune/Bootstrap.php';
    
        $di = new PhDI();
        $bootstrap = new Bootstrap();
    
        echo $bootstrap->run($di, []);
    } catch (\Exception $e) {
        if ($di->has('logger')) {
            $logger = $di->getShared('logger');
            $logger->error($e->getMessage());
            $logger->error('<pre>' . $e->getTraceAsString() . '</pre>');
        }
    }
    

    We create a new bootstrap application and pass in it a DI container. For this part of the application the FactoryDefault DI container is used. However we will be able to inject a Phalcon CLI DI container for the CLI application we will discuss later on.

    Bootstrap.php

    Our bootstrap class contains all the code we need to run the application. It is a bit shy of 400 lines which according to PHP Mess Detector is not something we want to be doing because it increases complexity and if we are not careful it will create a mess :). We opted to ignore that rule and left the file as is because once we had everything working as we wanted, we were not going to be messing with that file again.

    Constants

    We use several constants throughout the application.

    • K_PATH - the top folder path of our installation
    • K_CLI - whether this is a CLI application or not
    • K_DEBUG - whether we are in debug/development mode. In this mode all volt templates are being created at every request and cache is not used.
    • K_TESTS - whether we are running the test suite or not (test suite is not implemented yet)
            /**
             * The app path
             */
            if (!defined('K_PATH')) {
                define('K_PATH', dirname(dirname(dirname(__FILE__))));
            }
    
            ....
    
            /**
             * Check if this is a CLI app or not
             */
            $cli   = $utils->fetch($options, 'cli', false);
            if (!defined('K_CLI')) {
                define('K_CLI', $cli);
            }
    
            $tests = $utils->fetch($options, 'tests', false);
            if (!defined('K_TESTS')) {
                define('K_TESTS', $tests);
            }
    
            ....
    
            /**
             * Check if we are in debug/dev mode
             */
            if (!defined('K_DEBUG')) {
                $debugMode = boolval($utils->fetch($config, 'debugMode', false));
                define('K_DEBUG', $debugMode);
            }
    
    Configuration

    The configuration is split into two files. The git tracked base.php (under /var/config/) contains an array of elements that are needed throughout the application, such as cache settings, routes etc. The config.php located in the same folder is installation dependent and is not tracked in git. You can override every element that exists in base.php.

            /**
             * The configuration is split into two different files. The first one
             * is the base configuration. The second one is machine/installation
             * specific.
             */
            if (!file_exists(K_PATH . '/var/config/base.php')) {
                throw new \Exception('Base configuration files are missing');
            }
    
            if (!file_exists(K_PATH . '/var/config/config.php')) {
                throw new \Exception('Configuration files are missing');
            }
    
            /**
             * Get the config files and merge them
             */
            $base     = require(K_PATH . '/var/config/base.php');
            $specific = require(K_PATH . '/var/config/config.php');
            $combined = array_replace_recursive($base, $specific);
    
            $config = new Config($combined);
            $di->set('config', $config, true);
    
    Loader

    The loader uses the namespaces defined in the base.php and config.php. Additionally the composer autoloader is included to offer functionality needed from the composer components we have.

            /**
             * We're a registering a set of directories taken from the
             * configuration file
             */
            $loader = new Loader();
            $loader->registerNamespaces($config->namespaces->toArray());
            $loader->register();
    
            require K_PATH . '/vendor/autoload.php';
    
    Logger

    The logger is set to create a log file every day (with the date as the prefix).

            /**
             * LOGGER
             *
             * The essential logging service
             */
            $format    = '[%date%][%type%] %message%';
            $name      = K_PATH . '/var/log/' . date('Y-m-d') . '-kitsune.log';
            $logger    = new LoggerFile($name);
            $formatter = new LoggerFormatter($format);
            $logger->setFormatter($formatter);
            $di->set('logger', $logger, true);
    
    Error handler

    We decided to have no errors thrown in the application even if those are E_NOTICE. A simple isset() in most cases is more than enough to ensure that there are no E_NOTICE errors thrown in our log. Any errors thrown in the log files slow our application down, even if the errors are suppressed using the php.ini directives. We also set the timezone to US/Eastern in that file. That particular piece could become configurable and stored in the config.php. Finally we specify a custom error handler, to offer verbosity in errors thrown as well as log metrics when in debug mode.

            /**
             * ERROR HANDLING
             */
            ini_set('display_errors', boolval(K_DEBUG));
    
            error_reporting(E_ALL);
    
            set_error_handler(
                function ($exception) use ($logger) {
                    if ($exception instanceof \Exception) {
                        $logger->error($exception->__toString());
                    } else {
                        $logger->error(json_encode(debug_backtrace()));
                    }
                }
            );
    
            set_exception_handler(
                function (\Exception $exception) use ($logger) {
                    $logger->error($exception->getMessage());
                }
            );
    
            register_shutdown_function(
                function () use ($logger, $memoryUsage, $currentTime) {
                    $memoryUsed = number_format(
                        (memory_get_usage() - $memoryUsage) / 1024,
                        3
                    );
                    $executionTime = number_format(
                        (microtime(true) - $currentTime),
                        4
                    );
                    if (K_DEBUG) {
                        $logger->info(
                            'Shutdown completed [Memory: ' . $memoryUsed . 'Kb] ' .
                            '[Execution: ' . $executionTime .']'
                        );
                    }
                }
            );
    
            $timezone = $config->get('app_timezone', 'US/Eastern');
            date_default_timezone_set($timezone);
    
    Routes

    Our routes are stored in the base.php. Additional routes can be set in the config.php. The router is not initialized if this is a CLI application.

            /**
             * Routes
             */
            if (!K_CLI) {
                $di->set(
                    'router',
                    function () use ($config) {
                        $router = new Router(false);
                        $router->removeExtraSlashes(true);
                        $routes = $config->routes->toArray();
                        foreach ($routes as $pattern => $options) {
                            $router->add($pattern, $options);
                        }
    
                        return $router;
                    },
                    true
                );
            }
    
    Dispatcher

    The dispatcher is instantiated with a listener, attaching to the beforeException event of the dispatcher. A custom plugin NotFoundPlugin is used to send output to the 404 page. Using the plugin allows us to reuse it anywhere in the application. This implementation is very beneficial when developing multi module applications.

    NOTE: For the CLI application later on, we will need the CLI dispatcher.

            /**
             * We register the events manager
             */
            $di->set(
                'dispatcher',
                function () use ($di) {
                    $eventsManager = new EventsManager;
    
                    /**
                     * Handle exceptions and not-found exceptions using NotFoundPlugin
                     */
                    $eventsManager->attach('dispatch:beforeException', new NotFoundPlugin);
    
                    $dispatcher = new Dispatcher;
                    $dispatcher->setEventsManager($eventsManager);
    
                    $dispatcher->setDefaultNamespace('Kitsune\Controllers');
    
                    return $dispatcher;
                }
            );
    
    Views

    The views are being initialized using Volt as the template engine. The main view is set up with the expected options.

            $di->set(
                'view',
                function () use ($config) {
                    $view = new View();
                    $view->setViewsDir(K_PATH . '/app/views/');
                    $view->registerEngines([".volt" => 'volt']);
                    return $view;
                }
            );
    
            /**
             * Setting up volt
             */
            $di->set(
                'volt',
                function ($view, $di) {
                    $volt = new VoltEngine($view, $di);
                    $volt->setOptions(
                        [
                            "compiledPath"  => K_PATH . '/var/cache/volt/',
                            'stat'          => K_DEBUG,
                            'compileAlways' => K_DEBUG,
                        ]
                    );
                    return $volt;
                },
                true
            );
    
    Cache

    The cache component is configured using the config.php. We can define the parameters in that file and thus use say the File cache for our local/development machine and a more advanced cache (Memcached for instance) for the production system.

            /**
             * Cache
             */
            $frontConfig = $config->cache_data->front->toArray();
            $backConfig  = $config->cache_data->back->toArray();
            $class       = '\Phalcon\Cache\Frontend\\' . $frontConfig['adapter'];
            $frontCache  = new $class($frontConfig['params']);
            $class       = '\Phalcon\Cache\Backend\\' . $backConfig['adapter'];
            $cache       = new $class($frontCache, $backConfig['params']);
            $di->set('cache', $cache, true);
    
            /**
             * viewCache
             */
            $frontConfig = $config->cache_view->front->toArray();
            $backConfig  = $config->cache_view->back->toArray();
            $class       = '\Phalcon\Cache\Frontend\\' . $frontConfig['adapter'];
            $frontCache  = new $class($frontConfig['params']);
            $class       = '\Phalcon\Cache\Backend\\' . $backConfig['adapter'];
            $cacheView   = new $class($frontCache, $backConfig['params']);
            $di->set('viewCache', $cacheView, true);
    
    Markdown Renderer

    We use Ciconia for the rendering of markdown with several plugins, existing and user defined. The registration is pretty straight forward.

            /**
             * Markdown renderer
             */
            $di->set(
                'markdown',
                function () {
                    $ciconia = new Ciconia();
                    $ciconia->addExtension(new FencedCodeBlockExtension());
                    $ciconia->addExtension(new TaskListExtension());
                    $ciconia->addExtension(new InlineStyleExtension());
                    $ciconia->addExtension(new WhiteSpaceExtension());
                    $ciconia->addExtension(new TableExtension());
                    $ciconia->addExtension(new UrlAutoLinkExtension());
                    $ciconia->addExtension(new MentionExtension());
    
                    $extension = new IssueExtension();
                    $extension->setIssueUrl(
                        '[#%s](https://github.com/phalcon/cphalcon/issues/%s)'
                    );
                    $ciconia->addExtension($extension);
    
                    $extension = new PullRequestExtension();
                    $extension->setIssueUrl(
                        '[#%s](https://github.com/phalcon/cphalcon/pull/%s)'
                    );
                    $ciconia->addExtension($extension);
                    return $ciconia;
                },
                true
            );
    
    Posts Finder

    This is a class we came up with, which is used to give us an easy way to get information about a specific post, the tag cloud, the index page etc. It is utilizing cache a lot!

            /**
             * Posts Finder
             */
            $di->set(
                'finder',
                function () use ($utils, $cache) {
                    $key        = 'post.finder.cache';
                    $postFinder = $utils->cacheGet($key);
                    if (null === $postFinder) {
                        $postFinder = new PostFinder();
                        $cache->save($key, $postFinder);
                    }
                    return $postFinder;
                },
                true
            );
    

    Conclusion

    In the next post of these series we will take a look at the router and discuss what each route means to our application.

    Comments are more than welcome. If you have any questions on the implementation, feel free to ask in the comments below.

    References

  • 2015-10-02 22:42:39

    Every microsecond counts!

    Preface

    One of the primary factors that always needs to be taken into consideration when designing and implementing an application is performance. In this day and age of information overload, everything is about speed. If your website is slow (more than a second or two) to appear on the browser, most likely your visitors will leave and go elsewhere. If the application you designed/implemented is slow, it will use more resources (memory/cpu) and thus cost more money to run. Time is money.

    The Nanosecond

    I have the highest admiration for Grace Hopper, a pioneer computer scientist who invented the first compiler for a computer language and paved the way for the evolution of programming languages in general. In a video she describes the value of a nanosecond in computer terms. I encourage you to click the link and watch it, it might give you a better perspective on why your application must be as fast as possible.

    This Blog

    As I wrote in a previous post, a new version of this blog has been launched, based on work done for for the Phalcon blog. While in development mode, metrics that are printed in the logger. The output looks something like this:

    [Fri, 02 Oct 15 20:10:27 -0400][INFO] Shutdown completed [2.798s] - [4,134.16 KB] 
    [Fri, 02 Oct 15 20:11:01 -0400][INFO] Shutdown completed [2.979s] - [4,134.00 KB] 
    [Fri, 02 Oct 15 20:14:43 -0400][INFO] Shutdown completed [2.891s] - [4,142.60 KB] 
    [Fri, 02 Oct 15 20:26:10 -0400][INFO] Shutdown completed [2.721s] - [1,075.02 KB] 
    [Fri, 02 Oct 15 20:30:16 -0400][INFO] Shutdown completed [2.735s] - [1,002.25 KB] 
    [Fri, 02 Oct 15 20:30:29 -0400][INFO] Shutdown completed [2.708s] - [1,002.29 KB] 
    [Fri, 02 Oct 15 20:54:04 -0400][INFO] Shutdown completed [2.674s] - [1,003.43 KB] 
    [Fri, 02 Oct 15 20:55:28 -0400][INFO] Shutdown completed [2.677s] - [1,003.31 KB] 
    [Fri, 02 Oct 15 21:12:33 -0400][INFO] Shutdown completed [2.013s] - [913.81 KB] 
    [Fri, 02 Oct 15 21:14:11 -0400][INFO] Shutdown completed [2.002s] - [895.35 KB] 
    [Fri, 02 Oct 15 21:32:48 -0400][INFO] Shutdown completed [2.054s] - [894.71 KB] 
    [Fri, 02 Oct 15 21:39:02 -0400][INFO] Shutdown completed [2.028s] - [894.04 KB]
    [Fri, 02 Oct 15 21:44:19 -0400][INFO] Shutdown completed [2.046s] - [895.59 KB]
    [Fri, 02 Oct 15 21:45:55 -0400][INFO] Shutdown completed [2.023s] - [893.75 KB]
    

    As you can see there is room for improvement. Granted these results come from my local installation, where the debugMode is set to 1, which means that there is no caching and everything gets recalculated on every request. Still, if I can make this local installation perform as fast as possible, then on the production server it will be even faster.

    The first few lines show a relatively OK response (2.7-3.0 seconds) but a high usage in memory. This had to be rectified and looking at the code, I managed to refactor the PostFinder class and reduce the memory consumption significantly. Removing objects and referenced objects in them made a huge difference. Arrays work just fine for my purposes.

    Additional optimizations led to dropping the execution time to just above 2.0 seconds and the memory consumption below 1Mb.

    There are still a lot of things I can try (and will) both on the application level as well as the server level. I am aiming to reduce the execution time to below 1 second and I will for sure share the results and the tools/techniques used.

    I hope you enjoy the performance increase (however noticeable). More to come in the near future.

    References

  • 2011-11-22 12:00:00

    Fast serialization of data in PHP

    Serializing/Unserializing data

    Serialization is the process of converting a data structure or object state into a format that can be stored and "resurrected" later in the same or another computer environment. source

    There are a lot of areas where one can use serialization. A couple are:

    • in a database (storing an array of options specific to the user), 
    • in an AJAX enabled application (call to get a status update and display to the user without refreshing the whole page), etc.

    Based on the the application, serializing and unserializing data can be a very intensive process and can prove to have a big performance hit on the overall system.

    Options

    The most obvious option for serializing and unserializing data are the serialize and unserialize PHP functions. A bit less popular are json_encode and json_decode. There is also a third option, using a third party module that one can easily install on their server. This module is called igbinary.

    In this blog post I am comparing the three options, in the hope that it will aid you with your selection of the best option for you so as to increase the performance of your application.

    I created a test script that used several arrays of data (strings, integers, floats, booleans, objects, mixed data, all of the data types) to test the speed and size of the serialization and speed of unserialization of each of the three candidate function pairs. I run the same function to serialize or unserialize the data respectively for 1,000,000 times so as to produce the results below.

    The script that I have used is listed below:

    $_testStrings = array(
        'AK' => 'Alaska',   'AZ' => 'Arizona', 'VT' => 'Vermont',
        'VA' => 'Virginia', 'AZ' => 'West Virginia',
    );
    
    $_testIntegers = array(0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 84, 144,);
    
    $_testBooleans = array(TRUE, TRUE, FALSE, FALSE, FALSE, TRUE, TRUE,);
    
    $_testFloats = array(
        0, 1.1, 1.1, 2.22, 3.33, 5.55, 8.88, 13.13, 21.2121, 34.3434, 
        55.5555, 84.8484, 144.144,
    );
    
    $_testMixed = array(
        'one', 13 => 'two', 0 => 25.46, 'four' => 0.007, 
        'five' => TRUE, TRUE => 42,
    );
    
    $_objectOne            = new stdClass();
    $_objectOne->firstname  = 'Leroy';
    $_objectOne->lastname   = 'Jenkins';
    $_objectOne->profession = 'Gamer';
    $_objectOne->status     = 'Legend';
    
    $_objectTwo        = new stdClass();
    $_objectTwo->series = 'Fibonacci';
    $_objectTwo->data   = $_testIntegers;
    
    $_testObjects = array($_objectOne, $_objectTwo,);
    
    $_maxLoop = 1000000;
    
    $_templateEncode = "%s [%s]: Size: %s bytes, %s time to encode\r\n";
    $_templateDecode = "%s [%s]: %s time to decode\r\n";
    
    set_time_limit(0);
    
    $_output = '';
    
    /**
     * Set the source arrays
     */
    $_allTestData = array(
        'str' => $_testStrings,
        'int' => $_testIntegers,
        'bln' => $_testBooleans,
        'flt' => $_testFloats,
        'mix' => $_testMixed,
        'obj' => $_testObjects,
    );
    
    $_testSources = array(
        'strings'  => $_testStrings,
        'integers' => $_testIntegers,
        'booleans' => $_testBooleans,
        'floats'   => $_testFloats,
        'mixed'    => $_testMixed,
        'objects'  => $_testObjects,
        'all'      => $_allTestData,
    );
    
    /**
     * ENCODE DATA
     */
    
    /**
     * Start each test
     */
    foreach ($_testSources as $_area => $_source)
    {
        /**
         * Start the timer
         */
        $_serializeStart = microtime(TRUE);
    
        for ($_counter = 0; $_counter < $_maxLoop; $_counter++)
        {
            serialize($_source);
        }
    
        $_serializeEnd = microtime(TRUE);
    
        $_serializeOutput = serialize($_source);
    
        $_output .= sprintf(
            $_templateEncode,
            'serialize()', 
            $_area, 
            strlen($_serializeOutput), 
            $_serializeEnd - $_serializeStart
        );
    
        /**
         * JSON
         */
        $_jsonStart = microtime(TRUE);
    
        for ($_counter = 0; $_counter < $_maxLoop; $_counter++)
        {
            json_encode($_source);
        }
    
        $_jsonEnd = microtime(TRUE);
    
        $_jsonOutput = json_encode($_source);
    
        $_output .= sprintf(
            $_templateEncode,
            'json_encode()', 
            $_area, 
            strlen($_jsonOutput), 
            $_jsonEnd - $_jsonStart
        );
    
        /**
         * igbinary
         */
        $_igbinaryStart = microtime(TRUE);
    
        for ($_counter = 0; $_counter < $_maxLoop; $_counter++)
        {
            igbinary_serialize($_source);
        }
    
        $_igbinaryEnd = microtime(TRUE);
    
        $_igbinaryOutput = igbinary_serialize($_source);
    
        $_output .= sprintf(
            $_templateEncode,
            'igbinary_serialize()', 
            $_area, 
            strlen($_igbinaryOutput), 
            $_igbinaryEnd - $_igbinaryStart
        );
    
        $_output .= str_repeat('=', 20) . "\r\n";
    }
    
    $_output .= str_repeat('=:=', 20) . "\r\n";
    
    
    /**
     * DECODE DATA
     */
    
    /**
     * Start each test
     */
    foreach ($_testSources as $_area => $_source)
    {
        /**
         * Start the timer
         */
        $_data = serialize($_source);
    
        $_serializeStart = microtime(TRUE);
    
        for ($_counter = 0; $_counter < $_maxLoop; $_counter++)
        {
            unserialize($_data);
        }
    
        $_serializeEnd = microtime(TRUE);
    
        $_output .= sprintf(
            $_templateDecode,
            'unserialize()', 
            $_area, 
            $_serializeEnd - $_serializeStart
        );
    
        /**
         * JSON
         */
        $_data = json_encode($_source);
    
        $_jsonStart = microtime(TRUE);
    
        for ($_counter = 0; $_counter < $_maxLoop; $_counter++)
        {
            json_decode($_data, TRUE);
        }
    
        $_jsonEnd = microtime(TRUE);
    
        $_jsonOutput = json_encode($_source);
    
        $_output .= sprintf(
            $_templateDecode,
            'json_decode()', 
            $_area, 
            $_jsonEnd - $_jsonStart
        );
    
        /**
         * igbinary
         */
        $_data = igbinary_serialize($_source);
    
        $_igbinaryStart = microtime(TRUE);
    
        for ($_counter = 0; $_counter < $_maxLoop; $_counter++)
        {
            igbinary_unserialize($_data);
        }
    
        $_igbinaryEnd = microtime(TRUE);
    
        $_igbinaryOutput = igbinary_serialize($_source);
    
        $_output .= sprintf(
            $_templateDecode,
            'igbinary_unserialize()', 
            $_area, 
            $_igbinaryEnd - $_igbinaryStart
        );
    
        $_output .= str_repeat('=', 20) . "\r\n";
    }
    
    echo '' . $_output . '</pre>';
    

    Serializing results

    When serializing data we are always concerned about the size of the result but also about the time it took for the data to be serialized.

    As far as size is concerned, json_encode seems to be producing the smallest result in bytes for most of the tests.

    Size comparison

    Strings

    serialize() [strings]: Size: 105 bytes, 1.8710339069366 time to encode
    json_encode() [strings]: Size: 67 bytes, 1.5691390037537 time to encode
    igbinary_serialize() [strings]: Size: 64 bytes, 3.2276048660278 time to encode <==
    

    Integers

    serialize() [integers]: Size: 121 bytes, 3.0198090076447 time to encode
    json_encode() [integers]: Size: 34 bytes, 1.2248229980469 time to encode <==
    igbinary_serialize() [integers]: Size: 58 bytes, 2.2877519130707 time to encode
    

    Booleans

    serialize() [booleans]: Size: 62 bytes, 2.0834550857544 time to encode
    json_encode() [booleans]: Size: 39 bytes, 1.0889070034027 time to encode
    igbinary_serialize() [booleans]: Size: 27 bytes, 1.8252439498901 time to encode <==
    

    Floats

    serialize() [floats]: Size: 709 bytes, 27.496570825577 time to encode
    json_encode() [floats]: Size: 77 bytes, 5.0476500988007 time to encode <==
    igbinary_serialize() [floats]: Size: 142 bytes, 2.4856028556824 time to encode
    

    Mixed

    serialize() [mixed]: Size: 178 bytes, 6.301619052887 time to encode
    json_encode() [mixed]: Size: 54 bytes, 2.0463008880615 time to encode
    igbinary_serialize() [mixed]: Size: 50 bytes, 2.3894169330597 time to encode <==
    

    Objects

    serialize() [objects]: Size: 326 bytes, 4.8698291778564 time to encode
    json_encode() [objects]: Size: 148 bytes, 2.4744520187378 time to encode <==
    igbinary_serialize() [objects]: Size: 177 bytes, 6.472992181778 time to encode
    

    All data types

    serialize() [all]: Size: 1567 bytes, 42.437592029572 time to encode
    json_encode() [all]: Size: 462 bytes, 9.9569129943848 time to encode <==
    igbinary_serialize() [all]: Size: 478 bytes, 18.053789138794 time to encode
    
    Speed comparison

    Analyzing the time it took for each test to be completed, we see again that json_encode is the clear winner (highlighted in bold the shortest time for the function).

    Strings

    serialize() [strings]: Size: 105 bytes, 1.8710339069366 time to encode
    json_encode() [strings]: Size: 67 bytes, 1.5691390037537 time to encode <==
    igbinary_serialize() [strings]: Size: 64 bytes, 3.2276048660278 time to encode
    

    Integers

    serialize() [integers]: Size: 121 bytes, 3.0198090076447 time to encode
    json_encode() [integers]: Size: 34 bytes, 1.2248229980469 time to encode <==
    igbinary_serialize() [integers]: Size: 58 bytes, 2.2877519130707 time to encode
    

    Booleans

    serialize() [booleans]: Size: 62 bytes, 2.0834550857544 time to encode
    json_encode() [booleans]: Size: 39 bytes, 1.0889070034027 time to encode <==
    igbinary_serialize() [booleans]: Size: 27 bytes, 1.8252439498901 time to encode
    

    Floats

    serialize() [floats]: Size: 709 bytes, 27.496570825577 time to encode
    json_encode() [floats]: Size: 77 bytes, 5.0476500988007 time to encode
    igbinary_serialize() [floats]: Size: 142 bytes, 2.4856028556824 time to encode <==
    

    Mixed

    serialize() [mixed]: Size: 178 bytes, 6.301619052887 time to encode
    json_encode() [mixed]: Size: 54 bytes, 2.0463008880615 time to encode <==
    igbinary_serialize() [mixed]: Size: 50 bytes, 2.3894169330597 time to encode
    

    Objects

    serialize() [objects]: Size: 326 bytes, 4.8698291778564 time to encode
    json_encode() [objects]: Size: 148 bytes, 2.4744520187378 time to encode <==
    igbinary_serialize() [objects]: Size: 177 bytes, 6.472992181778 time to encode
    

    All data types

    serialize() [all]: Size: 1567 bytes, 42.437592029572 time to encode
    json_encode() [all]: Size: 462 bytes, 9.9569129943848 time to encode <==
    igbinary_serialize() [all]: Size: 478 bytes, 18.053789138794 time to encode
    

    Combination

    Having the smallest result in size might not always be the best metric to base the choice of the serialization algorithm. For instance, looking at the results above in the Strings test, igbinary produces indeed the smallest result in size (64 bytes) but it takes twice as much to serialize the result in comparison to json_encode (3.22 vs. 1.56 seconds) and the size difference is a mere 3 bytes (64 vs. 67).

    Similarly, for the Boolean test, igbinary produces 27 bytes and json_encode 39 bytes. It does however take igbinary nearly 80% more time to produce the result compared to json_encode.

    For the Floats test the situation is reversed. json_encode produces a result that is around 50% smaller than the one of igbinary but it takes twice as much time to produce it.

    As far as serializing data, in my personal opinion, json_encode is the clear winner.

    Unserializing Results

    Unserializing data is equally - and at times - more important than serializing. In many applications, developers sacrifice performance in writing but don't compromise when reading data.

    In the tests below once can easily see that igbinary is the clear winner. At times the unserialize function is very close (or outperforms igbinary) but overall, igbinary is the the function that unserializes data the fastest.

    Speed comparison

    Strings

    unserialize() [strings]: 1.8259189128876 time to decode <==
    json_decode() [strings]: 2.6482670307159 time to decode
    igbinary_unserialize() [strings]: 1.8359968662262 time to decode
    

    Integers

    unserialize() [integers]: 2.3886890411377 time to decode <==
    json_decode() [integers]: 2.8659090995789 time to decode
    igbinary_unserialize() [integers]: 2.4441809654236 time to decode
    

    Booleans

    unserialize() [booleans]: 1.8097970485687 time to decode
    json_decode() [booleans]: 2.4416139125824 time to decode
    igbinary_unserialize() [booleans]: 1.7585029602051 time to decode <==
    

    Floats

    unserialize() [floats]: 18.512004137039 time to decode
    json_decode() [floats]: 3.7896130084991 time to decode
    igbinary_unserialize() [floats]: 2.6730649471283 time to decode <==
    

    Mixed

    unserialize() [mixed]: 4.6794769763947 time to decode
    json_decode() [mixed]: 2.7775249481201 time to decode
    igbinary_unserialize() [mixed]: 1.9598047733307 time to decode <==
    

    Objects

    unserialize() [objects]: 5.5468521118164 time to decode
    json_decode() [objects]: 5.7660481929779 time to decode
    igbinary_unserialize() [objects]: 5.2672090530396 time to decode <==
    

    All data types

    unserialize() [all]: 31.01339006424 time to decode
    json_decode() [all]: 14.574991941452 time to decode
    igbinary_unserialize() [all]: 10.734386920929 time to decode <==
    

    Conclusion

    If your application is mostly focused on reads rather than writes, igbinary is the clear winner, since it will unserialize your data faster than the other two functions. If however you are more focused on storing data, json_encode is the clear choice.

    Updates

    2013-03-07: memcached was not used with igbinary. PHP version for tests was 5.3.1 on a Linux Mint machine with 6GB RAM 2013-06-14: Reader Dennis has been kind enough to run the same script on his server and share the results with me. He run the scripts on a i7-3930K, 64GB, Debian Squeeze with the latest version of PHP (5.4.16) and igbinary.

    Serialize

    Strings

    serialize() [strings]: Size: 105 bytes, 0.63280701637268 time to encode <== Time
    json_encode() [strings]: Size: 67 bytes, 0.78271317481995 time to encode
    igbinary_serialize() [strings]: Size: 64 bytes, 0.97228002548218 time to encode <== Size
    

    Integers

    serialize() [integers]: Size: 121 bytes, 1.3659980297089 time to encode
    json_encode() [integers]: Size: 34 bytes, 0.46304202079773 time to encode <== Time/Size
    igbinary_serialize() [integers]: Size: 58 bytes, 0.65074491500854 time to encode
    

    Booleans

    serialize() [booleans]: Size: 62 bytes, 0.80747985839844 time to encode
    json_encode() [booleans]: Size: 39 bytes, 0.27534413337708 time to encode <== Time
    igbinary_serialize() [booleans]: Size: 27 bytes, 0.52206611633301 time to encode <== Size
    

    Floats

    serialize() [floats]: Size: 307 bytes, 6.3345258235931 time to encode
    json_encode() [floats]: Size: 77 bytes, 3.3697159290314 time to encode <== Time/Size
    igbinary_serialize() [floats]: Size: 142 bytes, 0.70451712608337 time to encode
    

    Mixed

    serialize() [mixed]: Size: 105 bytes, 1.4573359489441 time to encode
    json_encode() [mixed]: Size: 54 bytes, 0.98674011230469 time to encode
    igbinary_serialize() [mixed]: Size: 50 bytes, 0.71359205245972 time to encode <== Time/Size
    

    Objects

    serialize() [objects]: Size: 326 bytes, 2.4085388183594 time to encode
    json_encode() [objects]: Size: 148 bytes, 1.6553950309753 time to encode <== Time/Size
    igbinary_serialize() [objects]: Size: 177 bytes, 2.1983618736267 time to encode
    

    All

    serialize() [all]: Size: 1092 bytes, 13.614814043045 time to encode
    json_encode() [all]: Size: 462 bytes, 7.7341570854187 time to encode <== Size
    igbinary_serialize() [all]: Size: 478 bytes, 5.6470530033112 time to encode <== Time
    
    Unserialize

    Strings

    unserialize() [strings]: 0.69071316719055 time to decode
    json_decode() [strings]: 1.381010055542 time to decode
    igbinary_unserialize() [strings]: 0.52063202857971 time to decode <==
    

    Integers

    unserialize() [integers]: 1.0607678890228 time to decode
    json_decode() [integers]: 1.4053201675415 time to decode
    igbinary_unserialize() [integers]: 0.70937013626099 time to decode <==
    

    Booleans

    unserialize() [booleans]: 0.65101194381714 time to decode
    json_decode() [booleans]: 1.0951101779938 time to decode
    igbinary_unserialize() [booleans]: 0.49839997291565 time to decode <==
    

    Floats

    unserialize() [floats]: 5.3973641395569 time to decode
    json_decode() [floats]: 2.0127139091492 time to decode
    igbinary_unserialize() [floats]: 0.75269412994385 time to decode <==
    

    Mixed

    unserialize() [mixed]: 1.5048658847809 time to decode
    json_decode() [mixed]: 1.2782678604126 time to decode
    igbinary_unserialize() [mixed]: 0.55352306365967 time to decode <==
    

    Objects

    unserialize() [objects]: 2.6635551452637 time to decode
    json_decode() [objects]: 3.3167290687561 time to decode
    igbinary_unserialize() [objects]: 1.9917018413544 time to decode <==
    

    All

    unserialize() [all]: 11.949328899384 time to decode
    json_decode() [all]: 9.9836950302124 time to decode
    igbinary_unserialize() [all]: 4.4029591083527 time to decode <==