Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Python referencing counting: Why isn't Py_REFCNT(o) = 1?

I'm trying to track down a memory leak in a Python C extension, and am having trouble understanding why my code is giving the reference counts that it is.

Referring to the code example that follows, I'd like to understand the following,

  1. Why does the reference count in Block 2 start at 118 and not 1? (Note that Block 2 uses a different variable to Block 1.)
  2. Why does the reference count in Block 4 start at 120 and not 1? (Note that Block 4 uses the same variable name as Block 1.)
  3. Given that the reference count in Block 4 didn't start at 1, why is it now 1 at the start of block 5?
  4. The last number printed seems to be a memory address. Why doesn't it print a reference count of 0?

Here's a short code example. (I'm using 64-bit Python 2.7 on Windows 10, using VS2012.)

// test.c : Test python reference counting
//

#include <stdlib.h> 
#include <Python.h>

int main()
{
    PyObject *PythonFunctionInput;
    FILE *fid;

    PyObject *tempPyObj;
    PyObject *tempPyObjInt;
    const int x = 1;  // This is to mimic data in my actual application
    const int y = 1;
    const int z = 1;


    Py_InitializeEx(0);
    PythonFunctionInput = PyDict_New();

    fid = fopen("TestOutputFile.txt", "wt");

    fprintf(fid,"%d\n",(int)Py_REFCNT(PythonFunctionInput));  // 1

    // Block 1
    tempPyObj = PyString_FromString("name1");
    fprintf(fid,"%d ",(int)Py_REFCNT(tempPyObj));  // 1
    PyDict_SetItemString(PythonFunctionInput,"fieldname1",tempPyObj);
    fprintf(fid,"%d ",(int)Py_REFCNT(tempPyObj));  // 2
    Py_DECREF(tempPyObj);
    fprintf(fid,"%d\n",(int)Py_REFCNT(tempPyObj));  // 1

    // Block 2
    tempPyObjInt = PyInt_FromLong((long)x);
    fprintf(fid,"%d ",(int)Py_REFCNT(tempPyObjInt)); // 118  why?
    PyDict_SetItemString(PythonFunctionInput,"fieldname2",tempPyObjInt);
    fprintf(fid,"%d ",(int)Py_REFCNT(tempPyObjInt));  // 119
    Py_DECREF(tempPyObjInt);
    fprintf(fid,"%d\n",(int)Py_REFCNT(tempPyObjInt));  //118

    // Block 3
    tempPyObjInt = PyInt_FromLong((long)y);
    fprintf(fid,"%d ",(int)Py_REFCNT(tempPyObjInt));  // 119
    PyDict_SetItemString(PythonFunctionInput,"fieldname3",tempPyObjInt);
    fprintf(fid,"%d ",(int)Py_REFCNT(tempPyObjInt));  // 120
    Py_DECREF(tempPyObjInt);
    fprintf(fid,"%d\n",(int)Py_REFCNT(tempPyObjInt));  // 119

    // Block 4
    tempPyObj = PyInt_FromLong((long)z);
    fprintf(fid,"%d ",(int)Py_REFCNT(tempPyObj));   // 120  why?
    PyDict_SetItemString(PythonFunctionInput,"fieldname4",tempPyObj);
    fprintf(fid,"%d ",(int)Py_REFCNT(tempPyObj));  // 121
    Py_DECREF(tempPyObj);
    fprintf(fid,"%d\n",(int)Py_REFCNT(tempPyObj));  // 120

    // Block 5
    tempPyObj = PyString_FromString("name5"); 
    fprintf(fid,"%d ",(int)Py_REFCNT(tempPyObj));  // 1  why?
    PyDict_SetItemString(PythonFunctionInput,"fieldname5",tempPyObj);
    fprintf(fid,"%d ",(int)Py_REFCNT(tempPyObj));  // 2
    Py_DECREF(tempPyObj);
    fprintf(fid,"%d\n",(int)Py_REFCNT(tempPyObj)); // 1

    // Block 6
    Py_DECREF(PythonFunctionInput);
    fprintf(fid,"%d\n",(int)Py_REFCNT(PythonFunctionInput));  // 0
    fprintf(fid,"%d ",(int)Py_REFCNT(tempPyObj));  // why not 0 ?

    Py_Finalize();

    fclose(fid);

    return 0;
}

The above code generates a file that looks like the following:

1
1 2 1
118 119 118
119 120 119
120 121 120
1 2 1
0
-1825751608 

I have annotated the end of each fprintf line in the code to show what number it displays.

like image 363
Phil Goddard Avatar asked Aug 02 '26 16:08

Phil Goddard


1 Answers

You are printing the reference count of an object created by PyInt_FromLong(). You are surprised that it is 118 (or some other value), rather than 1.

This is because small numbers in Python can be "shared" meaning the runtime does not need to physically instantiate a new object every time you create a small number. This is for efficiency: most Python programs create a bunch of numbers like 0, 1, 2, 3 and so on, and sharing them reduces memory consumption and garbage collection time.

like image 139
John Zwinck Avatar answered Aug 04 '26 05:08

John Zwinck



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!