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

问题描述 投票:0回答:1

我正在编写一个具有多种存储类型('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=每个操作的 dtypes 数量,而函数指针数组将是 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,但不太确定要使用哪些搜索词。

c gcc function-pointers
1个回答
4
投票
首先,尝试降低函数的复杂度。您应该能够有一个类似的声明

Result_t function (Param_t*);

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


1.拥有巨大的多维函数指针常量数组的成本是多少(如果有的话)?大型库或可执行文件?慢的 加载时间?等等

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

2.使用像 IncrementFuncs、SetFuncs 等这样的泛型(这些都可能依赖于 memcpy 或类型转换)是否会造成障碍 编译时优化?

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

3.如果要使用如上所述的 switch 语句,现代编译器会优化这些语句吗?或者他们会被评估 每一次?

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

© www.soinside.com 2019 - 2024. All rights reserved.