问题 “默认”开关案例会干扰跳转表优化吗?


在我的代码中,我习惯于编写包含类似下面的断言的后备默认情况,以防止在语义改变的情况下忘记更新开关

switch(mode) {
case ModeA: ... ;
case ModeB: ... ;
case .. /* many of them ... */
default: {
  assert(0 && "Unknown mode!");
  return ADummyValue();
}
};

现在我想知道人工回退检查默认情况是否会干扰跳表生成?想象一下,“ModeA”和“ModeB”等是连续的,所以编译器可以优化成一个表。由于“默认”情况包含一个实际的“返回”语句(因为断言将在发布模式中消失,并且编译器会抱怨缺少返回语句),编译器似乎不太可能优化默认分支。

处理这个问题的最佳方法是什么?有些朋友建议我用空指针取消引用替换“ADummyValue”,以便编译器在存在未定义的行为时可以省略警告丢失的return语句。有没有更好的方法来解决这个问题?


9475
2017-11-25 15:54


起源

鉴于 assert,它可能是最好的 throw 要么 terminate 而不是 return。 - Mooing Duck


答案:


如果您的编译器是MSVC,则可以使用 __assume 内在的: http://msdn.microsoft.com/en-us/library/1b3fsfxw(v=VS.80).aspx


2
2017-11-25 16:12



太好了!如果GCC只存在那个:) - Johannes Schaub - litb
@Johannes Google是你的朋友:) en.chys.info/2010/07/counterpart-of-assume-in-gcc - ruslik


至少在我看过的编译器中,答案通常是否定的。他们中的大多数会像这样编译一个switch语句,代码大致相当于:

if (mode < modeA || mode > modeLast) {
    assert(0 && "Unknown mode!");
    return ADummyValue();
}
switch(mode) { 
    case modeA: ...;
    case modeB: ...;
    case modeC: ...;
    // ...
    case modeLast: ...;
}

3
2017-11-25 16:07



多可惜。我必须牺牲速度验证:( - Johannes Schaub - litb
@Johannes:函数指针数组的工作方式与跳转表类似吗?从数组中查找函数指针并调用它。 (跳转表由一个寄存器跳转和一个固定跳转组成;函数指针数组是一个寄存器加载和一个寄存器跳转,我想?) - rwong


如果你使用“默认”(哈!) <assert.h>,无论如何,定义都与NDEBUG宏相关联,所以也许只是

    case nevermind:
#if !defined(NDEBUG)
    default:
        assert("can" && !"happen");
#endif
    }

2
2017-11-25 17:19



这是我推荐的。它不会牺牲速度,您仍然可以测试未计入的类型,并且您仍然可以获得编译器警告 - Trevor Hickey


我只看到一个解决方案,以防优化实际上受到干扰:臭名昭着的“#ifndef NDEBUG”绕过默认情况。这不是最好的技巧,但在这种情况下很明显。

顺便说一句:您是否已经了解了编译器的默认情况?


1
2017-11-25 16:00



还没看过生成的代码。 - Johannes Schaub - litb
@Johannes:好吧,至少Jerry Coffin做到了。 - stefaanv


如果你有一个永远不应该达到的状态,那么你应该杀死程序,因为它刚刚达到意想不到的状态,即使在发布模式下(你可能只是更外交,实际上保存用户数据并做其他所有好东西在下降之前)。

除非您实际测量(使用分析器)您需要它们,否则请不要过度使用微优化。


1
2017-11-25 16:01



但我不想在发布模式下检查无法访问的状态。我的假设是代码中没有错误。当然,这很可能是一个错误的假设,但我似乎需要摆脱对无法访问状态的检查(这是非常关键的性能:脚本语言运行时的名称查找例程,隐式转换例程等)。 - Johannes Schaub - litb


处理此问题的最佳方法是不禁用断言。这样你也可以留意可能的错误。有时候,应用程序崩溃时会有一个好消息来解释究竟发生了什么,然后继续工作。


1
2017-11-25 16:06





使用编译器扩展:

// assume.hpp
#pragma once

#if defined _MSC_VER
#define MY_ASSUME(e) (__assume(e), (e) ? void() : void())
#elif defined __GNUC__
#define MY_ASSUME(e) ((e) ? void() : __builtin_unreachable())
#else   // defined __GNUC__
#error unknown compiler
#endif  // defined __GNUC__

-

// assert.hpp
#include <cassert>
#include "assume.hpp"

#undef MY_ASSERT
#ifdef NDEBUG
#define MY_ASSERT MY_ASSUME
#else   // NDEBUG
#define MY_ASSERT assert
#endif  // NDEBUG

0
2018-01-14 13:00