【问题标题】:How much does speed decrease with called argument size? (C/C++)速度会随着被调用的参数大小而降低多少? (C/C++)
【发布时间】:2012-03-22 21:11:11
【问题描述】:

我必须做出一个决定,这对我的应用程序的未来开发非常重要,所以它必须是完美的。

实际问题:

为方便起见,最好多留 20 个空字节,但这似乎效率极低。当我将 50 字节的大结构传递给函数时,我的程序会受到多远的影响? =P

我为什么要问:

我运行 64 位。这意味着 8 每个变量的字节数。
目前stuff_s3*8=24 字节大。

  • 只需再添加三/四个变量,但会增大结构体大小并减慢函数调用速度。
  • 或者选择一些相当复杂但安全的额外字节(和 RAM 空间)。

我将使用这些stuff_ss 在树中包含任何类型的数据对象,它们可以处理对象或模块(即特定的东西,忽略模块)。数据对象的大小可以不同......对于通常使用的结构,它们甚至可能是几千兆字节。现在我不希望每个该死的结构都携带额外的 50 个字节。如果那可能只有一个字节,或者不超过 8... =(

我有一个结构:

//
// stuff reference (either module or object)
//
struct stuff_s
{
    char s; // stuff: 'm' || 'o'
    union {
        struct {
            mfunc_t *fs;
            ppackage (*knockf)(ppackage p);
        } m;
        struct {
            void *h;
            size_t s;
            // HERE should the additional variables go.
        } o;
    };
};

【问题讨论】:

  • 按引用而不是按值传递,这完全不是问题。 (如果你不能通过引用传递,那么你不应该标记你的 C 问题c++。)
  • 即使您使用的是 C,也可以传递指针而不是按值传递结构。
  • 一如既往的性能:衡量而不是猜测。特别是如果您需要一个完美的答案。
  • 过早的优化是万恶之源——Donald Knuth
  • “我必须做出一个决定,这对我的应用程序的未来开发非常重要”:这就是项目在开始之前就死掉的方式。经理们想要做出这样的决定,却没有意识到软件开发的全部意义在于灵活性。

标签: c struct stack


【解决方案1】:

使用引用。

C++:

void foo(stuff_s &what)
{
  what.o.s = 4;
}

int main()
{
  stuff_s SetToFour;
  foo(SetToFour);
}

C:

void foo(stuff_s *what)
{
  if (what == NULL) return;
  what->o.s = 4;
}

int main(int argc, char *argv[])
{
  stuff_s SetToFour;
  foo(&SetToFour);

  return 0; /* Because it's main in a strict C compiler. Ignore this. */
}

关于你的程序设计,如果你的数据真的非常不可知,我会推荐使用一个 void * 指针和一个对象类型标签,或者从一个基类派生一个类。

C++:

class base {
  public:
    int a;
    virtual void do_whatever();
    virtual int get_type() = 0; // Grab our type ID
};

class derived : public base {
  public:
    int b;
    float c;
    char *d;
    virtual void do_whatever() { c = 4.0F; }
    virtual int get_type() { return 1; }
};

struct stuff_s {
//    ...in o
  base *futs;
}

// Later on...
stuff_s foo;
switch (foo.o.futs->get_type()) {
  case 0: // Base class
    break;
  case 1: // Class type 'derived'
    derived *a = dynamic_cast<derived *>(foo.o.futs); // Dynamic cast lets us take a pointer to a base type and make it into a pointer to a derived type. Using our tag ID that we get from the virtual function, we can determine exactly which type to do.
    a->b = 4;
    break;
}
// This is roughly the same amount of code as the C version would use

如果你真的需要,我会提供一个 C 示例,但这太长了

编辑:我在下面看到它必须与 C 兼容。因此,我将扩展以包括最后一个代码的 C 等效项,还包括通过引用传递。请记住,这是概念性的:它会编译但不是很有用。

C:

/* This is a generic object. It has a type ID and a pointer, that is all that is needed. It can hold anything. */
struct generic_holder {
  int type;
  void *ptr;
};

/* This is an example struct it may point to. */
struct holder_one {
  int a;
  int b;
  float c;
  char *d;
  int change_this;
};

/* This is another example struct. */
struct holder_two {
  char best[50];
  char worst[50];
  int change_this;
};

struct stuff_s {
/*    ...in .o */
  generic_holder data;
};

/* Forward-declaration */
void set_to_four(stuff_s *foo);

/* Later on... */
int main(int argc, char* argv[])
{
  stuff_s foo;
  holder_two test_struct = { "C++", "Lisp", 0 };  // Best, worst. Haha.

  foo.o.data.ptr = (void *)&test_struct; /* This makes it into a "generic pointer" by casting it to void */

  foo.o.data.type = 2; /* = 2 because we are using holder_two */

  /* We're going to use our function to set data in a generic object, passing by reference. This would work equally well on something of type holder_one, and can be expanded for data types you haven't thought of. */
  set_to_four(&foo);

  return 0;
}

/* This function will take a generic object and set the 'change_this' variable to 4 */
void set_to_four(stuff_s *foo)
{
  holder_one *ptr1;
  holder_two *ptr2;

  if (foo == NULL) return;            /* Obsessively check for invalid pointers */
  assert(foo.o.data.ptr != NULL);     /* Some people prefer to do checks only in debug, for speed. This does the same thing but only if debug is on. And it stops the program when it gets there if there is an error. It evaluates to nothing if it's a release build. */

  switch(foo.o.data.type) {
    case 1:
      ptr1 = (holder_one *)foo.o.data.ptr;
      ptr1->change_this = 4;
      break;
    case 2:
      ptr2 = (holder_two *)foo.o.data.ptr;
      ptr2->change_this = 4;
      break;
    default:
      break;
  }
}

【讨论】:

  • 为什么不直接使用if (what) what-&gt;o.s = 4;
  • @hochl NULL 不保证为 0,即使在几乎每个实现中都是。
  • @OrgnlDave - true,但 if(ptr) 当且仅当 ptr 不为 NULL 时才为 true。
  • @Robᵩ这真的有保证吗?如果是这样,我有一些大扫除。你能指点我一些文档吗?
  • @OrgnlDave:对于 C++03,请参阅 6.4/3“作为表达式的条件的值是表达式的值,隐式转换为布尔值”。空指针值转换为 false 任何其他指针值都转换为 true (C++03 4.12)。对于 C,请参见 C99 6.8.4.1/2:“如果表达式比较不等于 0,则执行第一个子语句”。所以任何if 条件本质上等同于if ((condition) != 0)。而且由于根据定义0 是空指针常量,因此您可以对空指针进行隐式测试。
【解决方案2】:

首先,如果你在你的结构体中加入一些访问器,你明天就可以安全地改变结构体的实现,而不会影响任何其他代码。

例如,

struct MyStruct
{
    int getVal() {return val;}
    int val;
}

至于问题的另一半,恐怕没有直截了当的答案。您应该尝试两者并自己测试一下。

我注意到你认为拥有额外的字节“更容易”,如果你在谈论打字,这不是真的,因为你可以使用位域,即

struct MyStruct
{
    unsigned val1 : 10; // val1 is 10 bits long
    unsigned val2 : 3;  // val2 is 3 bits big
};

// access values
MyStruct s;
s.val1 = ...;

此外,如果您将 const ref 传递给函数调用,而不是按值传递,函数调用不会因传入的大小而“减慢”速度。那就是:

void foo( const MyStruct & myStruct)
{ 
//eg: myStruct.val1 = 10; // your code here 
}

至于你的新“实际问题”

除非它在某个内部循环中,否则它不会对速度产生太大影响。经验法则是通过 const ref,并在您的程序实际上太慢时考虑优化

【讨论】:

  • 非常感谢,但这一切都需要与 C 兼容。 =) 另外,位字段使用起来不是很痛苦吗?
  • 完全没有,它很无痛
  • @imacake 哦,C 兼容吗?如果您想要通用基类/派生事物的 C 版本,我将扩展我的答案。
猜你喜欢
  • 2019-02-22
  • 2020-01-31
  • 1970-01-01
  • 2023-02-23
  • 2012-04-25
  • 2019-04-14
  • 2014-01-08
  • 2022-08-06
  • 2018-09-12
相关资源
最近更新 更多