在C / C ++中有效地处理函数称为百万次

时间:2015-04-22 20:48:04

标签: c++ c algorithm optimization

对于编程问题,我是C和stackoverflow的新手。我已经对我的问题进行了一些谷歌搜索,我无法直接提出解决这个问题的信息。但是,我也可能只是对这个主题这么新,我不确定哪些术语甚至适合搜索。如果这是一个常见问题,我很抱歉。

我的计划正在实施科学计算。特别是,它主要涉及获取给定的坐标和给定的力并基于一系列计算更新所有内容。为了得到结果,我经历了大约一百万次,所以我担心尽可能高效。特别是我发现我有3种类型的变量

  1. 不变且不变的变量
  2. 从迭代变为迭代的临时变量 并且只是在计算中用作占位符
  3. 每次迭代后更改的数据,然后继续执行 到下一个
  4. 声明这些不同变量的最有效方法是什么?在我的天真中,我很想将一切都声明为全局变量,我很确定这对于类型(1)和(3)的变量是有意义的。但是对于类型(2)我不确定。如果我调用一个函数一百万次,并且每次调用它时都会初始化一个临时变量,那么这会浪费更多的时间,而不是我有一个它改变的全局临时值吗?

6 个答案:

答案 0 :(得分:3)

  

如果我调用一个函数一百万次并且每次调用它   初始化一个临时变量,这比我更浪费时间   有一个它改变的全局临时值吗?

不,甚至可能情况恰恰相反。在堆栈上创建临时变量。

分配堆栈变量没有性能成本
调用函数时,无论如何都必须移动堆栈指针,以便为函数参数和其他东西腾出空间。现在,如果必须分配一个额外的变量,那么必须做的就是增加堆栈指针的移动量 与Java不同,您可以保留未初始化的变量,并在需要时直接存储最终值,这样就不会产生初始化成本。

Locality of reference
当你使用堆栈变量时,也会更好。这意味着函数使用的值彼此更接近,这有助于处理器更有效地缓存数据。缓存按行通常为64字节组成,这意味着彼此相邻的两个32位值比两个分散的值更有效。
发生分页时,空间局部性尤其重要,因为从永久存储加载页面会产生巨大的性能成本。因此,最好将特定时间所需的所有变量紧密地存储在一起,以便它们适合一页。

答案 1 :(得分:2)

粗略地说,如果在循环中调用函数,将变量存储为全局或局部不应该改变任何内容。实际上,汇编代码应该很可能是相同的(sub esp, SomeValue);唯一的区别是SomeValue可能会有所改变;这对延迟没有任何改变;到时钟周期精度。

实际上,将变量设置为全局甚至可能会使程序运行速度变慢,因为编译器将无法理解您的代码并对其进行优化。

所以,如果你在一个循环中调用你的函数,不要打扰全局与本地,只需按常规方式:变量尽可能本地化。

但是,如果您的百万次调用是递归调用,那么内存很少,您应该尽力保存它,然后应该采用全局方式。

无论如何,你很可能有更好的优化等待在代码的某个地方进行!

答案 2 :(得分:2)

类型1变量应该是全局的

const double ABC = 1.234567;

const对性能很重要。

类型3不能是全局的。它们需要在每个函数调用中传递

void f1(double x)
{
    x = x + ABC;
    f2(x);
}

如果您需要f2来更改x并获得更改后的值,请改为使用引用:

void f1(double& x)
{
    x = x + ABC;
    f2(x);
}

类型2在函数中只是本地的:

void f1(double x)
{
    double y;
    x = x + ABC;
    f2(x);
}

请务必检查编译器优化标记以获得最佳性能。

答案 3 :(得分:1)

您正在讨论C语言中的两个不同概念:范围和范围。

  • 范围与变量持续的时间有关。全局变量总是全局范围,这意味着它们从程序开始到程序结束,从整个程序生命期间保留其赋值到赋值。局部变量通常具有局部范围,这意味着变量在其定义点处被创建/分配,并且其寿命延伸到包含它的内部块的末尾。通常,编译器会根据块的本地存储以及参数的大小和数量(在功能块的情况下)将每个函数/块条目调用的堆栈指针寄存器推进固定量,因此添加局部变量只会更改要添加到SP寄存器的常量值,不会产生额外的执行惩罚。这也是在进入时未初始化局部变量的原因。

  • 范围与变量的可见性有关。每个对象有三个不同的范围:全局范围表示可以在程序中的任何位置访问变量; 文件范围表示变量仅在定义它的文件模块中可见,但在其他地方不可见; 本地范围表示从声明点到块结尾可见变量。对于全局范围,您始终可以使用其名称来引用它。相反,局部范围意味着变量名仅在包含其声明的内部块中可用(这是定义对象的内部{}

    < / LI>

当您使用单词static时,它意味着两件事,具体取决于您使用它的位置:

  • 如果您在任何区块之外使用它,static表示文件范围(与extern相反,这意味着全局程序范围)默认情况下,变量(和数据)是文件范围和函数(和代码)是全局范围的(使用函数,您必须使用static使它们仅在声明文件中可见)。对于文件范围,变量仅在声明它的文件中可见,但它也具有全局范围。在任何块之外(在文件级别)定义的所有对象都具有全局范围(它们从程序开始到程序结束)
  • 如果在块中使用static,则表示此变量具有局部范围但具有全局范围。如果在块中使用extern,则意味着对象是以这种方式定义的,但在其他地方(它具有全局范围和范围,我想在本地使用它)

只是为了完成,extern的出现不会使编译器分配变量,它只会通知它有一个变量(在其他地方定义,具有全局范围和范围)具有此类型和名称并且可以从这里访问,本地(或文件范围,如果在任何块之外使用)你将不得不在某个地方(在其他文件中或在此中)放置一个声明而不使用单词extern来编译器为它分配空间。对于程序全局变量,您必须在它实际驻留的文件中定义两次(一个带有extern关键字和一个没有它的定义---可能还有一个初始化程序),并声明为extern在将要使用它的所有其他文件中。

修改

对于在程序生命周期内未发生变化的变量,最好将它们定义为const。这样,您允许编译器记住常量值并使用其值,而不是在程序周围引用它。将全局值设为const有很大好处。如果您从未通过引用(通过const运算符)使用它们,&甚至没有分配内存。

答案 4 :(得分:0)

首先,不要猜测问题是什么(这就是你正在做的事情:)

其次,让正在运行的程序告诉你需要时间。 在你的评论中,你说它需要几个小时。 我使用的方法是在调试器下运行它,中断它,看看它在做什么。 如果你多次这样做,你会发现中断强烈地引起了问题。

使用编译器优化 off 执行此操作。当您尽可能快地完成它时,请打开编译器的优化器。 如果你在那里有愚蠢的东西,你需要清理,优化器不会为你清理它。它只会让你更难找到。

This post显示了我使用科学软件的一些经验。 它还给出了该方法如此有效的统计理由。

我过去发现的事情花费了很长一段时间a)用相同或几乎相同的参数反复调用explog等函数,b)调用通用函数,如矩阵乘法或cholesky变换,可以用专门的例程替换。

答案 5 :(得分:0)

  

如果我调用一个函数一百万次并且每次调用它   初始化一个临时变量,这比我更浪费时间   有一个它改变的全局临时值吗?

不一定,因为优化编译器可以将这些变量直接放入寄存器,而不是使用堆栈。 此外,全局临时值可以存储在寄存器中,如下所示。在某些体系结构中,例如x64,它有很多寄存器,可以提示编译器将常量和其他变量永久地分配给某些寄存器。

足够小的数据可以类似地放在xmm0..xmm7上。

register long int a __asm__("r14");

int foo(int b)
{
    a = b * 2 + a;
    return a;
}

int bar(int c)
{
    a = a * 5 - c;
    return a;
}

int init(int i) { a = i; }
// in a separate file
int main(){
  init(3);
  printf("%d %d\n", foo(1), bar(2));
}

反汇编显示,&#34;全球&#34;变量确实存储在寄存器中,仅在main

中分配一次
foo:  leal (%rdi,%rdi), %eax
      cltq
      addq %r14, %rax
      movq %rax, %r14
      ret

bar:  leaq (%r14,%r14,2), %rax
      movslq %edi, %rdi
      subq %rdi, %rax
      movq %rax, %r14
      ret

main:  ...
      movl $3, %r14d