2010-08-02 3 views
2

SWIG를 사용하여 PHP에서 C++ 라이브러리를 래핑하는 메모리 누수 문제가 있습니다. 디렉터가 활성화되어있는 동안 C++에서 복잡한 유형을 포함하는 콜백이 PHP로 전송 될 때 발생하는 것으로 보입니다.SWIG 생성 확장 프로그램의 메모리 누수

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 서브 클래스를 전달 클라이언트의 invoke 메소드로 전달한 다음 Client는 사용자가 지정한 메소드의 callback 메소드를 호출하고 빈 int 벡터를 전달합니다.

는 꿀꺽 꿀꺽 인터페이스 파일입니다

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

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

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

출력이 매우 큰, 그래서 내가 대신 페이스트 빈에 넣어 : debugasync_wrap.cpp합니다.

이 또한 관심을 가질
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의 메모리 누수를 보여주기 위해 (이 파일에 관심 아마 SwigDirector_ProcedureCallback :: 콜백 (라인 1319)입니다

<?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 호출 및 반복을 수행 debugasync.php I는 또한 페이스트 빈에 업로드 SWIG 의해 생성 된 프록시 클래스 집합)이다. 그것이 빠르게 성장하는 메모리 공간을 도시 러닝

$ 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 

를 또한 참고하면 C++ 콜백 통과하면 프리미티브 (즉 int)를 대신 복합 타입 (즉 std::vector<int>)를 어떠한 메모리 누출이 없다는 것이다.

이 메모리 누수의 원인은 무엇입니까?

더 일반적으로이 문제를 해결하기 위해 어떤 도구를 사용할 수 있습니까? Valgrind의 대산 괴는 디버깅 심볼을 사용하여 PHP를 빌드 한 후에도 실제로 어떤 일이 발생 하는지를 좁히지 못했습니다.

답변

2

특히 SWIG에 대해 아무것도 알지 못하지만 메모리 사용량이 memory_get_usage으로보고되면 촬영 한 메모리가 Zend Engine 메모리 관리자로 할당됩니다.

스크립트가 깨끗하게 (NO CTRL + C 또는 die)를 완료하면 메모리가 같이만큼 찾았다 누수에 대해, 메모리 관리자는 당신을 말할 것이다 :

  • PHP는 디버그 모드에서 컴파일 (--enable-debug를)
  • 당신은 php.ini 파일

해제되지 않은 메모리가 할당 된 곳이 당신을 말할 것이다에 report_memleaks = true 있습니다.

즉, 귀하의 스 니펫에는 특별히 웃기는 것이 없습니다. 할당되지 않은 유일한 스택 변수는 적절하게 처리됩니다.

+0

위의 for 루프에서 5,000 호출을 사용했으며 PHP가 debugasync_wrap.cpp의 860 줄에서 발생하는 5,000 개의 메모리 누수를보고했습니다. [Tue Aug 3 11:49:58 2010] Script : 'test.php' 빈/CPP/debugasync_wrap.cpp (860) : 마지막 누출이 4999 시간 === 총 5000 메모리 누수 내가 efree을 추가 === 을 감지 반복 = test.php 확보 0x085B96D0 (19 바이트), 스크립트 (classname- > value.str.val) 끝 부분의 FREE_ZVAL 앞에. 추가로 PHP는 메모리 누수없이 종료되지만 약간 느린 속도 (5M 호출 후 6.1M vs. 5.7M)에도 불구하고 동일한 증가하는 메모리를 계속 관찰합니다. –

+0

@Ed 좋은 catch, 나는'FREE_ZVAL'을 사용하지 않는다고 고백한다. 그래서 나는 소멸자를 호출하지 않았다는 것을 몰랐다. 'zval_ptr_dtor' 또는'zval_dtor' 다음에'FREE_ZVAL'을 사용할 수 있습니다. 정말로 문자열을 직접 풀어서는 안됩니다. 메모리 누수가없는 상태에서 메모리 사용량이 계속 증가하고 있다고보고되면 어쩌면 당신은 어딘가에 자원을 가지고있을 것입니다. 아마 정상 일 것입니다. 몇 천 개가 넘는 호출을 추가하고 PHP가 메모리가 부족한 지 확인하십시오. (그런데, 공백보다 최소한 3 자 이상인 사용자 이름을 선택하십시오. 그렇지 않으면 @ 사용자 이름을 사용하는 알림이 작동하지 않습니다) – Artefacto

+0

Thanks @Artefacto. efree()로 해결 된 PHP가 잡은 누수가 실제로 문제의 약 10 %였습니다. 나머지 90 %는 콜백 본문에서 "$ intVector = null"이라고 말하면서 해결되었습니다. unset() 사용은 참조 횟수가 콜백에 전달 될 때 3이므로 (xdebug를 사용하여 검증 됨) 작동하지 않았습니다. –