由于这里没有提到(缺乏)锁定需求,所以我查看了 shmop.c 扩展代码。所以如果我错了请纠正我,但是 shmop.c 扩展使用 memcpy() 将字符串复制到共享内存和从共享内存复制出来,没有任何形式的锁定,据我所知,memcpy() 不是原子的。
如果我的猜测正确,那么这些“易于使用”的函数就不再那么“易于使用”了,必须用锁(例如信号量、flock 等)来包装。
由于这里没有提到(缺乏)锁定需求,所以我查看了 shmop.c 扩展代码。所以如果我错了请纠正我,但是 shmop.c 扩展使用 memcpy() 将字符串复制到共享内存和从共享内存复制出来,没有任何形式的锁定,据我所知,memcpy() 不是原子的。
如果我的猜测正确,那么这些“易于使用”的函数就不再那么“易于使用”了,必须用锁(例如信号量、flock 等)来包装。
我在2003年编写了一个PHP memcache,作为一种概念验证。
它在几台机器上用于进行繁重的页面加载缓存……
它运行得很好。
以下是我编写的一些核心函数
<?php
###############################################
#### 共享内存函数
/*
用于调试这些
使用 `ipcs` 查看当前内存
使用 `ipcrm -m {shmid}` 删除
在某些系统上,如果您
不想手动操作,请使用 `ipcclean` 清理未使用的内存
*/
###############################################
function get_key($fsize, $file){
if(!file_exists(TMPDIR.TMPPRE.$file)){
touch(TMPDIR.TMPPRE.$file);
}
$shmkey = @shmop_open(ftok(TMPDIR.TMPPRE.$file, 'R'), "c", 0644, $fsize);
if(!$shmkey) {
return false;
}else{
return $shmkey;
}//fi
}
function writemem($fdata, $shmkey){
if(MEMCOMPRESS && function_exists('gzcompress')){
$fdata = @gzcompress($fdata, MEMCOMPRESSLVL);
}
$fsize = strlen($fdata);
$shm_bytes_written = shmop_write($shmkey, $fdata, 0);
updatestats($shm_bytes_written, "add");
if($shm_bytes_written != $fsize) {
return false;
}else{
return $shm_bytes_written;
}//fi
}
function readmem($shmkey, $shm_size){
$my_string = @shmop_read($shmkey, 0, $shm_size);
if(MEMCOMPRESS && function_exists('gzuncompress')){
$my_string = @gzuncompress($my_string);
}
if(!$my_string) {
return false;
}else{
return $my_string;
}//fi
}
function deletemem($shmkey){
$size = @shmop_size($shmkey);
if($size > 0){ updatestats($size, "del"); }
if(!@shmop_delete($shmkey)) {
@shmop_close($shmkey);
return false;
}else{
@shmop_close($shmkey);
return true;
}
}
function closemem($shmkey){
if(!shmop_close($shmkey)) {
return false;
}else{
return true;
}
}
function iskey($size, $key){
if($ret = get_key($size, $key)){
return $ret;
}else{
return false;
}
}
################################################
?>
我编写了一个脚本来突出共享内存存储的优越性。
虽然它没有使用 shmop 函数,但底层概念类似。
'/shm_dir/' 是一个 tmpfs 目录,它基于共享内存,我已经将其挂载到服务器上。
以下是 Intel Pentium VI 2.8 服务器上的结果
对 1000 个文件的 IO 测试
普通目录的 IO 结果:0.079015016555786 秒
共享内存目录的 IO 结果:0.047761917114258 秒
对 10000 个文件的 IO 测试
普通目录的 IO 结果:3.7090260982513 秒
共享内存目录的 IO 结果:0.46256303787231 秒
对 40000 个文件的 IO 测试
普通目录的 IO 结果:117.35703110695 秒
共享内存目录的 IO 结果:2.6221358776093 秒
在 100 个文件的情况下,差异并不明显也不令人信服。
但是当我们将文件数量增加到 10000 和 40000 时,共享内存的优势就非常明显了。
脚本由 http://www.enhost.com 提供
<?php
set_time_limit(0);
// 普通目录。确保具有写入权限
$setting['regular_dir'] = '/home/user/regular_directory/';
// 共享内存目录。
$setting['shm_dir'] = '/shm_dir/';
// 要读取和写入的文件数量
$setting['files'] = 40000;
function IO_Test($mode)
{
$starttime = time()+microtime();
global $setting;
for($i = 0 ; $i< $setting['files'] ;$i++)
{
$filename = $setting[$mode].'test'.$i.'.txt';
$content = "Just a random content";
// 一些错误检测
if (!$handle = fopen($filename, 'w+'))
{
echo "无法打开文件 ".$filename;
exit;
}
if (fwrite($handle, $content ) === FALSE)
{
echo "无法写入文件:".$filename;
exit;
}
fclose($handle);
// 读取测试
file_get_contents($filename);
}
$endtime = time()+microtime();
$totaltime = ($endtime - $starttime);
return $totaltime;
}
echo '<b>对 '.$setting['files']. ' 个文件的 IO 测试</b><br>';
echo '<b>普通</b> 目录的 IO 结果:'.IO_Test('regular_dir') .' 秒<br>';
echo '<b>共享内存</b> 目录的 IO 结果:'.IO_Test('shm_dir') .' 秒<br>';
/* 删除文件以避免低估
#
# 无法删除文件将导致基准测试不准确
# 因为它会导致 IO_Test 函数不会重新创建现有文件
*/
foreach ( glob($setting['regular_dir']."*.txt") as $filename) {
unlink($filename);$cnt ++;
}
foreach ( glob($setting['shm_dir']."*.txt") as $filename) {
unlink($filename);$cnt ++;
}
?>
Windows 通过内存映射文件支持共享内存。查看以下函数以了解详细信息
* CreateFileMapping
* MapViewOfFile
此帮助页面中描述的 shmop 实现实际上只是一个仅存在于 PHP 中,甚至仅存在于 Linux 服务器上的 ramdisk/tmpfs。或者我错过了什么?
在 Windows 上,可以通过创建这样的磁盘轻松实现相同的功能。
事实上,在我的服务器上,我使用的是 tmpfs 磁盘,而不是——在我看来——有限的 shmop 功能。
为什么不实现一个 $_SHARED 或 $_MUTUAL 超全局变量,我们可以随意创建变量,并且所有连接都可以共享?
这将极大地提高许多 PHP 应用程序的性能,并可以节省大量服务器内存开销。特别是如果这些变量可以包含函数的类。
您可以实现由程序员来保证原子性。
这种超全局变量在 Windows 服务器上也是可行的。
SHMOP 背后的理念是一个易于使用的共享内存接口,
无需向共享内存段添加任何额外的标头
也不需要任何特殊的控制来访问 PHP 之外的共享内存
段。SHMOP 从 C 的 shm api 借用其 api,
这使得它非常易于使用,因为它像 C 一样将共享内存视为
一种文件。由于此功能,即使是新手也很容易使用
此功能。最重要的是 SHMOP 使用 shm 段来存储原始数据,
这意味着当您
使用 C、perl 或其他编程语言打开/创建/读取/写入由 PHP 创建或将由 PHP 使用的 shm 段时,您无需担心匹配标头等。
在这方面,它与
sysvshm 不同,sysvshm 的 shm 接口使用一个特殊的标头,该标头位于
共享内存段内,当您
想要从外部程序访问 php shm 时,这会增加不必要的难度。
此外,根据我在 Linux 2.2/2.4 和 FreeBSD 3.3 中的个人测试,SHMOP 比 sysvshm 快约
20%,主要是因为它不需要解析
专用标头并将数据存储为原始格式。
尽管共享内存的读取和写入不是原子的,但读取和写入单个字节始终是原子的。如果您的应用程序经常读取而很少写入“小”数据块(约 10-15 字节),这将非常有用。您可以通过使用 8 位校验和(如 CRC-8)对数据进行签名来避免使用任何类型的锁。这是确保数据不会损坏的有效且可靠的方法。冗余自然为 8 位。
您需要意识到的是,sysvshm 在其能力上极度面向 php,它与其他非 PHP 实用程序的接口相当笨拙。例如,您是否尝试过使用 sysvshm 读取非 php 创建的 shm 段?这是不可能的,因为 sysvshm 使用专有格式,实质上它只能在 PHP 内使用,除非您花时间弄清楚这种格式。
因此,基本上,shmop 的目的是提供一个简单的共享内存接口,可以与其他非 php shm 创建者一起使用。
希望这能澄清。