Commit ba3c4f94 authored by jschuh's avatar jschuh Committed by Commit bot

Performance optimizations for base/numerics absolute value and multiply

Removes a number of branches from the calculations. Also now exporting
SafeUnsignedAbs as a public API.

NOTRY=true

Review-Url: https://codereview.chromium.org/2566733002
Cr-Commit-Position: refs/heads/master@{#437769}
parent 01537d2e
...@@ -36,6 +36,9 @@ namespace base { ...@@ -36,6 +36,9 @@ namespace base {
// IsValueNegative<>() - A convenience function that will accept any arithmetic // IsValueNegative<>() - A convenience function that will accept any arithmetic
// type as an argument and will return whether the value is less than zero. // type as an argument and will return whether the value is less than zero.
// Unsigned types always return false. // Unsigned types always return false.
// SafeUnsignedAbs() - Returns the absolute value of the supplied integer
// parameter as an unsigned result (thus avoiding an overflow if the value
// is the signed, two's complement minimum).
// StrictNumeric<> - A wrapper type that performs assignments and copies via // StrictNumeric<> - A wrapper type that performs assignments and copies via
// the strict_cast<> template, and can perform valid arithmetic comparisons // the strict_cast<> template, and can perform valid arithmetic comparisons
// across any range of arithmetic types. StrictNumeric is the return type // across any range of arithmetic types. StrictNumeric is the return type
...@@ -279,6 +282,7 @@ STRICT_COMPARISON_OP(IsNotEqual, !=); ...@@ -279,6 +282,7 @@ STRICT_COMPARISON_OP(IsNotEqual, !=);
using internal::strict_cast; using internal::strict_cast;
using internal::saturated_cast; using internal::saturated_cast;
using internal::SafeUnsignedAbs;
using internal::StrictNumeric; using internal::StrictNumeric;
using internal::MakeStrictNum; using internal::MakeStrictNum;
......
...@@ -30,6 +30,52 @@ struct IntegerBitsPlusSign { ...@@ -30,6 +30,52 @@ struct IntegerBitsPlusSign {
std::is_signed<NumericType>::value; std::is_signed<NumericType>::value;
}; };
// Helper templates for integer manipulations.
template <typename Integer>
struct PositionOfSignBit {
static const size_t value = IntegerBitsPlusSign<Integer>::value - 1;
};
template <typename T>
constexpr bool HasSignBit(T x) {
// Cast to unsigned since right shift on signed is undefined.
return !!(static_cast<typename std::make_unsigned<T>::type>(x) >>
PositionOfSignBit<T>::value);
}
// This wrapper undoes the standard integer promotions.
template <typename T>
constexpr T BinaryComplement(T x) {
return static_cast<T>(~x);
}
// This performs a safe, non-branching absolute value via unsigned overflow.
template <typename T>
constexpr T SafeUnsignedAbsImpl(T value, T sign_mask) {
static_assert(!std::is_signed<T>::value, "Types must be unsigned.");
return (value + sign_mask) ^ sign_mask;
}
template <typename T,
typename std::enable_if<std::is_integral<T>::value &&
std::is_signed<T>::value>::type* = nullptr>
constexpr typename std::make_unsigned<T>::type SafeUnsignedAbs(T value) {
using UnsignedT = typename std::make_unsigned<T>::type;
return SafeUnsignedAbsImpl(
static_cast<UnsignedT>(value),
// The sign mask is all ones for negative and zero otherwise.
static_cast<UnsignedT>(-static_cast<T>(HasSignBit(value))));
}
template <typename T,
typename std::enable_if<std::is_integral<T>::value &&
!std::is_signed<T>::value>::type* = nullptr>
constexpr T SafeUnsignedAbs(T value) {
// T is unsigned, so |value| must already be positive.
return static_cast<T>(value);
}
enum IntegerRepresentation { enum IntegerRepresentation {
INTEGER_REPRESENTATION_UNSIGNED, INTEGER_REPRESENTATION_UNSIGNED,
INTEGER_REPRESENTATION_SIGNED INTEGER_REPRESENTATION_SIGNED
...@@ -288,11 +334,6 @@ struct TwiceWiderInteger { ...@@ -288,11 +334,6 @@ struct TwiceWiderInteger {
IsSigned>::type; IsSigned>::type;
}; };
template <typename Integer>
struct PositionOfSignBit {
static const size_t value = IntegerBitsPlusSign<Integer>::value - 1;
};
enum ArithmeticPromotionCategory { enum ArithmeticPromotionCategory {
LEFT_PROMOTION, // Use the type of the left-hand argument. LEFT_PROMOTION, // Use the type of the left-hand argument.
RIGHT_PROMOTION // Use the type of the right-hand argument. RIGHT_PROMOTION // Use the type of the right-hand argument.
......
...@@ -42,21 +42,6 @@ struct UnsignedOrFloatForSize<Numeric, false, true> { ...@@ -42,21 +42,6 @@ struct UnsignedOrFloatForSize<Numeric, false, true> {
using type = Numeric; using type = Numeric;
}; };
// Helper templates for integer manipulations.
template <typename T>
constexpr bool HasSignBit(T x) {
// Cast to unsigned since right shift on signed is undefined.
return !!(static_cast<typename std::make_unsigned<T>::type>(x) >>
PositionOfSignBit<T>::value);
}
// This wrapper undoes the standard integer promotions.
template <typename T>
constexpr T BinaryComplement(T x) {
return static_cast<T>(~x);
}
// Probe for builtin math overflow support on Clang and version check on GCC. // Probe for builtin math overflow support on Clang and version check on GCC.
#if defined(__has_builtin) #if defined(__has_builtin)
#define USE_OVERFLOW_BUILTINS (__has_builtin(__builtin_add_overflow)) #define USE_OVERFLOW_BUILTINS (__has_builtin(__builtin_add_overflow))
...@@ -192,27 +177,21 @@ template <typename T, ...@@ -192,27 +177,21 @@ template <typename T,
((IntegerBitsPlusSign<T>::value * 2) > ((IntegerBitsPlusSign<T>::value * 2) >
IntegerBitsPlusSign<intmax_t>::value)>::type* = nullptr> IntegerBitsPlusSign<intmax_t>::value)>::type* = nullptr>
bool CheckedMulImpl(T x, T y, T* result) { bool CheckedMulImpl(T x, T y, T* result) {
if (x && y) { // Since the value of x*y is potentially undefined if we have a signed type,
if (x > 0) { // we compute it using the unsigned type of the same size.
if (y > 0) { using UnsignedDst = typename std::make_unsigned<T>::type;
if (x > std::numeric_limits<T>::max() / y) const T is_negative = HasSignBit(x) ^ HasSignBit(y);
return false; const UnsignedDst ux = SafeUnsignedAbs(x);
} else { const UnsignedDst uy = SafeUnsignedAbs(y);
if (y < std::numeric_limits<T>::lowest() / x) UnsignedDst uresult = static_cast<UnsignedDst>(ux * uy);
return false; // This is a non-branching conditional negation.
} *result = static_cast<T>((uresult ^ -is_negative) + is_negative);
} else { // This uses the unsigned overflow check on the absolute value, with a +1
if (y > 0) { // bound for a negative result.
if (x < std::numeric_limits<T>::lowest() / y) return (uy == 0 ||
return false; ux <= (static_cast<UnsignedDst>(std::numeric_limits<T>::max()) +
} else { is_negative) /
if (y < std::numeric_limits<T>::max() / x) uy);
return false;
}
}
}
*result = x * y;
return true;
} }
template <typename T, template <typename T,
...@@ -529,11 +508,8 @@ template <typename T, ...@@ -529,11 +508,8 @@ template <typename T,
typename std::enable_if<std::is_integral<T>::value && typename std::enable_if<std::is_integral<T>::value &&
std::is_signed<T>::value>::type* = nullptr> std::is_signed<T>::value>::type* = nullptr>
bool CheckedAbs(T value, T* result) { bool CheckedAbs(T value, T* result) {
if (value != std::numeric_limits<T>::lowest()) { *result = static_cast<T>(SafeUnsignedAbs(value));
*result = std::abs(value); return *result != std::numeric_limits<T>::lowest();
return true;
}
return false;
} }
template <typename T, template <typename T,
...@@ -545,24 +521,6 @@ bool CheckedAbs(T value, T* result) { ...@@ -545,24 +521,6 @@ bool CheckedAbs(T value, T* result) {
return true; return true;
} }
template <typename T,
typename std::enable_if<std::is_integral<T>::value &&
std::is_signed<T>::value>::type* = nullptr>
constexpr typename std::make_unsigned<T>::type SafeUnsignedAbs(T value) {
using UnsignedT = typename std::make_unsigned<T>::type;
return value == std::numeric_limits<T>::lowest()
? static_cast<UnsignedT>(std::numeric_limits<T>::max()) + 1
: static_cast<UnsignedT>(std::abs(value));
}
template <typename T,
typename std::enable_if<std::is_integral<T>::value &&
!std::is_signed<T>::value>::type* = nullptr>
constexpr T SafeUnsignedAbs(T value) {
// T is unsigned, so |value| must already be positive.
return static_cast<T>(value);
}
// This is just boilerplate that wraps the standard floating point arithmetic. // This is just boilerplate that wraps the standard floating point arithmetic.
// A macro isn't the nicest solution, but it beats rewriting these repeatedly. // A macro isn't the nicest solution, but it beats rewriting these repeatedly.
#define BASE_FLOAT_ARITHMETIC_OPS(NAME, OP) \ #define BASE_FLOAT_ARITHMETIC_OPS(NAME, OP) \
......
...@@ -106,11 +106,14 @@ struct LogOnFailure { ...@@ -106,11 +106,14 @@ struct LogOnFailure {
#define TEST_EXPECTED_SUCCESS(actual) TEST_EXPECTED_VALIDITY(true, actual) #define TEST_EXPECTED_SUCCESS(actual) TEST_EXPECTED_VALIDITY(true, actual)
#define TEST_EXPECTED_FAILURE(actual) TEST_EXPECTED_VALIDITY(false, actual) #define TEST_EXPECTED_FAILURE(actual) TEST_EXPECTED_VALIDITY(false, actual)
// We have to handle promotions, so infer the underlying type below from actual.
#define TEST_EXPECTED_VALUE(expected, actual) \ #define TEST_EXPECTED_VALUE(expected, actual) \
EXPECT_EQ(static_cast<Dst>(expected), \ EXPECT_EQ(static_cast<typename std::decay<decltype(actual)>::type::type>( \
expected), \
((actual) \ ((actual) \
.template Cast<Dst>() \ .template ValueOrDie< \
.template ValueOrDie<Dst, LogOnFailure>())) \ typename std::decay<decltype(actual)>::type::type, \
LogOnFailure>())) \
<< "Result test: Value " << GetNumericValueForTest(actual) << " as " \ << "Result test: Value " << GetNumericValueForTest(actual) << " as " \
<< dst << " on line " << line << dst << " on line " << line
...@@ -143,6 +146,8 @@ static void TestSpecializedArithmetic( ...@@ -143,6 +146,8 @@ static void TestSpecializedArithmetic(
TEST_EXPECTED_FAILURE(-CheckedNumeric<Dst>(DstLimits::lowest())); TEST_EXPECTED_FAILURE(-CheckedNumeric<Dst>(DstLimits::lowest()));
TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()).Abs()); TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()).Abs());
TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(-1).Abs()); TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(-1).Abs());
TEST_EXPECTED_VALUE(DstLimits::max(),
MakeCheckedNum(-DstLimits::max()).Abs());
TEST_EXPECTED_SUCCESS(CheckedNumeric<Dst>(DstLimits::max()) + -1); TEST_EXPECTED_SUCCESS(CheckedNumeric<Dst>(DstLimits::max()) + -1);
TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()) + -1); TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()) + -1);
...@@ -161,6 +166,13 @@ static void TestSpecializedArithmetic( ...@@ -161,6 +166,13 @@ static void TestSpecializedArithmetic(
TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()) / -1); TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()) / -1);
TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(-1) / 2); TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(-1) / 2);
TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()) * -1); TEST_EXPECTED_FAILURE(CheckedNumeric<Dst>(DstLimits::lowest()) * -1);
TEST_EXPECTED_VALUE(DstLimits::lowest(),
MakeCheckedNum(DstLimits::lowest()).UnsignedAbs());
TEST_EXPECTED_VALUE(DstLimits::max(),
MakeCheckedNum(DstLimits::max()).UnsignedAbs());
TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(0).UnsignedAbs());
TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1).UnsignedAbs());
TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(-1).UnsignedAbs());
// Modulus is legal only for integers. // Modulus is legal only for integers.
TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>() % 1); TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>() % 1);
...@@ -221,6 +233,12 @@ static void TestSpecializedArithmetic( ...@@ -221,6 +233,12 @@ static void TestSpecializedArithmetic(
CheckedNumeric<typename std::make_signed<Dst>::type>( CheckedNumeric<typename std::make_signed<Dst>::type>(
std::numeric_limits<typename std::make_signed<Dst>::type>::lowest()) std::numeric_limits<typename std::make_signed<Dst>::type>::lowest())
.UnsignedAbs()); .UnsignedAbs());
TEST_EXPECTED_VALUE(DstLimits::lowest(),
MakeCheckedNum(DstLimits::lowest()).UnsignedAbs());
TEST_EXPECTED_VALUE(DstLimits::max(),
MakeCheckedNum(DstLimits::max()).UnsignedAbs());
TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>(0).UnsignedAbs());
TEST_EXPECTED_VALUE(1, CheckedNumeric<Dst>(1).UnsignedAbs());
// Modulus is legal only for integers. // Modulus is legal only for integers.
TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>() % 1); TEST_EXPECTED_VALUE(0, CheckedNumeric<Dst>() % 1);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment