预测在共享内存中存储数据所需的大小

时间:2014-02-18 16:06:58

标签: php shared-memory

我正在使用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.cphp_sysvshm.h),但我已经找到了一个问题使用在php.net上建议的解决方案:虽然我可以将复杂的公式简化为我在这里所包含的内容(基本上是从C源代码中获取),但这对于原始代码是不可能的,因为有类型转换和没有浮点数学。该公式除以sizeof(long)并再次与它相乘 - 这在PHP中是无用的,但在C上舍入为sizeof(long)的倍数。所以我需要先在PHP中纠正它。尽管如此,这并不是一切,因为测试显示我可以将一些值存储在比公式返回的更少的内存中(见上文)。

2 个答案:

答案 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_chunk3*sizeof(long) + sizeof(char),这会产生3*4 + 1 = 13个字节。
  • sysvshm_chunk_head8*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 :问题解决了,欢迎评论,如果您有任何其他问题,现在是时候了。