Federico Cargnelutti / Wiki

ZendController Benchmarks

From Federico Cargnelutti

Jump to: navigation, search

Contents

Introduction

The responsibility for application performance occurs both at design time and at run time. From a design point of view, when performance begins to fall below the stated minimum performance requirements, it means that you have reached the limit of the application’s scalability, and therefore it will perform poorly.

The Problem

Zend_Controller_Router_Rewrite is the standard framework router. It takes a URI endpoint and decomposes it into parameters to determine which module, controller, and action of that controller should receive the request. Routing occurs only once, when the request is initially received and before the first controller is dispatched.

To use the router you have to instantiate it, add some user defined routes and inject it into the controller:

$routes['blog'] = new Zend_Controller_Router_Route(
	'blog/:year/:month/:day/:title/*',
	array(
		'year'       => date('Y'),
		'month'      => date('m'),
		'day'        => date('d'),
		'module'     => 'blog',
		'controller' => 'index',
		'action'     => 'archive'
	),
	array(
		'year'  => '\d+',
		'month' => '\d+',
		'day'   => '\d+',
		'title' => '([0-9a-zA-Z-]+)',
	)
);
 
$frontController = Zend_Controller_Front::getInstance();
$router = $frontController->getRouter();
$router->addRoutes($routes);
$frontController->dispatch();

Front Controller dispatch mechanism:

o-- Zend_Controller_Front::dispatch()
--> Zend_Controller_Front::setResponse()
--> Zend_Controller_Front::getRouter()
--> Zend_Controller_Router_Rewrite::setParams()
--> Zend_Controller_Router_Rewrite::route()

Zend_Controller_Front gets an instance of Zend_Controller_Router_Rewrite, routes the request, iterates through all the provided routes and matches its definitions to the current request URI.

The problem with this is that the router has to load all the objects into memory, iterate through all the routes and perform regular expressions until it finds a positive match. This means that the more routes you add, the longer the router will take to process them. This problem becomes critical when using XML or INI configuration files, and a site increases in complexity and attracts more traffic.

The Solution

One of the solutions is to split the routes file into different .php or .ini files. For example, instead of loading a single file:

$frontController = Zend_Controller_Front::getInstance(); 
$frontController->addModuleDirectory('../application/modules'); 
$config = new Zend_Config_Ini('../application/config/routes/config.ini', 'production');
$router = $frontController->getRouter();
$router->addConfig($config, 'routes');

You can use the MyRouter class to set the path to the directory where all the configuration files are located:

$frontController = Zend_Controller_Front::getInstance(); 
$frontController->addModuleDirectory('../application/modules'); 
$router = $frontController->getRouter();
$router->setRoutesDirectory('../application/config/routes', array('production', 'routes'));

This will allow the router to map the first segment of the URL path to a filename. For example, given the following URI:

http://www.zend-test.com/blog/2008/07/14/test

The router will load the blog.ini file:

application
|-- bootstrap.php
|-- config
|   `-- routes
|       |-- blog.ini
|       |-- eshop.ini
|       |-- forum.ini
|       `-- gallery.ini
|-- layouts
|   `-- main.phtml
`-- modules
    |-- blog
        |-- controllers
        |   `-- IndexController.php
        |-- models
        `-- views

Testing Environment

Hardware

  • Intel Xeon @ 1.86GHz, 2GB RAM

Operating System

  • Red Hat 4.1.2

Software

  • Apache 2.2.8
  • ApacheBench 2.0.40
  • Zend Framework 1.5.2

Test configuration

Red Hat

127.0.0.1 localhost
127.0.0.1 www.zend-test.com zend-test.com

Apache Web Server

<VirtualHost *>
        ServerName www.zend-test.com
        ServerAlias zend-test.com
        DocumentRoot /home/zend-test/public
        <Directory /home/zend-test/public>
                Options Indexes FollowSymLinks MultiViews
                AllowOverride None
                Order allow,deny
                allow from all
        </Directory>
       RewriteEngine on
       RewriteRule !\.(js|ico|gif|jpg|png|css|html)$ /index.php
</VirtualHost>

ApacheBench

fede@dev:~$ ab -kc 80 -t 60 http://www.zend-test.com/blog/2008/07/14/test

80 connections opened, using Keep-Alive, requesting http://www.zend-test.com/ for 60 seconds through those connections.

Test result data

Zend_Controller_Router_Rewrite (4 routes)

Server Hostname:         www.zend-test.com
Document Path:           /blog/2008/07/14/test
Requests per second 1:   88.24 [#/sec]
Requests per second 2:   89.30 [#/sec]

Zend_Controller_Router_Rewrite (16 routes)

Server Hostname:         www.zend-test.com
Document Path:           /blog/2008/07/14/test
Requests per second 1:   64.45 [#/sec]
Requests per second 2:   64.61 [#/sec]

MyRouter (4 routes)

Server Hostname:         www.zend-test.com
Document Path:           /blog/2008/07/14/test
Requests per second 1:   89.47 [#/sec]
Requests per second 2:   89.32 [#/sec]

MyRouter (40 routes)

Server Hostname:         www.zend-test.com
Document Path:           /blog/2008/07/14/test
Requests per second 1:   89.62 [#/sec]
Requests per second 2:   89.41 [#/sec]

Classes

My_Controller_Router_Rewrite

class My_Controller_Router_Rewrite extends Zend_Controller_Router_Rewrite
{
    /**
     * Routes directory
     * @var string
     */
    protected $_routesDirectory;
 
    /**
     * Set the path to the directory containing the routes
     *
     * @param string $path Path to the routes directory
     * @param array|null $sections Section name(s)
     * @return Zend_Controller_Router_Route_Interface Route object
     */
    public function setRoutesDirectory($path, $sections = null) 
    {
        $request = $this->getFrontController()->getRequest();
        if (null === $request) {
            // Instantiate default request object (HTTP version)
            require_once 'Zend/Controller/Request/Http.php';
            $request = new Zend_Controller_Request_Http();
            $this->getFrontController()->setRequest($request);
        }
 
        $filename = ltrim((string) $request->getRequestUri(), '/');
        if (strpos($filename, '/')) {
            $filename = substr($filename, 0, strpos($filename, '/'));
        }
 
        if ($filename !== '') {
            $path = rtrim((string) $path, '/\\');
            $this->_routesDirectory = $path;
            $this->addRoutesFromFile($path, $filename, $sections);
        }
 
        return $this;
    }
 
    /**
     * Return the path to the routes directory.
     *
     * @return null|string
     */
    public function getRoutesDirectory() 
    {
        return $this->_routesDirectory;
    }
 
 
    /**
     * Add routes from file
     * 
     * @param string $path
     * @param string $filename
     * @param array|null $sections
     * @return Zend_Controller_Router_Interface Rewrite object
     * @throws Zend_Controller_Router_Exception
     */
    public function addRoutesFromFile($path, $filename, $sections = null) 
    {
        $extension = 'ini';
        if (null === $sections) {
            $extension = 'php';
        }
        $file = $path . DIRECTORY_SEPARATOR . $filename . '.' . $extension;
        if (! file_exists($file)) {
            throw new Zend_Controller_Router_Exception('No such file or directory: ' . $file);
        }
 
        if ($extension === 'php') {
            include_once $file;
            if (isset($routes) && is_array($routes)) {
                $this->addRoutes($routes);
            }
        } else if ($extension === 'ini') {
            $fileSection = (array_key_exists(0, $sections)) ? $sections[0] : null;
            $propSection = (array_key_exists(1, $sections)) ? $sections[1] : 'routes';
            /** Zend_Config_Ini **/
            require_once 'Zend/Config/Ini.php';
            $config = new Zend_Config_Ini($file, $fileSection);
            $this->addConfig($config, $propSection);
        }
 
        return $this;
    }
}

Bootstrapper

/* Report all errors directly to the screen for simple diagnostics in the dev environment */  
error_reporting(E_ALL | E_STRICT);  
ini_set('display_startup_errors', 1);  
ini_set('display_errors', 1); 
date_default_timezone_set('Europe/London');
 
/* Add the Zend Framework library to the include path so that we can access the ZF classes */ 
set_include_path('../library' . PATH_SEPARATOR . get_include_path());  
 
/* Set up autoload so we don't have to explicitly require each Zend Framework class */ 
require_once "Zend/Loader.php"; 
Zend_Loader::registerAutoload(); 
 
/* Set the singleton instance of the front controller */ 
require_once 'Zend/Controller/Front.php';
$frontController = Zend_Controller_Front::getInstance(); 
$frontController->throwExceptions(true); 
$frontController->addModuleDirectory('../application/modules'); 
 
require_once "My/Controller/Router/Rewrite.php"; 
$router = new My_Controller_Router_Rewrite();
$frontController->setRouter($router);
$router->setRoutesDirectory('../application/config/routes', array('production', 'routes'));
 
/* Dispatch request */ 
$frontController->dispatch();
Personal tools