简体   繁体   English

C99:静态库中的动态调度

[英]C99: Dynamic dispatch in a static library

Say I am developing a Math library and I want it to be such that it will detect whether or not user's machine support SSE (and which version) and based on that, separate internal functions will be called for the same API function. 假设我正在开发一个数学库,并且希望它能够检测用户的计算机是否支持SSE(以及哪个版本),然后基于该库,将为同一个API函数调用单独的内部函数。 I can think of three ways to implement that: 我可以想到三种实现方法:

  1. Have global function pointers in the library and let user call mathInit() in their source. 库中具有全局函数指针,并让用户在其源代码中调用mathInit()。 When they do, figure out the hardware details and assign the function pointers to different functions. 完成后,找出硬件细节,然后将功能指针分配给不同的功能。

  2. Same, except instead of having global function pointers, put them in a struct which is returned by mathInit(). 相同,除了没有全局函数指针外,将它们放在由mathInit()返回的结构中。 This way, user will have to call math.vec3Add(...) or similar. 这样,用户将必须调用math.vec3Add(...)或类似名称。

  3. Same as 1, but instead of having global pointers, make mathInit() a macro so that the function pointers will have local scope in user's main() function (and require mathInit() to be called from main()). 与1相同,但不使用全局指针,而是将mathInit()设为宏,以便函数指针在用户的main()函数中具有局部作用域(并要求从main()调用mathInit())。 It will be in a header, of course. 当然,它将在标题中。

Is any of these methods preferable? 这些方法是否可取? Is there some other, better way? 还有其他更好的方法吗?

This is largely opinion-based, IMHO. 恕我直言,这很大程度上是基于意见的。

And my opinion is, a math library should expose the least possible amount of details about its internal workings and should not require tricky function pointers or data structures or even macros to work with to the user code, if possible. 我的观点是,数学库应公开其内部工作的最少详细信息,并且在可能的情况下,不应要求复杂的函数指针,数据结构甚至宏来处理用户代码。

I'd go with (1) and assume you would completely hide the function pointers in your library, ie call them through an indirection in library code. 我会选择(1),并假设您将函数指针完全隐藏在库中,即通过库代码中的间接调用它们。

(3) is definitely the worst option, because it puts restrictions on the user code that are not directly obvious. (3)绝对是最糟糕的选择,因为它对用户代码施加了不直接明显的限制。 It might also create non-obvious problems/observations when debugging user code. 调试用户代码时,它也可能会产生非显而易见的问题/观察。

(2) Is a pretty uncommon way to present a library and requires at least intermediate C fluency, and might put off non-expert C users. (2)这是表示库的一种非常不常见的方法,并且至少需要中等C的流利程度,并且可能会使非专家C用户望而却步。

You could also expose a hasSSE function along with SSE and non-SSE functions and leave the decision what to use to the user code. 您还可以将hasSSE函数与SSE和非SSE函数一起公开, hasSSE用户决定使用什么功能。 Not sure that would have any benefits over (1), though. 但是不确定(1)是否会有任何好处。

My suggestion will be compiling a separate unit for each instruction set (eg Xnosse.o Xsse3.o Xsse4.o, etc.) and use an automatic dispatcher for those. 我的建议是为每个指令集(例如Xnosse.o Xsse3.o Xsse4.o等)编译一个单独的单元,并对这些指令使用自动调度程序。 The user needs to get the best performance for his PC, and not care about the inside detailing. 用户需要为自己的PC获得最佳性能,而无需关心内部细节。

Since you wrote your code runs in a library, you can make the dispatch decision on load time automatically by using an init function that will be called on library load. 由于您编写的代码是在库中运行的,因此可以使用将在库加载时调用的init函数来自动决定加载时间。 You can also make this decision run only the firsts time a function is actually called, this is for lazy binding. 您还可以使此决定仅在首次调用函数时才运行,这是用于延迟绑定。

Here is a code example (gcc only!) 这是一个代码示例(仅适用于gcc!)

Compilation units: 编制单位:

//Xnosse.c
void do_some_math_stuff_no_sse(int x, int y)
{
    ...do some sophisticated math stuff with no sse support
}
void do_some_other_math_stuff_no_sse(int x, int y)
{
    ...do some other sophisticated math stuff with no sse support
}

//Xsse3.c
void do_some_math_stuff_sse3(int x, int y)
{
    ...do some sophisticated math stuff with sse3 support
}
void do_some_other_math_stuff_sse3(int x, int y)
{
    ...do some other sophisticated math stuff with sse3 support
}

//Xsse4.c
void do_some_math_stuff_sse4(int x, int y)
{
    ...do some sophisticated math stuff with sse4 support
}
void do_some_other_math_stuff_sse4(int x, int y)
{
    ...do some other sophisticated math stuff with sse4 support
}

Now to the library: 现在到图书馆:

//my_math.h
/* Following definitions are in my_math.c */
extern void (*do_some_math_stuff)(int x, int, y);
extern void (*do_some_other_math_stuff)(int x, int y);

//my_math.c
void not_set(int x, int y)
{
    // If you don't want to use the constructor for any reason,
    // say you want lazy binding, this will do the trick as our
    // functions do_math_stuff and do_other_math_stuff are initialized
    // to this one
    setup();
}

void (*do_some_math_stuff)(int x, int, y) = not_set;
void (*do_some_other_math_stuff)(int x, int y) = not_set;

int detect_sse()
{
    ..Do runtime detection of sse version
}

/* The following function will be called when your library loads */
void __attribute__ ((constructor)) setup(void) 
{
    if (detect_sse() == 0)
    {
        do_some_math_stuff = do_some_math_stuff_no_sse;
        do_some_other_math_stuff = do_some_other_math_stuff_no_sse;
    }
else if (detect_sse() == 3)
    {
        do_some_math_stuff = do_some_math_stuff_sse3;
        do_some_other_math_stuff = do_some_other_math_stuff_sse3;
    }
else if (detect_sse() == 4)
    {
        do_some_math_stuff = do_some_math_stuff_sse4;
        do_some_other_math_stuff = do_some_other_math_stuff_sse4;
    }
}

If you want lazy binding, remove constructor decorater from setup and compile with: 如果您想要延迟绑定,请从安装程序中删除构造函数decorater并使用以下命令进行编译:

gcc -Wall -shared -fPIC -o libmy_math.so my_math.c Xnosse.c Xsse3.c Xsse4.c

If you want the dynamic dispatcher to run when the library loads use the following additional parameters to gcc: 如果要在库加载时运行动态调度程序,请对gcc使用以下附加参数:

gcc -Wall -shared -Wl,-init,setup -fPIC -o libmy_math.so my_math.c Xnosse.c Xsse3.c Xsse4.c

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM