Perl XS 垃圾回收

Perl XS garbage collection

我不得不处理我公司中一个非常古老的代码库,它通过 perl 公开了 C++ api。

在代码审查中,我建议有必要对在 c++ 中分配的内存进行垃圾回收。

这里是代码的框架:

char* convert_to_utf8(char *src, int length) {
    .
    .
    .
    length = get_utf8_length(src);
    char *dest = new char[length];
    .
    .
    // No delete
    return dest;
}

Perl xs 定义:

PROTOTYPE: ENABLE

char * _xs_convert_to_utf8(src, length)
    char *src
    int length

CODE:
    RETVAL = convert_to_utf8(src, length)

OUTPUT:
    RETVAL

所以,我有评论说在 C++ 函数中创建的内存不会被 Perl 垃圾收集。并且 2 java 开发人员认为它会崩溃,因为 perl 将垃圾收集由 c++ 分配的内存。我建议使用以下代码。

CLEANUP:
    delete[] RETVAL

我错了吗?

我还 运行 这段代码并向他们展示了内存利用率的增加,有和没有 CLEANUP 部分。但是,他们要求提供确切的证明文件,但我找不到。

Perl 客户端:

use ExtUtils::testlib;
use test;

for (my $i=0; $i<100000000;$i++) {
    my $a = test::hello();
}

C++代码:

#define PERL_NO_GET_CONTEXT
#include "EXTERN.h"
#include "perl.h"
#include "XSUB.h"

#include "ppport.h"
#include <stdio.h>

char* create_mem() {
    char *foo = (char*)malloc(sizeof(char)*150);
    return foo;
}

XS代码:

MODULE = test       PACKAGE = test      
    char * hello()
CODE:
    RETVAL = create_mem();
OUTPUT:
    RETVAL
CLEANUP:
    free(RETVAL);

恐怕编写(和编写)Perl XS 文档的人可能认为 Perl 无法神奇地检测到其他语言(如 C++)进行的内存分配以明确记录这一点太明显了。 perlguts 文档页面中有一点说通过 Perl XS API 使用的所有内存都必须使用 Perl 的宏才能这样做,这可能会帮助您争论。

当您编写 XS 代码时,您正在编写 C(或有时是 C++)代码。您仍然需要编写适当的 C/C++,其中包括在适当的时候释放已分配的内存。


您希望 XS 创建的粘合函数如下:

void hello() {
    dSP;                       // Declare and init SP, the stack pointer used by mXPUSHs.
    char* mem = create_mem();
    mXPUSHs(newSVpv(mem, 0));  // Create a scalar, mortalize it, and push it on the stack.
    free(mem);                 // Free memory allocated by create_mem().
    XSRETURN(1);
}

newSVpv 复制 mem 而不是占有它,所以上面清楚地表明需要 free(mem) 来释放 mem.


在 XS 中,您可以将其写为

void hello()
CODE:
    {                          // A block is needed since we're declaring vars.
        char* mem = create_mem();
        mXPUSHs(newSVpv(mem, 0));
        free(mem);
        XSRETURN(1);
    }

或者您可以利用 XS 功能,例如 RETVALCLEANUP

SV* hello()
    char* mem;                 // We can get rid of the block by declaring vars here.
CODE:
    mem = create_mem();
    RETVAL = newSVpv(mem, 0);  // Values returned by SV* subs are automatically mortalized.
OUTPUT:
    RETVAL
CLEANUP:                       // Happens after RETVAL has been converted
    free(mem);                 //   and the converted value has been pushed onto the stack.

或者您也可以利用类型映射,它定义了如何将返回值转换为标量。

char* hello()
CODE:
    RETVAL = create_mem();
OUTPUT:
    RETVAL
CLEANUP:
    free(RETVAL);

这三个都完全可以接受。


关于凡人的笔记。

Mortalizing 是一种延迟的引用计数递减。如果您要在 hello returns 之前递减由 hello 创建的 SV,它将在 hello returns 之前被释放。取而代之的是,它不会被释放,直到调用者有机会检查它或占有它(通过增加它的引用计数)。