FazBrowse GitHub Viewer | Trending |
URL:
| Home
Tools: [Download Repo ZIP]   [Original HTTPS Page]

Building an FFI call unnecessarily requires the symbol · Issue #299 · polyml/polyml · GitHub

/ polyml Public

Building an FFI call unnecessarily requires the symbol #299

Description

I have a performance issue with FFI calls which turns out to be my fault: I am building the call on every call. However, the current interface provided by Foreign does not make the fix easy in this particular case because buildCall<N> functions require the symbol to build the call, which isn't actually necessary.

The application I am building uses ML-NLFFI to generate the ML code to call C functions. For now I have adapted the MLton version of ML-NLFFI to generate code for Poly/ML instead - a fairly minor tweak. ML-NLFFI generates a 'Callop' structure for each combination of argument and return value types. Initially, I didn't use the structure Foreign but instead used an alternative structure PolyMLFFI that implements the signature POLYML_F_F_I, giving a Callop structure as follows:

structure Callop_3 = struct
    local
        open PolyMLFFI
    in
        val callop = call (cPointer &&> cPointer &&> cPointer --> cPointer)
    end
end

This function can be called as Callop_3.callop sym (arg1 & arg2 & arg3). Notably, the symbol is supplied at the time of the call.

PolyMLFFI avoids the buildCall<N> family of functions by using nested tuples, introducing infix operators to make such expressions more readable. (Aside: It would be nice if ML had a way to construct functions on tuples by composition.) This seems to work well enough but I had concerns about inlining of the FFI call code so wanted to compare performance with using Foreign instead. The equivalent Callop structure to above is:

structure Callop_3 = struct
    local
        open Foreign
    in
        fun callop sym
            = buildCall3 (sym, (cPointer, cPointer, cPointer), cPointer)
    end
end

Of course, buildCall3 is not evaluated until Callop_3.callop is applied to sym which is done only at the time of a call, hence my performance issue. I think I could cache the built call, for example, by evaluating

val callSym = Lazy.lazy (fn () => Callop_3.callop sym)

once and calling as callSym () (arg1, arg2, arg3), using Lazy.lazy to implement this common pattern.

Still, it would be good if Foreign supported this use-case directly. I think Foreign is easily changed by moving the symbol argument out of the callF argument of buildCall:

        fun buildCall(argConv, resConv, callF) =
        let
            val { ctype = resType, load = resLoad, ...} = resConv
            val { store=storeArgs, ctype={size=argSize, ...}, updateML=updateArgs, ...} = argConv
            val resultOffset = alignUp(argSize, #align resType)
            val argResSpace = resultOffset + #size resType
        in
            fn fnAddr => fn mlArgs =>
                alloca(argResSpace,
                    fn rMem =>
                    let
                        val freeArgs = storeArgs(rMem, mlArgs)
                        val resultAddr = rMem++resultOffset
                    in
                        let
                            val () = callF fnAddr (rMem, resultAddr)
                            val result = resLoad resultAddr
                        in
                            updateArgs(rMem, mlArgs);
                            freeArgs();
                            result
                        end handle exn => (freeArgs(); raise exn)
                    end)
        end

and then e.g.

        fun buildCall3withAbi (abi: abi,
            (arg1Conv:  'a conversion, arg2Conv:  'b conversion, arg3Conv:  'c conversion), resConv: 'd conversion): symbol -> 'a * 'b *'c -> 'd =
        let
            val callF = callwithAbi abi [#ctype (breakConversion arg1Conv), #ctype (breakConversion arg2Conv), #ctype (breakConversion arg3Conv)] (#ctype (breakConversion resConv))
            val argConv = cStruct3(arg1Conv, arg2Conv, arg3Conv)
        in
            buildCall(argConv, resConv, callF)
        end

Unfortunately this changes the type of the buildCall<N> functions so breaks existing code.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions


      Back | FazBrowse Home | New Git URL