egregius.be

Urban Exploration, PHP and others…

Speeding up things: Cache variables

I can say that I am quite addicted to speed, especially when it comes to websites and web applications. Speed is everything.
I tent to search and squeeze to the last millisecond.
Sometimes we assume that packages that are taken for granted just are the fastest, but are they really?

Let’s do some tests and compare storing and retreiving times of simple variables.

The test is executed on a Synology DS1513+ with 4GB Ram running DSM 6.0.1-7393 Update 1 on 5 standard disks in SHR volume. The MySQL server used is the standard Synology MariaDB package. Memcached is the package from synocommunity.

As you can already tell the test will compare the times between a file based variable cache, a MySQL database cache and MemCached.
The code used for the test is:

<?php 
echo 'runs;file;mysql;memcache<br/>';
for($y=1;$y<=200;$y++){
	$runs=$y;
	$f=-microtime(true);
	for($x=1;$x<=$runs;$x++){
		fset($x,$x);
		fget($x);
	}
	$f+=microtime(true);
	$s=-microtime(true);
	for($x=1;$x<=$runs;$x++){
		sset($x,$x);
		sget($x);
	}
	$s+=microtime(true);
	$c=-microtime(true);
	for($x=1;$x<=$runs;$x++){
		cset($x,$x);
		cget($x);
	}
	$c+=microtime(true);
	echo $runs.';'.$f*1000 .';'.$s*1000 .';'.$c*1000 .'<br/>';
}
function fset($key,$value){file_put_contents('/volume1/web/logs/'.$key.'.cache',$value);}
function fget($key) {global $time;if(file_exists('/volume1/web/logs/'.$key.'.cache')===true)$reply=file_get_contents('/volume1/web/logs/'.$key.'.cache');else $reply=0;return $reply;}

function cset($key,$value){if(!$m=xsMemcached::Connect('127.0.0.1', 11211)){die('Memcache failed to connect.');}$m->Set($key,$value);}
function cget($key){if(!$m=xsMemcached::Connect('127.0.0.1', 11211)){die('Memcache failed to connect.');}return $m->Get($key);}
class xsMemcached{
	private $Host;private $Port;private $Handle;
	public static function Connect($Host,$Port,$Timeout=5){$Ret=new self();$Ret->Host=$Host;$Ret->Port=$Port;$ErrNo=$ErrMsg=NULL;if(!$Ret->Handle=@fsockopen($Ret->Host,$Ret->Port,$ErrNo,$ErrMsg,$Timeout))return false;return $Ret;}
	public function Set($Key,$Value,$TTL=0){return $this->SetOp($Key,$Value,$TTL,'set');}
	public function Get($Key){$this->WriteLine('get '.$Key);$Ret='';$Header=$this->ReadLine();if($Header=='END'){$Ret=0;$this->SetOp($Key,0,0,'set');return $Ret;}while(($Line=$this->ReadLine())!='END')$Ret.=$Line;if($Ret=='')return false;$Header=explode(' ',$Header);if($Header[0]!='VALUE'||$Header[1]!=$Key) throw new Exception('unexcpected response format');$Meta=$Header[2];$Len=$Header[3];return $Ret;}
	public function Quit(){$this->WriteLine('quit');}
	private function SetOp($Key,$Value,$TTL,$Op){$this->WriteLine($Op.' '.$Key.' 0 '.$TTL.' '.strlen($Value));$this->WriteLine($Value);return $this->ReadLine()=='STORED';}
	private function WriteLine($Command,$Response=false){fwrite($this->Handle,$Command."\r\n");if($Response)return $this->ReadLine();return true;}
	private function ReadLine(){return rtrim(fgets($this->Handle),"\r\n");}
	private function __construct(){}
}
function sset($key,$value){
	$db = new mysqli('localhost', 'test', 'test', 'test');
	if($db->connect_errno > 0){ die('Unable to connect to database [' . $db->connect_error . ']');}
	$sql = "insert into `cache` (`name`,`value`) VALUES ('$key','$value') ON DUPLICATE KEY UPDATE `value`='$value';";
	if(!$result = $db->query($sql)) { die('There was an error running the query ['.$sql .' - ' . $db->error . ']');}
	$db->close();
}
function sget($key){
	$db = new mysqli('localhost', 'test', 'test', 'test');
	if($db->connect_errno > 0){ die('Unable to connect to database [' . $db->connect_error . ']');}
	$value = mysqli_fetch_assoc(mysqli_query($db, "select value from `cache` where name like '$key';"));
	$db->close();
	return $value['value'];
}

So, the script will store and retreive a variable like 1 => 1, 2=>2,… sequential using file, mysql and memcached and run that in loop until 200 variables in a row are set and retreived.

Now, enough theory, let’s crunch the numbers. Because of inconsistent results I ran the tests 5 times and created the average times.
All results can be viewed in the Excel file: memcached.xlsx

In the first test I did a set and get of each variable.

set-get file
set-get mysql
set-get memcached

In all three tests we see that the 4th and 5th run is lot more stable than the first runs. That’s where other caching systems kick in (e.g. filecache, MySQL Query cache,…) We also see that the file method is the most instable.

In the second test I only retrieved the previous set variables. In most applications you’ll read the variables more than you write them.

get file
get mysql
get memcached

Conclusion:

set-get avg
get avg

In each scenario MemCached is the fastest and most stable of these 3 options. The average test results show a maximum difference of 35 times faster than file cache and 2,5 times faster than mysql. The average difference is 2,11 for file cache and 2,22 for mysql.
So, conclusion is that MemCaches is at least double as fast.