2

SWIGを使用してPHPでC++ライブラリをラップするときにメモリリークの問題が発生します。ディレクターが有効になっているときに、複合型を含むC++からのコールバックがPHPに送信されると発生するようです。リークを再現するためのスタンドアロンの例を次に示します。

Client.hpp:

#ifndef CLIENT_HPP_
#define CLIENT_HPP_

#include <vector>
#include "ProcedureCallback.hpp"

class Client {
public:
    void invoke(ProcedureCallback *callback) {
        callback->callback(std::vector<int>(0));
    }
};

#endif /* CLIENT_HPP_ */

ProcedureCallback.hpp:

#ifndef PROCEDURECALLBACK_HPP_
#define PROCEDURECALLBACK_HPP_

#include <vector>

class ProcedureCallback {
public:
    virtual void callback(std::vector<int>) = 0;
};

#endif /* PROCEDURECALLBACK_HPP_ */

したがって、これを使用するには、を作成しClient、サブクラス化ProcedureCallbackされたものをClientのinvokeメソッドに渡します。次に、Clientは、指定したメソッドを呼び出してcallback、空のintベクトルを渡します。

これはSWIGインターフェースファイルです:

%module(directors="1") debugasync
%feature("director");

%{
#include "Client.hpp"
#include "ProcedureCallback.hpp"
%}

%include "Client.hpp"
%include "ProcedureCallback.hpp"

その出力は非常に大きいので、代わりにpastebinに置きます:debugasync_wrap.cpp。このファイルで興味深いのは、おそらくSwigDirector_ProcedureCallback :: callback(1319行目)です。

void SwigDirector_ProcedureCallback::callback(std::vector< int > arg0) {
  zval *args[1];
  zval *result, funcname;
  MAKE_STD_ZVAL(result);
  ZVAL_STRING(&funcname, (char *)"callback", 0);
  if (!swig_self) {
    SWIG_PHP_Error(E_ERROR, "this pointer is NULL");
  }

  zval obj0;
  args[0] = &obj0;
  {
    SWIG_SetPointerZval(&obj0, SWIG_as_voidptr(&arg0), SWIGTYPE_p_std__vectorT_int_t, 2);
  }
  call_user_function(EG(function_table), (zval**)&swig_self, &funcname,
    result, 1, args TSRMLS_CC);
  FREE_ZVAL(result);
  return;
fail:
  zend_error(SWIG_ErrorCode(),"%s",SWIG_ErrorMsg());
}

これも興味深いかもしれません(行827):

static void
SWIG_ZTS_SetPointerZval(zval *z, void *ptr, swig_type_info *type, int newobject TSRMLS_DC) {
  swig_object_wrapper *value=NULL;
  /*
   * First test for Null pointers.  Return those as PHP native NULL
   */
  if (!ptr ) {
    ZVAL_NULL(z);
    return;
  }
  if (type->clientdata) {
    if (! (*(int *)(type->clientdata)))
      zend_error(E_ERROR, "Type: %s failed to register with zend",type->name);
    value=(swig_object_wrapper *)emalloc(sizeof(swig_object_wrapper));
    value->ptr=ptr;
    value->newobject=newobject;
    if (newobject <= 1) {
      /* Just register the pointer as a resource. */
      ZEND_REGISTER_RESOURCE(z, value, *(int *)(type->clientdata));
    } else {
      /*
       * Wrap the resource in an object, the resource will be accessible
       * via the "_cPtr" member. This is currently only used by
       * directorin typemaps.
       */
      value->newobject = 0;
      zval *resource;
      MAKE_STD_ZVAL(resource);
      ZEND_REGISTER_RESOURCE(resource, value, *(int *)(type->clientdata));
      zend_class_entry **ce = NULL;
      zval *classname;
      MAKE_STD_ZVAL(classname);
      /* _p_Foo -> Foo */
      ZVAL_STRING(classname, (char*)type->name+3, 1);
      /* class names are stored in lowercase */
      php_strtolower(Z_STRVAL_PP(&classname), Z_STRLEN_PP(&classname));
      if (zend_lookup_class(Z_STRVAL_P(classname), Z_STRLEN_P(classname), &ce TSRMLS_CC) != SUCCESS) {
        /* class does not exist */
        object_init(z);
      } else {
        object_init_ex(z, *ce);
      }
      Z_SET_REFCOUNT_P(z, 1);
      Z_SET_ISREF_P(z);
      zend_hash_update(HASH_OF(z), (char*)"_cPtr", sizeof("_cPtr"), (void*)&resource, sizeof(zval), NULL);
      FREE_ZVAL(classname);
    }
    return;
  }
  zend_error(E_ERROR, "Type: %s not registered with zend",type->name);
}

また、PHPでのメモリリークを示すために(debugasync.phpは、SWIGによって生成されたプロキシクラスのセットであり、pastebinにもアップロードしています):

<?php

require('debugasync.php');

class MyCallback extends ProcedureCallback {
    public function callback($intVector) {}
}

$client = new Client();
$callback = new MyCallback();

while (true) {
    print(number_format(memory_get_usage()) . "\n");
    for ($j = 0; $j < 1000; $j++) {
        $client->invoke($callback);
    }
}

これにより、メモリ使用量が出力され、1kの呼び出しが実行され、繰り返されます。それを実行すると、急速に成長するメモリスペースが表示されます。

$ php test.php 
692,664
1,605,488
2,583,232
3,634,776
4,538,784
5,737,760
6,641,768
7,545,816
^C

intまた、C ++コールバックが複合型(つまり)ではなくプリミティブ(つまり)を渡す場合、std::vector<int>メモリリークは発生しないことにも注意してください。

このメモリリークの原因は何ですか?

そして、より一般的には、これを解決するためにどのようなツールを使用できますか?Valgrindの山塊は、デバッグシンボルを使用してPHPを構築した後でも、実際に何が起こっているのかを絞り込むことができませんでした。

4

1 に答える 1

2

SWIGについては特に何も知りませんが、メモリ使用量がによって報告されたmemory_get_usage場合、取得されたメモリはZendEngineメモリマネージャに割り当てられます。

スクリプトが正常に終了すると( CTRL + Cまたはdie)、メモリマネージャは、次の場合に限り、検出されたメモリリークについて通知します。

  • PHPはデバッグモードでコンパイルされます(--enable-debug
  • report_memleaks = truephp.iniファイルにあります

これにより、解放されなかったメモリがどこに割り当てられたかがわかります。

とはいえ、スニペットには特に面白いものはありません。スタックに割り当てられていない唯一の変数は適切に破棄されます。

于 2010-08-03T00:38:52.320 に答える