重复调用的函数中的静态常量与常量

时间:2009-08-22 06:22:42

标签: c++ static const

我只是想知道如何

void test()
{
   const static int ABC = 12;
   cout << ABC;
}

不同
void test()
{
   const int ABC = 12;
   cout << ABC;
}

如果在程序执行时重复调用此函数?我的意思是,有任何性能差异吗?或者你是否应该选择一个而不是另一个?

8 个答案:

答案 0 :(得分:51)

有几件事会影响答案:

  • 首先,只要值const,它几​​乎肯定会在任何情况下都被优化。这意味着生成的代码很可能是相同的。
  • 其次,static成员存储在其他位置,这意味着更少的位置,可能还有缓存未命中。
  • 第三,初始化的成本取决于类型。在您的情况下,对于int,初始化的成本基本上是不存在的。对于更复杂的用户定义类型,它可能很大。

所以答案是,在简单到足以让编译器弄清楚并优化的情况下,它会使区别。在你的例子中几乎可以肯定的是。

只要变量具有易于构造且便宜的类型,则首选非静态以避免缓存未命中。

如果构造的类型很昂贵,可能想要使用static

当然,最后,最重要的是:

不要相信我们的猜测。如果您担心绩效,那么只有一个正确的行动方案:

  • 测量它,以验证它确实是一个问题
  • 衡量每种可能解决方案的绩效
  • 选择能带来最佳效果的解决方案。

答案 1 :(得分:2)

理论上,前一种方法稍微好一点,因为它只对变量进行一次实例化。

然而,编译器将简单地删除“const”行并替换“cout&lt;&lt; 12;”进入函数(当使用optimisations编译时,显然)。

答案 2 :(得分:2)

在第一次演员表中,ABC将在第一次函数调用时初始化一次。在第二种情况下,ABC每次都会被初始化。如果ABC是带构造函数的复杂类型,你会感觉到区别。它可以分配内存或初始化一些互斥锁。对于int,实践中没有区别。

根据C ++ 03标准3.7.1 / 2:

  

如果静态存储持续时间的对象具有初始化或具有副作用的析构函数,即使它看起来未被使用也不应被删除,除非类对象或其副本可以按照   12.8。

答案 3 :(得分:2)

您还可以通过使用编译器的汇编程序或类似的在线工具https://godbolt.org来了解编译器的功能,并查看生成的汇编代码。

值得一看,因为这个问题很重要。

基于您的问题的示例,考虑本地与静态类型声明和实例化,查看原语与“复杂”(甚至不是非常):

#include <iostream>

struct non_primitive
{
    int v;
    non_primitive(const int v_) : v(v_) {}
};

void test_non_static_const_primitive()
{
   const int ABC = 12;
   std::cout << ABC;
}

void test_static_const_primitive()
{
   const static int ABC = 12;
   std::cout << ABC;
}

void test_non_static_constant_non_primitive_global_struct() 
{
    const non_primitive s(12);
    std::cout << s.v;
}

void test_non_static_constant_non_primitive_local_struct() 
{
    struct local_non_primitive
    {
        int v;
        local_non_primitive(const int v_) : v(v_) {}
    };
    const local_non_primitive s(12);
    std::cout << s.v;
}

void test_static_constant_non_primitive_global_struct() 
{
    const static non_primitive s(12);
    std::cout << s.v;
}

void test_static_constant_non_primitive_local_struct() 
{
    struct local_non_primitive
    {
        int v;
        local_non_primitive(const int v_) : v(v_) {}
    };
    const static local_non_primitive s(12);
    std::cout << s.v;
}

编译器认识到它不必处理本地const复杂类型的任何内存链接,前四种情况的处理方式相同。看起来编译器不会尝试探索复杂类型的相对简单性以及在静态实例化中进一步简化的潜力。

由此产生的成本差异相对较大。

使用gcc7.2和-O3优化编译的汇编代码:

test_non_static_const_primitive():
  mov esi, 12
  mov edi, OFFSET FLAT:std::cout
  jmp std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
test_static_const_primitive():
  mov esi, 12
  mov edi, OFFSET FLAT:std::cout
  jmp std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
test_non_static_constant_non_primitive_global_struct():
  mov esi, 12
  mov edi, OFFSET FLAT:std::cout
  jmp std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
test_non_static_constant_non_primitive_local_struct():
  mov esi, 12
  mov edi, OFFSET FLAT:std::cout
  jmp std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
test_static_constant_non_primitive_global_struct():
  movzx eax, BYTE PTR guard variable for test_static_constant_non_primitive_global_struct()::s[rip]
  test al, al
  je .L7
  mov esi, DWORD PTR test_static_constant_non_primitive_global_struct()::s[rip]
  mov edi, OFFSET FLAT:std::cout
  jmp std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
.L7:
  sub rsp, 8
  mov edi, OFFSET FLAT:guard variable for test_static_constant_non_primitive_global_struct()::s
  call __cxa_guard_acquire
  test eax, eax
  mov esi, DWORD PTR test_static_constant_non_primitive_global_struct()::s[rip]
  je .L8
  mov edi, OFFSET FLAT:guard variable for test_static_constant_non_primitive_global_struct()::s
  mov DWORD PTR test_static_constant_non_primitive_global_struct()::s[rip], 12
  call __cxa_guard_release
  mov esi, 12
.L8:
  mov edi, OFFSET FLAT:std::cout
  add rsp, 8
  jmp std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
test_static_constant_non_primitive_local_struct():
  movzx eax, BYTE PTR guard variable for test_static_constant_non_primitive_local_struct()::s[rip]
  test al, al
  je .L14
  mov esi, DWORD PTR test_static_constant_non_primitive_local_struct()::s[rip]
  mov edi, OFFSET FLAT:std::cout
  jmp std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
.L14:
  sub rsp, 8
  mov edi, OFFSET FLAT:guard variable for test_static_constant_non_primitive_local_struct()::s
  call __cxa_guard_acquire
  test eax, eax
  mov esi, DWORD PTR test_static_constant_non_primitive_local_struct()::s[rip]
  je .L15
  mov edi, OFFSET FLAT:guard variable for test_static_constant_non_primitive_local_struct()::s
  mov DWORD PTR test_static_constant_non_primitive_local_struct()::s[rip], 12
  call __cxa_guard_release
  mov esi, 12
.L15:
  mov edi, OFFSET FLAT:std::cout
  add rsp, 8
  jmp std::basic_ostream<char, std::char_traits<char> >::operator<<(int)
_GLOBAL__sub_I__Z31test_non_static_const_primitivev:
  sub rsp, 8
  mov edi, OFFSET FLAT:std::__ioinit
  call std::ios_base::Init::Init()
  mov edx, OFFSET FLAT:__dso_handle
  mov esi, OFFSET FLAT:std::__ioinit
  mov edi, OFFSET FLAT:std::ios_base::Init::~Init()
  add rsp, 8
  jmp __cxa_atexit

答案 4 :(得分:1)

这取决于编译器。

在嵌入式软件中,静态const通常存储在闪存(即代码存储器)中,并且可以像普通变量一样直接访问,而无需进行初始化。

相反,非静态const可能将其存储在flash中,但const本身将像变量一样在堆栈上创建,并且像变量一样初始化。

如果这是编译器处理这些场景的方式,那么静态const更有效率,因为它既不需要堆栈分配也不需要初始化。

显然,非嵌入式编译器可能会对这些场景进行不同的处理。

答案 5 :(得分:0)

对于一个基本类型,例如一个整数值,那么除非你做了一些基准测试并考虑了各种权衡(例如初始化一个静态的),否则我会将使用静态作为“过早优化”。非零值通常需要表中的条目来指定加载代码时要设置的位置,大小和初始值。

除非您正在获取指向int的指针,并且在函数返回后它被取消引用,否则您不需要静态 - 让编译器进行优化。

如果在函数退出后取消引用了指向该值的指针,那么我会将其作为持久状态变量进行分类,并且最好在类或模块级别定义它以使其清晰。

答案 6 :(得分:-1)

我会选择第二个 - 它更具可读性。为什么不必要地添加一个关键字(静态),它不会真正为读取代码的人添加任何值。

答案 7 :(得分:-1)

静态const示例肯定会节省后续调用的执行时间,对于使用静态const的复杂对象构造来说要快得多,但我质疑是否需要将ABC限制为函数范围并从第1个函数引入函数行为的变化拨打以下电话。通常一个文件包含耦合的功能耦合函数,只需给出ABC文件范围并完成它。