我正在使用PHP shm(semaphores extension的一部分,不要与项目中的shmop!)功能混淆。基本上共享内存作为一种堆,我只有一个数组,其中我存储键(具有无意义的值)作为散列索引,我只是检查“啊,它已经存在”。现在我的问题是:该阵列有时会变得很大,但并不总是如此。我不想保留我通常不需要的大量内存,而是动态调整大小。
我已经注册了一个错误处理程序,可以将错误转换为ErrorException
,所以我可以catch
shm_put_var
当内存很小以存储数组时所引发的错误 - 但不幸的是当数据不适合时,PHP会清除该段,因此所有其他数据也会丢失。因此,这不是一个选择。
因此,我需要一种方法来预测存储数据所需的大小。 One of the comments to shm_attach
at php.net声明PHP附加(PHP_INT_SIZE * 4) + 8
字节长度的标头,并且一个变量需要strlen(serialize($foo)) + 4 * PHP_INT_SIZE) + 4
(我已经简化了注释中给出的表达式,它等于我的但是被吹了不必要的)
虽然标题大小似乎是正确的(任何小于24字节的内存在创建时都会导致错误,因此24个字节似乎是PHP放入其中的标头的大小),每个变量条目的大小似乎不是在PHP的最新版本中保持不变:
- 我可以将“1”存储在大小为24 + strlen(serialize("1") + 3 * PHP_INT_SIZE) + 4
字节的共享内存段中(注意 3 而不是 4 ),>
- 我无法将“999”存储在一个24 + strlen(serialize("999") + 4 * PHP_INT_SIZE) + 4
有没有人知道使用shm函数预测在共享内存中存储任何数据需要多少内存的方法,或者对shm如何存储变量有一些参考? (我使用shmop函数读取整个内容并打印出来,但由于它是二进制数据,因此在合理的时间内不能反向工作)
(我会根据需要提供代码示例,我只是不确定哪些部分会相关 - 如果你想查看任何可用的样本,请给我打电话,我已经尝试了很多,所以我已准备好大多数情况下的样品)
[更新] 我的C非常糟糕,所以我没有深入了解来源(sysvshm.c和php_sysvshm.h),但我已经找到了一个问题使用在php.net上建议的解决方案:虽然我可以将复杂的公式简化为我在这里所包含的内容(基本上是从C源代码中获取),但这对于原始代码是不可能的,因为有类型转换和没有浮点数学。该公式除以sizeof(long)
并再次与它相乘 - 这在PHP中是无用的,但在C上舍入为sizeof(long)
的倍数。所以我需要先在PHP中纠正它。尽管如此,这并不是一切,因为测试显示我可以将一些值存储在比公式返回的更少的内存中(见上文)。
答案 0 :(得分:2)
作为在尝试更新变量时删除变量的问题的解决方法,并且段中没有足够的可用空间用于新值,您可以先检查是否有足够的可用空间,如果有,则仅检查然后你继续更新。
以下函数使用shmop_*
API获取使用shm_attach
创建的细分中的已用空间和总空间。
function getMemSegmentStats($segmentKey){
$segId = shmop_open($segmentKey, 'a', 0, 0) ;
$wc = PHP_INT_SIZE/4 ;
$stats = unpack("I{$wc}used/I{$wc}free/I{$wc}total",shmop_read($segId,8+PHP_INT_SIZE,3*PHP_INT_SIZE)) ;
shmop_close($segId) ;
return combineUnpackLHwords($stats) ;
}
function combineUnpackLHwords($array){
foreach($array as $key => &$val)
if( preg_match('/([^\d]+)(\d+)/',$key,$matches) ){
$key2 = $matches[1].($matches[2]+1) ;
$array[$matches[1]] = $val | $array[$key2] << 4*PHP_INT_SIZE ;
unset( $array[$key], $array[$key2] ) ;
}
return $array ;
}
64位机器需要函数combineUnpackLHwords
,因为unpack
函数不解包64位整数,所以它们必须由低位和高位32位字构造(在32位机器上,该功能无效)。
示例:
$segmentKey = ftok('/path/to/a/file','A') ;
$segmentStats = getMemSegmentStats($segmentKey) ;
print_r($segmentStats) ;
输出:
Array
(
[used] => 3296
[free] => 96704
[total] => 100000
)
答案 1 :(得分:1)
好的,自己回答这个问题,就像我现在想的那样。我仍然没有任何消息来源,但我自己的研究,所以请随意评论任何有用的链接或自己回答。
最重要的是:使用shm_*
函数计算在共享内存中存储数据所需大小的工作公式为:
$header = 24; // actually 4*4 + 8
$dataLength = (ceil(strlen(serialize($data)) / 4) * 4) + 16; // actually that 16 is 4*4
大小为$header
的标头仅在内存段的开头存储一次,并在分配段时存储(第一次使用shm_attach
时使用该系统v资源键)即使没有写入数据。因此,您不能创建小于24字节的内存段。
如果onyl想要使用它并且不关心细节,那么只需要一个警告:只要在C中long
s使用32位的系统上编译PHP就是正确的。 PHP使用64位long
进行编译,它最有可能是4 * 8 + 8 = 40
的标头大小,每个数据变量都需要(ceil(strlen(serialize($data)) / 8) * 8) + 32
。详细说明如下。
那么,我是怎么到那儿的?
我查看了PHP源代码。我不太了解C,所以我在这里讲的只是我如何得到它,它可能只是很多热空气...
相关文件已经在问题中链接 - 看那里。重要的部分是:
来自php_sysvshm.h
:
typedef struct {
long key;
long length;
long next;
char mem;
} sysvshm_chunk;
typedef struct {
char magic[8];
long start;
long end;
long free;
long total;
} sysvshm_chunk_head;
来自sysvshm.c
:
/* these are lines 166 - 173 in the sourcecode of PHP 5.2.17 (the one I found frist),
line nubmers may differ in recent versions */
/* check if shm is already initialized */
chunk_ptr = (sysvshm_chunk_head *) shm_ptr;
if (strcmp((char*) &(chunk_ptr->magic), "PHP_SM") != 0) {
strcpy((char*) &(chunk_ptr->magic), "PHP_SM");
chunk_ptr->start = sizeof(sysvshm_chunk_head);
chunk_ptr->end = chunk_ptr->start;
chunk_ptr->total = shm_size;
chunk_ptr->free = shm_size-chunk_ptr->end;
}
/* these are lines 371 - 397, comments as above */
/* {{{ php_put_shm_data
* inserts an ascii-string into shared memory */
static int php_put_shm_data(sysvshm_chunk_head *ptr, long key, char *data, long len)
{
sysvshm_chunk *shm_var;
long total_size;
long shm_varpos;
total_size = ((long) (len + sizeof(sysvshm_chunk) - 1) / sizeof(long)) * sizeof(long) + sizeof(long); /* long alligment */
if ((shm_varpos = php_check_shm_data(ptr, key)) > 0) {
php_remove_shm_data(ptr, shm_varpos);
}
if (ptr->free < total_size) {
return -1; /* not enough memeory */
}
shm_var = (sysvshm_chunk *) ((char *) ptr + ptr->end);
shm_var->key = key;
shm_var->length = len;
shm_var->next = total_size;
memcpy(&(shm_var->mem), data, len);
ptr->end += total_size;
ptr->free -= total_size;
return 0;
}
/* }}} */
所以,很多代码,我会尝试将其分解。
来自php_sysvshm.h
的部分告诉我们这些结构的大小,我们需要它。我假设每个char
有8位(最有可能在任何系统上都有效),并且每个long
有32位(在某些实际使用64位的系统上可能会有所不同 - 你必须改变那么数字。)
sysvshm_chunk
有3*sizeof(long) + sizeof(char)
,这会产生3*4 + 1 = 13
个字节。 sysvshm_chunk_head
有8*sizeof(char) + 4*sizeof(long)
,这会产生8*1 + 4*4 = 24
个字节。现在sysvshm.c
的第一部分是我们在PHP中调用shm_attach
时执行的代码的一部分。它通过编写标题结构来初始化内存段 - 标题结构 - 我们已经讨论过的定义为sysvshm_chunk_head
- 如果它已经不存在了。这将需要我们计算的24个字节 - 我在开头的公式中给出了相同的24个字节。
第二部分是实际将变量插入共享内存的函数。这个get由另一个函数调用,但是我跳过了那个函数,因为它不是那么有用。 Basicall,它获取共享内存头结构,包括meory段内数据的开始和结束地址。然后它会得到一个long
,其中包含用于存储变量的可变密钥,char*
(类似于字符串,但C版本),包含已经序列化的数据,以及该数据的长度(无论出于何种原因,它可以自己计算,但无论如何)
对于每个数据,标题(我们查看的结构定义为sysvshm_chunk
)加上实际数据现在写入内存。它与long
对齐但是为了更容易的内存管理(这意味着:它的大小总是四舍五入到sizeof(long)
的下一个倍数,这在大多数系统上再次是4个字节)。现在这里变得有点奇怪。根据我们正在查看的C代码,(ceil((strlen(serialize($data)) + 13 - 1) / 4) * 4) ;
应该有效(那里有13 sizeof(sysvshm_chunk)
)。但是:事实并非如此。它总是比我们实际需要的产生少4个字节。找不到那四个字节。我假设序列化数据(len
)的长度已经结合,但我没有查看源代码。但我无法在其他任何地方找到这4个字节。 char
在C结构定义中持续存在,char
在完整字节上对齐,仅此而已,因此不应该导致这4个额外字节 - 但如果我错了C这些也可能是原因。 ANyway,我在我的公式中单独对齐数据和标题,并且它有效(对齐标题alweayss有16个字节,这是我的公式中的16,数据长度通过该除数 - 乘法对齐)。但是,从技术上讲,公式也可以
$dataLength = (ceil((strlen(serialize($data)) + 13 - 1) / 4) * 4) + 4;
然而,如果我在其他地方错过了那4个字节,它会产生sam结果。我没有使用64位long
编译的PHP versoin运行系统,所以我无法验证哪一个是正确的。
tl; dr :问题解决了,欢迎评论,如果您有任何其他问题,现在是时候了。