【问题标题】:Optimizations are killing my integer overflow checks in clang 6优化正在扼杀我在 clang 6 中的整数溢出检查
【发布时间】:2018-09-01 06:25:08
【问题描述】:

我有一些金融应用程序的定点实现。它基本上是一个包含在一个类中的整数,该类基于给定的小数位数N视为十进制数。该类是偏执狂并检查溢出,但是当我在发布模式下运行测试时,它们失败了,最后我创建了一个演示问题的最小示例:

#include <iostream>
#include <sstream>

template <typename T, typename U>
typename std::enable_if<std::is_convertible<U, std::string>::value, T>::type 
FromString(U&& str)
{
    std::stringstream ss;
    ss << str;
    T ret;
    ss >> ret;
    return ret;
}

int main()
{
    int NewAccu=32;
    int N=10;

    using T = int64_t;

    T l = 10;
    T r = FromString<T>("1" + std::string(NewAccu - N, '0'));
    if (l == 0 || r == 0) {
        return 0;
    }
    T res = l * r;
    std::cout << l << std::endl;
    std::cout << r << std::endl;
    std::cout << res << std::endl;
    std::cout << (res / l) << std::endl;
    std::cout << std::endl;
    if ((res / l) != r) {
        throw std::runtime_error(
                   "FixedPoint Multiplication Overflow while upscaling [:" + std::to_string(l) + ", " + std::to_string(r) + "]");
    }

    return 0;
}

Clang 6 会发生这种情况,我的版本是:

$ clang++ --version
clang version 6.0.0-1ubuntu2 (tags/RELEASE_600/final)
Target: x86_64-pc-linux-gnu
Thread model: posix
InstalledDir: /usr/bin

这很有趣,因为它是一个令人印象深刻的优化,但这会破坏我的应用程序并阻止我检测溢出。我能够重现此问题in g++ here。它不会在那里抛出异常。

请注意,异常是在调试模式下抛出的,但不是在发布模式下。

【问题讨论】:

  • volatile怎么样?
  • @curiousguy 这可能是一个可能的解决方案。我现在就试试。但这不是矫枉过正吗?它会破坏我程序中的所有优化!
  • volatile 严格适用于一个变量,它不会抑制之前或之后的优化。不过,它确实打破了通过 volatile 变量的值传播。就像输出到本地套接字并读回相同的值(这样编译器可能不会知道是相同的)。所以这取决于你是想要价值传播还是简单的修复。
  • 对于溢出捕获定点算术,考虑CNL

标签: c++ c++11 optimization integer-overflow fixed-point


【解决方案1】:

正如@Basile 已经说过的,有符号整数溢出是一种未定义的行为,因此编译器可以以任何方式处理它——甚至优化它以获得性能优势.所以检测整数溢出发生后为时已晚。相反,您应该在整数溢出发生之前预测

这是我的整数乘法溢出预测的实现:

#include <limits>

template <typename T>
bool predict_mul_overflow(T x, T y)
{
    static_assert(std::numeric_limits<T>::is_integer, "predict_mul_overflow expects integral types");

    if constexpr (std::numeric_limits<T>::is_bounded)
    {
        return ((x != T{0}) && ((std::numeric_limits<T>::max() / x) < y));
    }
    else
    {
        return false;
    }
}

如果整数乘法 x * y 预计会溢出,则该函数返回 true

请注意,虽然unsigned 溢出在modular arithmetic 方面是明确定义的,但signed 溢出是undefined behavior。尽管如此,所呈现的函数也适用于 signedunsigned T 类型。

【讨论】:

  • 如果溢出检查器可能不会溢出,返回 optional 的速度会更快吗?
  • @sandthorn 你的意思是在不发生溢出的情况下返回算术运算的可选结果吗?
  • 没错。但我不确定 optional 是否需要分配?如果是,那可能会减慢速度。
  • @sandthorn 由于预测不使用算术结果,所以可以在预测前计算结果,预测溢出时丢弃。
【解决方案2】:

如果您想检测(签名)integer overflows(在标量类型上,如 int64_tlong),您应该使用适当的内置函数,通常是特定于编译器的。

对于 GCC,请参阅 integer overflow builtins

整数溢出(在普通的intlong 或其他有符号整数类型上)是undefined behavior 的一个实例,因此编译器可以针对它进行优化。成为scared。如果您依赖于 UB,那么您将不再使用标准 C++ 进行编码,并且您的程序与特定的编译器和系统相关联,因此根本不是 portable(甚至与其他编译器、其他编译器版本、其他编译标志、其他计算机和操作系统)。因此,允许 Clang(或 GCC)针对整数溢出进行优化,有时会这样做。

或者考虑使用一些bignum 包(当然你不只处理预定义的C++ 整数标量类型)。也许GMPlib

如果您的数字适合 128 位,您可以考虑使用 GCC 的 __int128

我相信您无法在整数溢出发生时可靠地检测到它们(除非您使用integer overflow builtins)。您应该避免使用它们(或使用一些 bignum 库,或使用这些内置函数的一些库等)。

【讨论】:

  • 仍然可以做可靠的溢出谓词,预测,而不是捕获溢出。正如你已经说过的,当溢出发生时,这是未定义的行为,编译器可以做任何他们想做的事情。
  • @TheQuantumPhysicist 正如我所见,您正试图检测溢出发生后。你应该在它发生之前预测它。
  • 整数溢出对任何 signed 整数类型(模拟整数类型行为的类类型除外)给出未定义的行为。对于无符号类型,它不是未定义的。无论如何,未定义的行为意味着不可能在标准C++中在事后检测到溢出。有必要检查一个操作是否会溢出,并在实际执行操作之前抛出异常。
  • boost::multiprecision 是否定义了它的有符号整数溢出行为?有符号整数溢出是标准未定义的行为,因此您现在处于 boost::multiprecision 的手中。
  • @BasileStarynkevitch 顺便说一句,感谢您的帮助。欣赏!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2017-06-28
  • 1970-01-01
  • 1970-01-01
  • 2012-05-17
  • 2023-03-05
  • 2021-10-04
  • 1970-01-01
相关资源
最近更新 更多