使用VC++的__assume可以带来可衡量的性能提升吗?

Nei*_*ice 10 c++ performance compiler-optimization visual-c++

使用VC++可以获得可衡量的性能提升__assume吗?如果是这样,请在答案中张贴带代码和基准的证明.

关于__assume的稀疏MSDN文章:http://msdn.microsoft.com/en-us/library/1b3fsfxw( v = vs.100).aspx

文章提到在是利用__assume(0)使switch通过语句更快__assume(0)荷兰国际集团的default情况.我通过__assume(0)这种方式测量的性能没有增加:

void NoAssumeSwitchStatement(int i)
{
    switch (i)
    {
    case 0:
        vector<int>();
        break;
    case 1:
        vector<int>();
        break;
    default:
        break;
    }
}

void AssumeSwitchStatement(int i)
{
    switch (i)
    {
    case 0:
        vector<int>();
        break;
    case 1:
        vector<int>();
        break;
    default:
        __assume(0);
    }
}

int main(int argc, char* argv[])
{
    const int Iterations = 1000000;
    LARGE_INTEGER start, middle, end;
    QueryPerformanceCounter(&start);
    for (int i = 0; i < Iterations; ++i)
    {
        NoAssumeSwitchStatement(i % 2);         
    }
    QueryPerformanceCounter(&middle);
    for (int i = 0; i < Iterations; ++i)
    {
        AssumeSwitchStatement(i % 2);
    }
    QueryPerformanceCounter(&end);
    LARGE_INTEGER cpuFrequency;
    QueryPerformanceFrequency(&cpuFrequency);
    cout << "NoAssumeSwitchStatement: " << (((double)(middle.QuadPart - start.QuadPart)) * 1000) / (double)cpuFrequency.QuadPart << "ms" << endl;
    cout << "  AssumeSwitchStatement: " << (((double)(end.QuadPart - middle.QuadPart)) * 1000) / (double)cpuFrequency.QuadPart << "ms" << endl;
    return 0;
}
Run Code Online (Sandbox Code Playgroud)

圆形控制台输出,1000000次迭代:

NoAssumeSwitchStatement:46ms
AssumeSwitchStatement:46ms

Mat*_* M. 9

基准谎言.他们很少衡量你想要的东西.在这种特殊情况下,这些方法可能都是内联的,因此__assume只是多余的.

至于实际问题,是的,它可能有所帮助.交换机通常由跳转表实现,通过减小该表的大小或删除一些条目,编译器可能能够选择更好的CPU指令来实现switch.

在极端的情况下,它可以switch变成一个if (i == 0) { } else { }通常有效的结构.

此外,修剪死分支有助于保持代码整洁,较少的代码意味着更好地使用CPU指令缓存.

然而,这些是微观优化,它们很少得到回报:你需要一个分析器来指出它们,甚至它们可能很难理解要进行的特定转换(是__assume最好的?).这是专家的工作.

编辑:与LLVM一起使用

void foo(void);
void bar(void);

void regular(int i) {
  switch(i) {
  case 0: foo(); break;
  case 1: bar(); break;
  }
}

void optimized(int i)  {
  switch(i) {
  case 0: foo(); break;
  case 1: bar(); break;
  default: __builtin_unreachable();
  }
}
Run Code Online (Sandbox Code Playgroud)

请注意,唯一的区别是存在或不存在__builtin_unreachable()与MSVC类似的情况__assume(0).

define void @regular(i32 %i) nounwind uwtable {
  switch i32 %i, label %3 [
    i32 0, label %1
    i32 1, label %2
  ]

; <label>:1                                       ; preds = %0
  tail call void @foo() nounwind
  br label %3

; <label>:2                                       ; preds = %0
  tail call void @bar() nounwind
  br label %3

; <label>:3                                       ; preds = %2, %1, %0
  ret void
}

define void @optimized(i32 %i) nounwind uwtable {
  %cond = icmp eq i32 %i, 1
  br i1 %cond, label %2, label %1

; <label>:1                                       ; preds = %0
  tail call void @foo() nounwind
  br label %3

; <label>:2                                       ; preds = %0
  tail call void @bar() nounwind
  br label %3

; <label>:3                                       ; preds = %2, %1
  ret void
}
Run Code Online (Sandbox Code Playgroud)

并注意这里如何将switch语句regular优化为简单的比较optimized.

这映射到以下x86程序集:

    .globl  regular                  |      .globl  optimized
    .align  16, 0x90                 |      .align  16, 0x90
    .type   regular,@function        |      .type   optimized,@function
regular:                             |    optimized:
.Ltmp0:                              |    .Ltmp3:
    .cfi_startproc                   |            .cfi_startproc
# BB#0:                              |    # BB#0:
    cmpl    $1, %edi                 |            cmpl    $1, %edi
    je      .LBB0_3                  |            je      .LBB1_2
# BB#1:                              |
    testl    %edi, %edi              |
    jne     .LBB0_4                  |
# BB#2:                              |    # BB#1:
    jmp     foo                      |            jmp     foo
.LBB0_3:                             |    .LBB1_2:
    jmp     bar                      |            jmp     bar
.LBB0_4:                             |
    ret                              |
.Ltmp1:                              |    .Ltmp4:
    .size   regular, .Ltmp1-regular  |      .size   optimized, .Ltmp4-optimized
.Ltmp2:                              |    .Ltmp5:
    .cfi_endproc                     |      .cfi_endproc
.Leh_func_end0:                      |    .Leh_func_end1:
Run Code Online (Sandbox Code Playgroud)

请注意,在第二种情况下:

  • 代码更严格(更少说明)
  • 在所有路径上都有一个比较/跳转(cmpl/je)(而不是一个路径有一个跳转,一个路径有两个)

还要注意这是如此接近,以至于我不知道如何测量除噪音以外的任何东西......

另一方面,从语义上来说它确实表明了一个意图,尽管它assert可能更适合语义.

  • @NeilJustice:我添加了LLVM IR和x86程序集.我手边没有Visual Studio(并且不打算再次使用它)所以我使用了Clang的`__builtin_unreachable`.我没有对代码进行基准测试,JimR的答案在这一点上看起来非常详尽,而且......无论如何在现实世界的功能中它可能会被淹没在噪音中. (2认同)

Jim*_*imR 7

如果设置正确的编译器开关似乎确实有所不同......

三次跟随.没有优化,选择速度和选择大小.

此运行没有优化

C:\temp\code>cl /EHsc /FAscu assume.cpp
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86

assume.cpp
Microsoft (R) Incremental Linker Version 10.00.40219.01

/out:assume.exe
assume.obj

C:\temp\code>assume
NoAssumeSwitchStatement: 29.5321ms
  AssumeSwitchStatement: 31.0288ms

这是最大优化(/ Ox)注意/ O2在速度上基本相同.

C:\temp\code>cl /Ox /EHsc /Fa assume.cpp
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86

assume.cpp
Microsoft (R) Incremental Linker Version 10.00.40219.01
/out:assume.exe
assume.obj

C:\temp\code>assume
NoAssumeSwitchStatement: 1.33492ms
  AssumeSwitchStatement: 0.666948ms

此次运行是为了最小化代码空间

C:\temp\code>cl -O1 /EHsc /FAscu assume.cpp
Microsoft (R) 32-bit C/C++ Optimizing Compiler Version 16.00.40219.01 for 80x86
assume.cpp
Microsoft (R) Incremental Linker Version 10.00.40219.01
/out:assume.exe
assume.obj

C:\temp\code>assume
NoAssumeSwitchStatement: 5.67691ms
  AssumeSwitchStatement: 5.36186ms

请注意,输出汇编代码与Matthiu M.在使用速度选项时必须说的内容一致.在其他情况下调用了开关功能.