c - C 中泛型函数与函数指针数组相比有何缺点?

标签 c gcc compiler-construction function-pointers

我正在编写一个具有多种存储类型('stypes')和多种数据类型('dtypes')的矩阵库(SciRuby 的一部分)。例如,矩阵的stype目前可能是密集、耶鲁(又名“csr”)或列表列表;及其 dtype可能是int8 , int16 , int32 , int64 , float32 , float64 , complex64

用 Ruby 或 sed 编写模板处理器非常容易,它采用基本函数(如稀疏矩阵乘法)并为每个可能的 dtype 创建一个自定义版本。 。我什至可以编写这样一个模板来处理两种不同的数据类型,比如说,如果我们想乘以 int32通过float64 .

在某些情况下,对于不同的类型可以执行相同的操作。但最终,您可能会得到一大堆函数,其中许多函数在大多数人的使用过程中甚至从未被使用过。

使用函数指针数组来访问这些函数也很容易——想象一下,即使是 3 维函数指针数组也不是太难:

MultFuncs[lhs->stype][lhs->dtype][rhs->dtype](lhs->shape[0], rhs->shape[1], lhs->data, rhs->data, result->data);
// This might point to some function like this:
// i32_f64_dense_mult(size_t, size_t, int32_t*, float64*, float64*);

当然,函数指针数组的极端替代方案是分层的 switch ,它的编码和维护会非常复杂。或if/else声明:

switch(lhs->stype) {
case STYPE_SPARSE:
  switch(lhs->dtype) {
  case DTYPE_INT32:
    switch(rhs->dtype) {
    case DTYPE_FLOAT64:
      i32_f64_mult(lhs->shape[0], rhs->shape[1], lhs->ija, rhs->ija, lhs->a, rhs->a, result->data);
      break;
    // ... and so on ...

这似乎也是 O(sd2),其中 s= stypes 数量,d=每个操作的数据类型数,而函数指针数组将为 O(r),其中 r= 数组中的维数。

但还有第三种选择。

第三种选择是使用函数指针数组进行常见操作(例如,从一种未知类型复制到另一种未知类型):

SetFuncs[lhs->dtype][rhs->dtype](5, // copy five consecutive items
 &to, // destination
 dtype_sizeof[lhs->dtype], // dtype_sizeof is a const size_t array giving sizeof(int8_t), sizeof(int16_t), etc.
 &from, // source
 dtype_sizeof[rhs->dtype]);

然后从通用稀疏矩阵乘法函数中调用它,可以这样声明:

void generic_sparse_multiply(size_t* ija, size_t* ijb, void* a, void* b, int dtype_a, int dtype_b);

这将使用 SetFuncs[dtype_a][dtype_b]例如,引用正确的赋值函数。那么,缺点是您可能必须实现一大堆 - IncrementFuncs、DecrementFuncs、MultFuncs、AddFuncs、SubFuncs 等 - 因为您永远不知道会出现什么类型。

最后,我的问题是:

  1. 拥有巨大的多维函数指针常量数组的成本是多少(如果有的话)?大型库或可执行文件?加载时间慢?等等
  2. 是否使用 IncrementFuncs 等泛型, SetFuncs等(可能都依赖于 memcpy 或类型转换)对编译时优化存在障碍?
  3. 如果要使用如上所述的 switch 语句,现代编译器会优化这些语句吗?还是每次都会对其进行评估?

我意识到这是一系列极其复杂的问题。

如果您可以简单地向我推荐一个好的资源并且不想直接回答,那完全没问题。在发布此内容之前,我广泛使用了 Google,但不太确定要使用哪些搜索词。

最佳答案

首先,尝试降低函数的复杂性。您应该能够有一个类似的声明

Result_t function (Param_t*);

其中 Param_t 是一个包含您传递的所有内容的结构。要使用泛型类型,请在结构中包含一个枚举,告知使用哪种类型,并在该类型中包含一个 void*。


1.What is the cost, if any, of having enormous multi-dimensional const arrays of function pointers? Large library or executable? Slow load time? etc.

绝对更大的可执行文件。加载时间取决于代码适用的系统。如果是基于 RAM 的系统(PC 等),则加载时间可能会增加,但不会对性能产生任何重大影响。当然,这取决于“巨大”有多大:)

2.Does use of generics like IncrementFuncs, SetFuncs, etc. (which all probably depend on memcpy or typecasts) present barriers to compile-time optimization?

可能不会,编译器可以优化的东西太多了。在处理 C 中的通用数据类型时,它通常最终归结为 memcpy(),它本身有望实现与复制一样快。

3.If one were to use switch statements as described above, would these be optimized out by modern compilers? Or would they be evaluated every single time?

讽刺的是,编译器可能会将其优化为函数指针数组之类的东西。然而,编译器可能无法预测数据的性质,特别是如果它是在运行时设置的。

关于c - C 中泛型函数与函数指针数组相比有何缺点?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9289272/

相关文章:

c - 如何将 C 中分配的字符串返回给 Ada 并在 Ada 中释放它?

c - gcc 选项 : pkg-config --libs --cflags gtk+-3. 0

c - 未知类型名称 '__uint64_t'

c++ - 公共(public)图书馆的 char 或 std::string 数组?

c++ - 如果初始化变量,编译器的性能会受到什么影响?

c - 我的程序经常崩溃

c - 如何不断刷新屏幕,实时更新

c - C 中的 Shell 排序未给出所需结果

C++ 程序无法构建。获取架构错误的 undefined symbol

c++ - 用 clang++ 生成的可执行文件变得疯狂