JuliaLang / julia

The Julia Programming Language
https://julialang.org/
MIT License
45.68k stars 5.48k forks source link

global const variables in __init__() are useless #13817

Closed mattcbro closed 2 months ago

mattcbro commented 9 years ago

I am trying to precompile some of my modules, one of which tries to initialize some cuda kernels which must be done at run time. I have code that looks something like this,

const MaxBlocks=65535
const MaxThreads=1024
const MyBlocks=MaxBlocks
const devnumber=0

using CUDArt
function __init__()
# select a CUDA device
CUDArt.init(devnumber)
# load the PTX module (each module can contain multiple kernel functions)
global const gpumd = CuModule("aper.ptx",false)
# get kernel functions
global const cuaper = CuFunction(gpumd, "cuaper")
global const cumatmul = CuFunction(gpumd, "matmul")
end

function gmmul(gA,gB)
    rA = convert(Int32, size(gA,1))
    cA = convert(Int32, size(gA,2))
    cB = convert(Int32, size(gB,2))
    gC = CudaArray(Ftype, (size(gA,1), size(gB,2))) 
    blksize = min(MaxThreads, cB)
    gridsize = min(MyBlocks, rA)
    launch(cumatmul, gridsize, blksize, (gA, rA,cA,gB,cB,gC  ) )
    return(gC)
end

The use of global const variables in init() is recommended in the documentation.

Unfortunately cumatmul is not defined in gmmul() because init() is called after evaluating the rest of the code in the file. Therefore this file will not compile. I see no way to use cumatmul and it certainly can't be a constant.

Even worse if you try to initialize it outside of init() and then let init() update it's value, you lose the ability to do type inference. This is really annoying since I have no idea how to do default initializations of some of these complex C types.

Is there some code pattern here that actually lets you precompile modules that must initialize data using external C libraries? It sure doesn't make sense to define global constants that can nowhere be used in your actual module.

mattcbro commented 9 years ago

My current very poor work around is to essentially remove the const declarations and call init() twice. I do the latter by copying the contents of init() outside the function declaration as a cheesy way to use type inference to initialize my global variables. This probably won't be a very good idea for a sufficiently complex module.

yuyichao commented 9 years ago

That recommendation should be removed. global const is incompatible with precompilation (ref https://github.com/JuliaLang/julia/issues/12010).

You can use a const Ref with un-initialized value and update its value in __init__.

simonbyrne commented 9 years ago

So is the idea is the idea that you should write something like this?:

module Foo

type Bar
 ...
end

global const bar = Ref{Bar}()

function __init__()
    bar() = unsafe_load(cglobal((:bar,lib),Bar))
end
end #module

If so, is there any way we could express this in one line to reduce the amount of code?

yuyichao commented 9 years ago

You mean bar[] = .....? Yeah, that's what I meant.

If so, is there any way we could express this in one line to reduce the amount of code?

Not too easy currently (I hope we can register __init__ in a macro for basically this reason...). You can probably still have your own __init__ register mechanism and write a macro around it though.

In the long term, I believe this is basically the plan to improve the performance of globals. See https://github.com/JuliaLang/julia/pull/11456#issuecomment-118527075 and related discussions linked from that thread.

yuyichao commented 9 years ago

I think the technical part of this issue is already well covered in a number of global related issues. Add doc label since it is true that the document needs to be updated.

mattcbro commented 9 years ago

@yuyichao As far as the const Ref idea, how could I use it to initialize cumatmul in the above example?

My problem is that the cumatmul variable is used in this line, launch(cumatmul, gridsize, blksize, (gA, rA,cA,gB,cB,gC ) )

which from the CUDArt package requires a fairly complex C signature. Is the Ref declaration sort of the equivalent of void *? I was getting type errors when I tried to initialize this variable naively.

yuyichao commented 9 years ago

I don't really understand the problem. You don't seem to be calling gmmul in the __init__ method so when you call gmmul, the globals should already be initialized.

I'm not exactly sure what is the type of the Cu* functions but assuming they are actually type constructors, you could have sth like,

using CUDArt
const MaxBlocks = 65535
const MaxThreads = 1024
const MyBlocks = MaxBlocks
const devnumber = 0

# Replace these with actual concrete types (if they are not already)
const gpumd = Ref{CuModule}()
const cuaper = Ref{CuFunction}()
const cumatmul = Ref{CuFunction}()

function __init__()
    # select a CUDA device
    CUDArt.init(devnumber)
    # load the PTX module (each module can contain multiple kernel functions)
    gpumd[] = CuModule("aper.ptx",false)
    # get kernel functions
    cuaper[] = CuFunction(gpumd[], "cuaper")
    cumatmul[] = CuFunction(gpumd[], "matmul")
end

function gmmul(gA, gB)
    rA = convert(Int32, size(gA, 1))
    cA = convert(Int32, size(gA, 2))
    cB = convert(Int32, size(gB, 2))
    gC = CudaArray(Ftype, (size(gA, 1), size(gB, 2))) 
    blksize = min(MaxThreads, cB)
    gridsize = min(MyBlocks, rA)
    launch(cumatmul[], gridsize, blksize, (gA, rA, cA, gB, cB, gC))
    return gC
end

end
rofinn commented 6 years ago

Might be nice to update the documentation if Ref is still the recommended approach to handling updates to global const variables.

RainerHeintzmann commented 6 years ago

With the update to Julia 1.0 the Ref{} approach seems to have some trouble. Maybe someone can help? From the above, I wrote:

struct Persistent
    classpathstring::String
end
global const classpathstring = Ref{Persistent}()

function __init__()
    # to have only one classpath accumulated over several usages of this model:
    classpath[]=unsafe_load(cglobal((:classpathstring,lib),Persistent))
    ...
end

The trouble is the "lib" is not known. Am I missing something here?

The persistent variable is needed to make JavaCall a bit nices, such that several modules can "register" their classpath via the JavaCall.addClasspath method before the JVM is started. However, since these are using JavaCall from different modules, each of these has a different internal classpath variable.

simonbyrne commented 6 years ago

lib should be the library that you're calling.

RainerHeintzmann commented 6 years ago

Thanks for the comment, but I did not intend to call any library. I am just looking for a way to have a variable collecting strings throughout all using JavaCall occurances. Do you mean that for this aim, one would need to write a library that is external to Julia and then call it? This sounds like a lot of work... I was somehow hoping lib would refer to an already existing library.

simonbyrne commented 6 years ago

I'm not really sure what you're asking, but it sounds like it would be better discussed as an issue on the JavaCall.jl repository.

RainerHeintzmann commented 6 years ago

I think I now found a workable solution by misusing the ENV mechanism. Maybe it now also becomes more clear, what I was trying to achieve.

module A
function register(myClassPath)
    pathstring="";
    sep=";"
    try
        pathstring=ENV["JULIA_JAVACALL_CLASSPATH"];
        ENV["JULIA_JAVACALL_CLASSPATH"]=string(pathstring,sep,myClassPath)
    catch error
       if isa(error, KeyError)
           println("sorry, I couldn't find anything")
       end
       ENV["JULIA_JAVACALL_CLASSPATH"]=string("-Djava.class.path=",myClassPath)
   end
end
end

module B
using Main.A
function __init__()
    A.register("myclassPath_B")
end
end

module C
using Main.A
function __init__()
    A.register("myclassPath_C")
end
end

using Main.B
using Main.C

ENV["JULIA_JAVACALL_CLASSPATH"]
simonbyrne commented 6 years ago

You can do the same thing with Refs:

module A
const classpath = Ref("")

function register(myClassPath)
    sep = ";"
    if classpath[] == ""
        classpath[] = string("-Djava.class.path=",myClassPath)
    else
        classpath[] = string(classpath[],sep,myClassPath)
    end
end
end

module B
using Main.A
function __init__()
    A.register("myclassPath_B")
end
end

module C
using Main.A
function __init__()
    A.register("myclassPath_C")
end
end

using Main.B
using Main.C
RainerHeintzmann commented 6 years ago

I see. And then access the value via

using Main.A
A.classpath.x
simonbyrne commented 6 years ago

Better to use A.classpath[].

RainerHeintzmann commented 6 years ago

By the way. I had to find out the hard way, that for this to work, it is important to put the code in init() rather than just into the main body of the module.