This post is written with Mathematica version 13.1 and is subject to change in the future.
Mathematica's C Types
does not include vector type but includes CArray
which can be converted to C vector type easily.
Compiling, in general, and in this case, will change symbol names that's why you used EXTERN_C DLLEXPORT
which are extern "C"
and __declspec(dllexport)
respectively to make sure they're exported and their names won't change. So I added a little bit of interface to make the code work.
- Let's assume our code looks like this:
cppCode = "
#include <vector>
#include <algorithm>
using namespace std;
// accept a reference to change the original data
void sort_add2(vector<int>& vec) {
sort(vec.begin(), vec.end());
for (int &i : vec) {
i=i+2;
}
}
extern \"C\" {
__declspec(dllexport) int* sort_add2_interface(int* arr,int len){
// build a vector from our array
vector<int> temp(arr, arr + len);
// do the operation
sort_add2(temp);
// return the first element address
return &(temp[0]);
}
}
";
- Now build the code:
lib = CCompilerDriver`CreateLibrary[cppCode, "selectionSort",
Language -> "C++"]
- Declare the function:
declaration =
LibraryFunctionDeclaration["sort_add2_interface",
lib, {"CArray"::["CInt"], "CInt"} -> "CArray"::["CInt"]]
- Load the exported library:
LibraryLoad[lib];
- On the Mathematica side, we convert our list to
CArray
using CreateTypeInstance
, then call the library and build the result from reading the output with FromRawPointer
(it get casts to Integer64).
fn = FunctionCompile[declaration,
Function[Typed[arr, "ListVector"::["Integer32"]],
Module[{temp, len, result},
len = Cast[Length[arr], "CInt", "BitCast"];
temp =
LibraryFunction["sort_add2_interface"][
CreateTypeInstance["Managed"::["CArray"::["CInt"]], arr], len];
Table[Cast[FromRawPointer[temp, i - 1], "Integer64"], {i, len}]
]
]
]
Testing:
fn[{2, 3, 1}]
(* Out: {3, 4, 5} *)
Remember that if you have a library that contains sort_add2
and is exposed, you can build another library that acts as an interface. In this case, I combine the two but they can be separated.
If they are separated, you have to include their declaration to your code and use CCompilerDriver`CreateLibrary
's "Libraries"
option to make it work.
For example, if you have a lib1.dll
(with a lib1.lib
) which contains sort_add2
, you code will look like this:
cppCode2 = "
#include <vector>
#include <algorithm>
using namespace std;
// declaration
extern \"C\" void sort_add2(vector<int>& vec);
extern \"C\" __declspec(dllexport) int* sort_add2_interface(int* \
arr,int len){
vector<int> temp(arr,arr+len);
sort_add2(temp);
return &(temp[0]);
}
";
You would use this code to compile:
lib = CCompilerDriver`CreateLibrary[cppCode2, "library_interface",
Language -> "C++", "Libraries" -> {"C:\\PATH_TO_LIBRARY\\lib1.lib"}]
And the rest is similar (follow from step 3, in step 4 you need to load both libraries).
I was more curious about the performance, so here is the result (assuming data = RandomInteger[{0, 2^31 - 1}, 10000]
):
Code |
Timing |
Memory Usage |
Sort[data]+2 |
0.00025 |
160,456 |
fn[data] |
0.0013 (5x slower) |
281,160 |
I decide to go deeper and this is the result of different parts of calling fn1
:
Code |
Timing (~) |
building vector (CreateTypeInstance ) |
0.00083 |
calling sort_add2 |
0.00048 |
building the result (Table[ ... FromRawPointer, ... ] ) |
0.00001 |
The newly added feature has much more power and along with the LibraryLink which can give direct access to the data (and I assume is faster), Mathematica become more connected than ever.
There are many things that I don't and I'm sure the code above can improve further. (I'll welcome any suggestion/correction)
Update 1
Thanks to @yode's comment and this post using "PackedArray"
instead of "ListVector"
will boost the speed by a factor of 2, so the new code will look like this:
fn2 = FunctionCompile[declaration,
Function[Typed[x, "PackedArray"["MachineInteger", 1]],
Module[{in, len, out},
len = Cast[Length[x], "CInt", "BitCast"];
in = CreateTypeInstance["CArray"["CInt"], len];
Do[ToRawPointer[in, i - 1, Cast[x[[i]], "CInt", "BitCast"]], {i,
len}];
out = LibraryFunction["sort_add2_interface"][in, len];
Table[Cast[FromRawPointer[out, i - 1], "Integer64"], {i, len}]]]]
In pursuing performance I did try sort_add2
with the i64
type, so we might gain speed by removing all the Cast
but it didn't have an effect.
My next attempt was building a library and hoping by using LibraryFunctionLoad
, we could gain speed which didn't happen (both Automatic
and "Shared"
memory management types). It was around ~20% slower than fn2
. Interestingly whether it was "Shared"
(in-place operation) or it was a copy of the original data, the speed didn't have a difference.
compile
, now my last update has obviously solved this problem well. :) $\endgroup$