返回值或直接在给定的输出参数上写它们会更好吗?

时间:2012-08-09 03:16:19

标签: performance function memory stack maintainability

我正在阅读此pull request on dablooms

  

最重要的是,Murmur不会在堆栈/寄存器上返回哈希值,而是将其直接写入提供的缓冲区。这使得使用大量随机数据填充bloom->哈希缓冲区非常容易,并逐步执行模块化。

for (i = 0; i < bloom->nsalts; i++, hashes += 4) {
    MurmurHash3_x64_128(key, key_len, bloom->salts[i], hashes);
    hashes[0] = hashes[0] % bloom->counts_per_func;
    hashes[1] = hashes[1] % bloom->counts_per_func;
    hashes[2] = hashes[2] % bloom->counts_per_func;
    hashes[3] = hashes[3] % bloom->counts_per_func;
}

我最近注意到一些库(至少在C ++中,我的知识非常有限,因为我是一个新手)似乎不会返回值,而是期望一个输出参数,他们将在其上写入结果。我更习惯看到这样的事情:

Matrix leftOperand = { /* some values */ };
Matrix rightOperand = { /* some values */ };
Matrix result;
result = multiplyMatrices( leftOperand, rightOperand );

其中resultreturn的{​​{1}}值。但是在我最近的项目中学习使用OpenGL,GLEW和freeglut,我经常看到这样的调用:

multiplyMatrices

我理解它的作用,但我觉得这个符号很奇怪。但是我越来越常见,当我看到上述拉动请求的作者“赞扬”时,我认为可能有充分的理由这样做。

因此,在我寻求编写不那么糟糕和有臭味的代码的过程中,我想知道这是不是很好的做法。我认为它必须是出于性能原因,但我不清楚是否从堆栈中推送/弹出比直接写入给定的内存地址要慢。

我正在寻找一些关于使用单一方式而不是另一种方式的原因的指南。

1 个答案:

答案 0 :(得分:1)

我们在C中采用一个简单的例子:

struct X
{
    /* a lot of data */
};

struct X func1(/*args*/)
{
    struct X result;
    /* fill in result */
    return result;
}

int func2(/*args*/, struct X *result)
{
    if (result == NULL)
        return -1;
    /* fill in *result */
    return 0;
}

/* in the code */
struct X x;
x = func1(/* args */);
func2(/* args */, &x);

func1,在自己的堆栈框架上填写必要的数据,然后行x = func1(...)将其复制到xfunc2获取指向x的指针作为其参数并将其填入。

正如您在此处看到的,使用func1有两个缺点:

  1. 除非将x设置为无效状态(对于所有结构可能不存在,例如矩阵),否则无法报告错误。
  2. 您必须复制大量数据。
  3. 在某些情况下,根本不可能出现错误,因此第1点可能不一定成立。然而,如果您的结构很大,则第2点是性能杀手。

    现在用C ++来想象同样的东西,除了struct X现在是class X,里面有一个复制构造函数和一堆std::stringstd::list等。使用func2的性能提升变得更加重要,因为复制类X的对象现在需要调用多个复制构造函数,深度复制所有内容,然后在func1内破坏本地对象。 (使用C ++ 11,这样会更糟糕(与C中一样糟糕),因为可以移动对象。)

    人们想要使用func1的唯一原因是为了便于阅读和编写。例如,比较一下:

    string fix(const string &s);
    void print(const string &s);
    
    string str;
    print(fix(str));
    

    vs this:

    void fix(const string &s, string &res);
    void print(const string &s);
    
    string str;
    string fixed;
    fix(str, fixed);
    print(fixed);
    

    第一个显然更容易理解,而第二个更有效。请注意,在像C这样的语言中,相当于第一个代码可能会导致内存泄漏,因此甚至无法实现。

    所以问题归结为你更关注这两者中的哪一个。如果你和一个Java人谈话,他们可能会告诉你“性能差异很小,你甚至都不会注意到它”。如果你和一个C人交谈,他们可能会告诉你“某些架构的性能差异可能很小,但这并不意味着你可以去做不必要的事情。”

    在您提到的帖子中,程序员正在尝试提高库的性能。他正在改进的功能是“填充缓冲区”。这会将缓冲区作为参数传递:

    • 您无需复制缓冲区的内容
    • 您可以提供自己的缓冲区,无论是静态还是动态分配。
      • 此功能不需要每次都分配内存
      • 您可以重用缓冲区而无需重新分配内存
      • 如果是静态分配,则不需要释放缓冲区
    • 您可以部分填写更大的缓冲区