Skip to main content

Is it possible for LibraryLink function to be Listable like that in Compile?


We know in Compile, there is an Option RuntimeAttributes -> {Listable} which can let the compiled function easily explore the power of thread parallelization.



So is it possible for a LibraryLink function to be Listable like Compile?



Answer




There is an easier solution than the one I gave almost 2 years ago. In principle, you wrap your library function inside another CompiledFunction that is listable. Let the code speak:


fun = LibraryFunctionLoad["demo", "demo_I_I", {Integer}, Integer];
With[{fc = fun},
funListable = Compile[{{i, _Integer, 0}}, fc[i],
RuntimeAttributes -> {Listable},
Parallelization -> True]
];


If you inspect the compiled function, you see there is no external call. Instead, you find a special instruction that calls the library function code:



libCall



The performance is exceptionally good even though we have this wrapping. It might be not the best idea to profile such a simple function, but let's do it anyway. For comparison, I'm creating the same function as directly compiled code


inc = Compile[{{i, _Integer, 0}},
i + 1,
RuntimeAttributes -> {Listable},
Parallelization -> True,

(* CompilationTarget -> "C" *)
]

You should leave out the CompilationTarget as it was in my tests slower than the one that runs on the virtual machine:


r = Range[10^7];
funListable[r]; // AbsoluteTiming
inc[r]; // AbsoluteTiming
funListable[r] === inc[r]

For the parallelized library function, I get a runtime of 0.42 seconds, while the compiled version needs 0.53 seconds (about 0.7 seconds with compilation target "C"). If you want to see this paradigm outperform other solutions, you should read this answer of mine where I used it to parallelize a highly complex and non-trivial c-code that came from a library.




It seems it is possible. At least I got a toy-example that works. Without having any specific information about this topic from WRI, I always suspected that LibraryLink was not mainly created to give users a way to attach shared library functions to the kernel. I believe that the underlying technology was first used in Compile to make CompilationTarget->"C" possible and afterwards, a part of the framework was exposed to the user to make it possible to use LibraryLink. If someone has more information about this, please feel free to add it here.


That being said, when you look at the InputForm of a simple compiled function, you will find that it contains a LibraryFunction in the exact same way you would get it when loading your own library functions with LibraryFunctionLoad:


fc = Compile[{{x, _Integer, 0}},
x,
Parallelization -> True,
RuntimeAttributes -> {Listable},
CompilationTarget -> "C"
];
InputForm[fc]


(*
CompiledFunction[{10, 10.3, 5852}, {_Integer},
{{2, 0, 0}, {2, 0, 0}}, {}, {0, 1, 0, 0, 0}, {{1}},
Function[{x}, x, Listable], Evaluate,
LibraryFunction["/home/some/path/compiledFunction0.so",
"compiledFunction0", {{Integer, 0, "Constant"}}, Integer
]
]
*)


Some tests with Compile seemed to indicate, that the code that is created is not different with or without using the Listable attribute. This makes me believe that the distribution of arguments for a parallel evaluation of fc happens before the actual LibraryFunction is called. Therefore, when fc is called with a tensor, there might be some wrapper C function that calls the underlying LibraryFunction on all elements of the tensor in parallel.


My idea was that it might be possible to replace the LibraryFunction inside this CompiledFunction when the type of the function is correct. In the above example fc gets a single integer and returns a single integer. Let us use a LibraryLink example of the same type:


libFun = LibraryFunctionLoad["demo",   "demo_I_I", 
{{Integer, 0, "Constant"}}, Integer]

Note that this function does something different than fc because it increments its argument by one. Additionally, we can ensure that it is not Listable:



Library function call




Looking at InputForm[libFun] reveals that it has the exact same type as the LibraryFunction inside fc except that does something completely differently and was created by us, not by Compile. Let us inject our libFun inside the existing CompiledFunction


fcLibFunc = fc /. _LibraryFunction -> libFun;

fcLibFunc[10]
(* 11 *)

Now the big question is, is fcLibFunc working on lists doing the work in parallel?


fcLibFunc[{1, 2, 3, 4, 5, 6, 7, 8, 9}]
(* {2, 3, 4, 5, 6, 7, 8, 9, 10} *)


That seems to work. Creating a bigger example shows that the function runs parallel. Let us time this simple toy function against a compiled function that does the same:


fc2 = Compile[{{x, _Integer, 0}},
x + 1,
Parallelization -> True,
RuntimeAttributes -> {Listable},
CompilationTarget -> "C"
];

r = Range[10^6];



Do[fc2[r], {100}] // AbsoluteTiming
(* {3.97018, Null} *)

Do[fcLibFunc[r], {100}] // AbsoluteTiming
(* {3.13338, Null} *)

I have measured it several times and it seems our fcLibFunc needs on my machine only 80% of the runtime of fc2. I do not know why this is and whether it can be generalized, but we could show that it is possible to make a library function parallel-Listable.


Let me end by making clear the steps to do this yourself:





  • Create a fake compiled function like above that has the exact same type that your library function has. Please note that you cannot use library functions that change their input arguments. Therefore, you should always use "Constant" passing.




  • Load your library function and replace the last argument inside CompiledFunction with it. This ensures that your library function is called instead of the code that was created by Compile.




  • Think carefully about that when you call this new function with the wrong arguments, the highlevel fake code is used! To give an example, try to evaluate fcLibFunc[I].





Comments

Popular posts from this blog

plotting - Filling between two spheres in SphericalPlot3D

Manipulate[ SphericalPlot3D[{1, 2 - n}, {θ, 0, Pi}, {ϕ, 0, 1.5 Pi}, Mesh -> None, PlotPoints -> 15, PlotRange -> {-2.2, 2.2}], {n, 0, 1}] I cant' seem to be able to make a filling between two spheres. I've already tried the obvious Filling -> {1 -> {2}} but Mathematica doesn't seem to like that option. Is there any easy way around this or ... Answer There is no built-in filling in SphericalPlot3D . One option is to use ParametricPlot3D to draw the surfaces between the two shells: Manipulate[ Show[SphericalPlot3D[{1, 2 - n}, {θ, 0, Pi}, {ϕ, 0, 1.5 Pi}, PlotPoints -> 15, PlotRange -> {-2.2, 2.2}], ParametricPlot3D[{ r {Sin[t] Cos[1.5 Pi], Sin[t] Sin[1.5 Pi], Cos[t]}, r {Sin[t] Cos[0 Pi], Sin[t] Sin[0 Pi], Cos[t]}}, {r, 1, 2 - n}, {t, 0, Pi}, PlotStyle -> Yellow, Mesh -> {2, 15}]], {n, 0, 1}]

plotting - Plot 4D data with color as 4th dimension

I have a list of 4D data (x position, y position, amplitude, wavelength). I want to plot x, y, and amplitude on a 3D plot and have the color of the points correspond to the wavelength. I have seen many examples using functions to define color but my wavelength cannot be expressed by an analytic function. Is there a simple way to do this? Answer Here a another possible way to visualize 4D data: data = Flatten[Table[{x, y, x^2 + y^2, Sin[x - y]}, {x, -Pi, Pi,Pi/10}, {y,-Pi,Pi, Pi/10}], 1]; You can use the function Point along with VertexColors . Now the points are places using the first three elements and the color is determined by the fourth. In this case I used Hue, but you can use whatever you prefer. Graphics3D[ Point[data[[All, 1 ;; 3]], VertexColors -> Hue /@ data[[All, 4]]], Axes -> True, BoxRatios -> {1, 1, 1/GoldenRatio}]

plotting - Mathematica: 3D plot based on combined 2D graphs

I have several sigmoidal fits to 3 different datasets, with mean fit predictions plus the 95% confidence limits (not symmetrical around the mean) and the actual data. I would now like to show these different 2D plots projected in 3D as in but then using proper perspective. In the link here they give some solutions to combine the plots using isometric perspective, but I would like to use proper 3 point perspective. Any thoughts? Also any way to show the mean points per time point for each series plus or minus the standard error on the mean would be cool too, either using points+vertical bars, or using spheres plus tubes. Below are some test data and the fit function I am using. Note that I am working on a logit(proportion) scale and that the final vertical scale is Log10(percentage). (* some test data *) data = Table[Null, {i, 4}]; data[[1]] = {{1, -5.8}, {2, -5.4}, {3, -0.8}, {4, -0.2}, {5, 4.6}, {1, -6.4}, {2, -5.6}, {3, -0.7}, {4, 0.04}, {5, 1.0}, {1, -6.8}, {2, -4.7}, {3, -1.