Context reference count keeps increasing unexpectedly

Hi Marco,

I work in Clojure, so I am not providing a Java code here, but the behavior is simple to describe, an I am not sure whether it is intentional (but it looks very strange). I am using JOCL 1.9.0 ON AMD platform (if that might be important).

Summary: the reference count for the context object keeps increasing when I call functions that use context, such as clCreateCommandQueue, and even the functions that do not take context as an argument, but use it indirectly, such as clEnqueueNDRangeKernel or clEnqueueReadBuffer

For, example, if i keep calling clEnqueueRead the context’s reference count just keeps increasing.
The increasing is not in the info function since if i call it repeatedly the reference count stays the same.

I am not calling clRetainContext anywhere and am simply delegating clojure calls to CL/clXXXXX methods, so I am pretty sure that the repeated increasing of the reference count is somewhere in JOCL code.

Is this behavior intended or incidental?

This is the clojure code, I hope it helps at least as pseudo-code:

Here is how I call JOCL api, nothing special here:



(defn command-queue* [context device ^long properties]
  (let [err (int-array 1)
        res (CL/clCreateCommandQueue context device properties err)]
    (with-check-arr err res)))

And here is how I call it


(def program-source
  (slurp "test/opencl/examples/openclinaction/ch04/hello-kernel.cl"))

(def p (first (platforms)))
(def dev (first (devices p)))

(def ch (chan))
(def ctx (context [dev]))
(def cqueue (command-queue ctx dev 0))

(def host-msg (direct-buffer 16))
(def cl-msg (cl-buffer ctx 16 :write-only))

(def prog (build-program! (program-with-source ctx [program-source])))
(def hello-kernel (kernel prog "hello_kernel"))

(def global-work-size (long-array [1]))
(def local-work-size (long-array [1]))

(set-args! hello-kernel cl-msg)

(enqueue-nd-range cqueue hello-kernel global-work-size local-work-size)

(follow ch (enqueue-read cqueue cl-msg host-msg) host-msg)

(apply str (map char (wrap-byte-seq int8 (byte-seq (:data (<!! ch))))))


At the end of this code, before clRelease calls, the reference count of ctx would be 6, and I expected it to be 1, just like most of other stuff. Program reference count also unexpectedly increases to 2…

*** Edit ***

I found the answer to my question, and here it is, if anybody bumps to the same issue:

  1. Context’s reference count keeps rising
  2. But, when i call release on the resources that are inside the context (queues, mem-objects, etc.) it keeps decreasing
    So, apparently, JOCL keeps the reference to the context because it needs it implicitly when working on objects dependent on that context, but those objects’ release calls would also appropriately release the context.

Hello,

Oh, you scared me for a moment. Thanks for sharing this insight. I’m not sure whether I already tracked the reference count of a context throughout an application lifecycle. But I can say that JOCL should not affect the reference count in any way. It is only the thinnest possible layer around the underlying, native OpenCL implementation. So the reference count that you are observing should be the same as in a pure, native (non-JOCL) OpenCL application. IF this was not the case, I have a problem, but according to the EDIT, everything seems to be fine: The reference count thus seems to count how many “objects” are still associated with the context. This is not obvious from the specification, but may be a valid strategy.

(I think I’ll run some test comparing how the reference count behaves in the AMD- and in the NVIDIA implementation. There currently are many other tasks enqueued in my “todo” list, but this should not take so long)

bye
Marco

So, I ran a short test, and the result of printing the reference counters is as follows:


Platform: AMD Accelerated Parallel Processing
Initial               : 1
after creating buffer : 2
after creating buffer : 3
after releasing buffer: 2
after releasing buffer: 1
Platform: NVIDIA CUDA
Initial               : 1
after creating buffer : 1
after creating buffer : 1
after releasing buffer: 1
after releasing buffer: 1

So indeed, the AMD platform increases the reference counter whenever a buffer is created, whereas the NVIDIA platform always keeps the same reference count. May be good to know…

import static org.jocl.CL.*;

import org.jocl.*;

public class RefCountTest
{
    public static void main(String args[])
    {
        CL.setExceptionsEnabled(true);

        int numPlatformsArray[] = new int[1];
        clGetPlatformIDs(0, null, numPlatformsArray);
        int numPlatforms = numPlatformsArray[0];
        cl_platform_id platforms[] = new cl_platform_id[numPlatforms];
        clGetPlatformIDs(platforms.length, platforms, null);
        
        for (cl_platform_id platform : platforms)
        {
            runTest(platform);
        }
    }
    private static void runTest(cl_platform_id platform)
    {
        System.out.println("Platform: "+getPlatformName(platform));

        cl_context_properties contextProperties = new cl_context_properties();
        contextProperties.addProperty(CL_CONTEXT_PLATFORM, platform);
        int numDevicesArray[] = new int[1];
        final long deviceType = CL_DEVICE_TYPE_ALL;
        clGetDeviceIDs(platform, deviceType, 0, null, numDevicesArray);
        int numDevices = numDevicesArray[0];
        cl_device_id devices[] = new cl_device_id[numDevices];
        clGetDeviceIDs(platform, deviceType, numDevices, devices, null);
        devices = new cl_device_id[] { devices[0] };
        cl_context context = clCreateContext(
            contextProperties, devices.length, devices, 
            null, null, null);
        
        System.out.println("Initial               : "+getRefCount(context));

        cl_mem m0 = clCreateBuffer(context, CL.CL_MEM_READ_ONLY, 4,  null, null);
        System.out.println("after creating buffer : "+getRefCount(context));

        cl_mem m1 = clCreateBuffer(context, CL.CL_MEM_READ_ONLY, 4,  null, null);
        System.out.println("after creating buffer : "+getRefCount(context));

        clReleaseMemObject(m0);
        System.out.println("after releasing buffer: "+getRefCount(context));

        clReleaseMemObject(m1);
        System.out.println("after releasing buffer: "+getRefCount(context));
        
    }
    
    private static String getPlatformName(cl_platform_id platform)
    {
        return getString(platform, CL_PLATFORM_NAME);        
    }
    
    private static String getString(cl_platform_id platform, int paramName)
    {
        long size[] = new long[1];
        clGetPlatformInfo(platform, paramName, 0, null, size);
        byte buffer[] = new byte[(int)size[0]];
        clGetPlatformInfo(platform, paramName, buffer.length, Pointer.to(buffer), null);
        return new String(buffer, 0, buffer.length-1);
    }
    
    
    private static long getRefCount(cl_context context)
    {
        long refCount[] = { 0 };
        Pointer p = Pointer.to(refCount);
        clGetContextInfo(context, CL.CL_CONTEXT_REFERENCE_COUNT, Sizeof.size_t, p, null);
        return refCount[0];
    }
    
}