【问题标题】:What are the performance impacts that Expects(cond) of the GSL imposes on the runtime?GSL 的 Expects(cond) 对运行时的性能影响是什么?
【发布时间】:2019-02-06 13:58:40
【问题描述】:

CPP 核心指南建议在函数中使用 GSL 中的 Expects(expression) to state preconditions 和 Ensures(expression) to state postconditions。

例子:

int area(int height, int width)
{
    Expects(height > 0 && width > 0);            // good
    if (height <= 0 || width <= 0) my_error();   // obscure
    // ...
}

这些宏对性能有何影响? 是否就像用 if 检查条件然后抛出异常一样? 调试和非调试模式有区别吗? IE。如果应用是作为发行版构建的,宏是否也处于活动状态?

我问是因为我正在考虑将其用作一种良好的做法,即尝试,如果没有特定的理由反对它,只是说明可能的前置/后置条件(假设使用不同的类型作为参数是不明智的)使用 GSL 中的这些宏。

【问题讨论】:

    标签: c++


    【解决方案1】:

    这些宏在gsl_assert 标头中定义。

    以下是相关代码:

    #define Expects(cond) GSL_CONTRACT_CHECK("Precondition", cond)
    

    #if defined(GSL_THROW_ON_CONTRACT_VIOLATION)
    
    #define GSL_CONTRACT_CHECK(type, cond)                                                             \
        (GSL_LIKELY(cond) ? static_cast<void>(0)                                                       \
                          : gsl::details::throw_exception(gsl::fail_fast(                              \
                                "GSL: " type " failure at " __FILE__ ": " GSL_STRINGIFY(__LINE__))))
    
    #elif defined(GSL_TERMINATE_ON_CONTRACT_VIOLATION)
    
    #define GSL_CONTRACT_CHECK(type, cond)                                                             \
        (GSL_LIKELY(cond) ? static_cast<void>(0) : gsl::details::terminate())
    
    #elif defined(GSL_UNENFORCED_ON_CONTRACT_VIOLATION)
    
    #define GSL_CONTRACT_CHECK(type, cond) GSL_ASSUME(cond)
    
    #endif // GSL_THROW_ON_CONTRACT_VIOLATION
    

    如您所见,这取决于您选择如何处理违反合同的行为。在前两种情况下,您将支付分支费用以及抛出异常或调用std::terminate。

    如果定义了GSL_UNENFORCED_ON_CONTRACT_VIOLATION,宏将扩展为GSL_ASSUME:

    //
    // GSL_ASSUME(cond)
    //
    // Tell the optimizer that the predicate cond must hold. It is unspecified
    // whether or not cond is actually evaluated.
    //
    #ifdef _MSC_VER
    #define GSL_ASSUME(cond) __assume(cond)
    #elif defined(__GNUC__)
    #define GSL_ASSUME(cond) ((cond) ? static_cast<void>(0) : __builtin_unreachable())
    #else
    #define GSL_ASSUME(cond) static_cast<void>((cond) ? 0 : 0)
    #endif
    

    在这种情况下,可能会或可能不会评估条件,具体取决于编译器。它还可以使您的代码更快,因为由于这些假设,编译器可能能够更积极地优化。

    【讨论】:

      【解决方案2】:

      断言没有停用,条件将一直被测试:

      #if defined(__clang__) || defined(__GNUC__)
      #define GSL_LIKELY(x) __builtin_expect(!!(x), 1)
      #define GSL_UNLIKELY(x) __builtin_expect(!!(x), 0)
      #else
      #define GSL_LIKELY(x) (!!(x))
      #define GSL_UNLIKELY(x) (!!(x))
      #endif
      

      然后是的,它会根据您的设置引发异常甚至终止:

      #if defined(GSL_THROW_ON_CONTRACT_VIOLATION)
      
      #define GSL_CONTRACT_CHECK(type, cond)                                                             \
          (GSL_LIKELY(cond) ? static_cast<void>(0)                                                       \
                            : gsl::details::throw_exception(gsl::fail_fast(                              \
                                  "GSL: " type " failure at " __FILE__ ": " GSL_STRINGIFY(__LINE__))))
      
      #elif defined(GSL_TERMINATE_ON_CONTRACT_VIOLATION)
      
      #define GSL_CONTRACT_CHECK(type, cond)                                                             \
          (GSL_LIKELY(cond) ? static_cast<void>(0) : gsl::details::terminate())
      
      #elif defined(GSL_UNENFORCED_ON_CONTRACT_VIOLATION)
      
      #define GSL_CONTRACT_CHECK(type, cond) GSL_ASSUME(cond)
      
      #endif // GSL_THROW_ON_CONTRACT_VIOLATION
      

      如果设置了GSL_UNENFORCED_ON_CONTRACT_VIOLATION,则什么也不会发生。

      【讨论】:

        【解决方案3】:

        我有一种感觉,Expects 有点违背 C++ 的精神(正如我所理解的这种精神)......如果 C++ 更主流和类似 Java 可能真的很好,但对我来说,它最初的想法是提供最大性能(有时以降低安全性/增加复杂性/降低可读性为代价)。我认为我们可以在不牺牲性能的情况下实现这两个目标。对我来说,解决方案非常简单 - 自定义断言 (cassert) - 这是可以为发布版本打开或关闭的快速断言。

        示例实现(仅限 Windows):

        // cassert.h:
        #pragma once
        
        // Switch this macro off for maximum performance release builds:
        #define __RELEASE_WITH_ASSERTS__
        
        #ifndef cassert
        
        #if defined(_DEBUG) || defined(__RELEASE_WITH_ASSERTS__)
        void __cassert(bool val, const char* szFile, int line, const char* szDesc);
        #define cassert(a) ((a) ? ((void)1) : __cassert(a, __FILE__, __LINE__, _CRT_STRINGIZE(#a)))
        #else
        #define cassert(a)
        #endif
        
        #endif
        
        // cassert.cpp
        #if defined(_DEBUG) || defined(__RELEASE_WITH_ASSERTS__)
        void __cassert(bool val, const char* szFile, int line, const char* szDesc)
        {
            if (!val)
            {
                std::string sIgnore;
                int mode;
        
                #if defined(_DEBUG)
                sIgnore = "\n\nIgnore assertion?";
                mode = MB_YESNO;
                #else
                sIgnore = "";
                mode = MB_OK;
                #endif
        
                char szLine[16];
                _itoa(line, szLine, 10);
        
                std::string sAssertion = std::string("Assertion failed in file ") + szFile + " at line " + szLine + "\n" + szDesc;
                Log(sAssertion.c_str());
        
                if (MessageBoxA(NULL, (std::string("Assertion failed in file ") + szFile + " at line " + szLine + "\n" + szDesc + sIgnore).c_str(), "Assertion failed", mode | MB_ICONINFORMATION) == IDNO)
                    _CrtDbgBreak();
            }
        }
        #endif
        

        这样的 cassert 甚至会使调试构建运行得更快,因为如果条件满足,则没有对 (c)assert 函数的实际调用(并且每个这样的调用都会非常繁重,因为 _CRT_STRINGIZE(#a) (条件为字符串)。

        【讨论】:

          猜你喜欢
          • 2021-04-29
          • 2015-12-03
          • 2010-09-22
          • 1970-01-01
          • 2010-09-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-01-17
          相关资源
          最近更新 更多