为什么我的C模块泄漏内存?

时间:2011-02-10 09:38:50

标签: python c module refcounting

我正在从大文件中读取列表,我最终希望将其存储为array.array。因为

map(int, line.split())

非常慢,我写了一个小的C模块,它做了strtok和更快版本的atoi:

inline long
minhashTables_myatoi(const char* s)
{
    int r;
    for (r = 0; *s; r = r * 10 + *s++ - '0');
    return r;
}

static PyObject*
minhashTables_ints(PyObject *self, PyObject *args)
{
    char* s;
    Py_ssize_t slen;

    if(!PyArg_ParseTuple(args, "s#", &s, &slen))
        return NULL;

    long* buf = malloc(sizeof(long) * (slen+1)/2);

    const char* tok = strtok(s, " ");
    buf[0] = minhashTables_myatoi(tok);
    Py_ssize_t i;
    for(i = 1; (tok = strtok(NULL, " ")) != NULL; i++)
        buf[i] = minhashTables_myatoi(tok);

    Py_ssize_t buflen = i;
    PyObject* list = PyList_New(buflen);
    PyObject *o;
        for(i = 0; i < buflen; i++)
    {
        o = PyInt_FromLong(buf[i]);
        PyList_SET_ITEM(list, i, o);
    }
    free(buf);

    return list;
}

因此,我的python脚本使用字符串调用ints()并将其传递给array.array构造函数,并将生成的数组保存在list中。

我的问题是,现在脚本泄漏了内存,当然它没有使用地图而不是ints()功能。

使用我自己版本的Pythons int()使用C模块也不会泄漏内存。

感谢您的帮助!

修改 为了对模块进行valgrind,我使用了这个脚本:

import minhashTables

data = ' '.join(map(str, range(10)))
print 'start'
foo = minhashTables.ints(data)
del data
del foo
print 'stop'

我运行valgrind --tool=memcheck --leak-check=full --show-reachable=yes python test.py,但是startstop之间的valgrind没有输出,之前和之后都有吨。

修改:确认泄漏的代码:     import minhashTables

for i in xrange(1000000000):
    data = ' '.join(map(str, range(10, 10000)))
    foo = minhashTables.ints(data)

我必须重新创建字符串,因为strtok会更改它。顺便说一下,将字符串复制到另一个内存位置不会改变行为。

3 个答案:

答案 0 :(得分:2)

我建议你看一下Valgrind - 它是一个非常有用的工具,可以在C中找到内存泄漏的底部。

答案 1 :(得分:1)

对于malloc的所有人来说,你真的需要long空间吗?

我不熟悉Python / C API,所以这可能是一个糟糕的建议,但是你不能只是迭代字符串并将你找到的每一长时间追加到列表中吗?

即。拿这个代码:

static const char* const testString = "12 345  67  8 910 11 1213 141516, 1718";

int main()
{
    const char* i = testString;
    long parseLong = 0;
    int gotLong = 0;

    for (;*i;++i)
    {
        if ('0' <= *i && *i <= '9')
        {
            parseLong = (parseLong * 10) + (*i - '0');
            gotLong = 1;
        }
        else if (gotLong)
        {
            printf("Got: %d\n", parseLong);
            parseLong = 0;
            gotLong = 0;
        }
    }

    if (gotLong)
        printf("Got: %d\n", parseLong);
}

然后将printf替换为PyList_Append()等适当的pythony-goodness。

除了避免使用malloc,使用更少的内存并且能够直接安全地操作常量Python字符串之外,此代码还处理诸如空字符串,多个空格和数字之间的其他分隔符等极端情况。

<小时/> 编辑:计算多头 如果你想先计算long的数量,那么你可以分配正确长度的Python List然后你可以添加这样的东西:

    for (i = testString;*i;++i)
    {
        const int isdigitoflong = isdigit(*i);

        if (!gotLong && isdigitoflong)
            longCount++;

        gotLong = isdigitoflong;
    }

哪个应该比较快。

<小时/> 编辑2:更好的解析器
这里有一个稍微好一点的解析器版本,它更紧凑,不需要gotLong,也不必重复代码来处理最终的长度:

    for (i = testString;*i;++i)
    {
        if (isdigit(*i))
        {
            do {
                parseLong = (parseLong * 10) + (*i - '0');
            } while (*++i && isdigit(*i));

            printf("Got: %d\n", parseLong);
            parseLong = 0;
        }
    }   

答案 2 :(得分:-1)

试试这个

inline long
    minhashTables_myatoi(const char* s)
    {
        long result=0;
        while((*s)!='\0'){
            result = result * 10 + (*s- '0');
            s++;
        }
        return result;
    }