Skip to content

Commit

Permalink
Utility: add CORRADE_LIKELY() and CORRADE_UNLIKELY() macros.
Browse files Browse the repository at this point in the history
Sigh, C++20, why do you always have to throw the wrench into a finely
working machine. The whole thing has to be FUGLY now thanks to you.
  • Loading branch information
mosra committed May 31, 2021
1 parent 2e614fd commit 2ab85d1
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 4 deletions.
2 changes: 2 additions & 0 deletions doc/corrade-changelog.dox
Expand Up @@ -91,6 +91,8 @@ namespace Corrade {
@ref std::string as well
- New @ref CORRADE_INTERNAL_ASSERT_EXPRESSION() macro for assertions that can
be evaluated directly inside larger expressions
- New @ref CORRADE_LIKELY() and @ref CORRADE_UNLIKELY() macros for
instruction cache microoptimizations in tight loops

@subsection corrade-changelog-latest-changes Changes and improvements

Expand Down
38 changes: 38 additions & 0 deletions doc/snippets/Utility.cpp
Expand Up @@ -44,6 +44,7 @@
#include "Corrade/Utility/FormatStl.h"
#include "Corrade/Utility/Macros.h"
#include "Corrade/Utility/Sha1.h"
#include "Corrade/Utility/StlMath.h"

/* [Tweakable-disable-header] */
#define CORRADE_TWEAKABLE
Expand All @@ -53,6 +54,8 @@
/* [ConfigurationValue] */
#include <Corrade/Utility/ConfigurationGroup.h>

#define DOXYGEN_IGNORE(...) __VA_ARGS__

struct Foo {
int a, b;
};
Expand Down Expand Up @@ -667,6 +670,41 @@ switch(a) {
/* [CORRADE_FALLTHROUGH] */
}

{
std::size_t size{};
/** @todo use Containers::BoolArray once it exists */
/* [CORRADE_LIKELY] */
float* in = DOXYGEN_IGNORE(nullptr);
float* out = DOXYGEN_IGNORE(nullptr);
std::vector<bool> mask = DOXYGEN_IGNORE({});
for(std::size_t i = 0; i != size; ++i) {
if CORRADE_LIKELY(mask[i]) {
out[i] = in[i];
} else {
out[i] = std::acos(in[i]);
}
}
/* [CORRADE_LIKELY] */
}

{
std::size_t size{};
auto someComplexOperation = []() { return 0.0f; };
/* [CORRADE_UNLIKELY] */
float* data = DOXYGEN_IGNORE(nullptr);
unsigned* indices = DOXYGEN_IGNORE(nullptr);
unsigned previousIndex = ~unsigned{};
float factor;
for(std::size_t i = 0; i != size; ++i) {
if CORRADE_UNLIKELY(indices[i] != previousIndex) {
factor = someComplexOperation();
}

data[i] *= factor;
}
/* [CORRADE_UNLIKELY] */
}

{
/* [CORRADE_LINE_STRING] */
const char* shader = "#line " CORRADE_LINE_STRING "\n" R"GLSL(
Expand Down
3 changes: 2 additions & 1 deletion src/Corrade/Utility/Assert.h
Expand Up @@ -596,7 +596,8 @@ You can override this implementation by placing your own
@cpp #define CORRADE_ASSUME @ce before including the
@ref Corrade/Utility/Assert.h header.
@see @ref CORRADE_ASSERT(), @ref CORRADE_ASSERT_UNREACHABLE()
@see @ref CORRADE_ASSERT(), @ref CORRADE_ASSERT_UNREACHABLE(),
@ref CORRADE_LIKELY(), @ref CORRADE_UNLIKELY()
*/
#ifndef CORRADE_ASSUME
#ifdef __clang__
Expand Down
73 changes: 70 additions & 3 deletions src/Corrade/Utility/Macros.h
Expand Up @@ -27,7 +27,7 @@
*/

/** @file
* @brief Macro @ref CORRADE_DEPRECATED(), @ref CORRADE_DEPRECATED_ALIAS(), @ref CORRADE_DEPRECATED_NAMESPACE(), @ref CORRADE_DEPRECATED_ENUM(), @ref CORRADE_DEPRECATED_FILE(), @ref CORRADE_DEPRECATED_MACRO(), @ref CORRADE_IGNORE_DEPRECATED_PUSH, @ref CORRADE_IGNORE_DEPRECATED_POP, @ref CORRADE_UNUSED, @ref CORRADE_FALLTHROUGH, @ref CORRADE_THREAD_LOCAL, @ref CORRADE_CONSTEXPR14, @ref CORRADE_ALWAYS_INLINE, @ref CORRADE_NEVER_INLINE, @ref CORRADE_FUNCTION, @ref CORRADE_LINE_STRING, @ref CORRADE_AUTOMATIC_INITIALIZER(), @ref CORRADE_AUTOMATIC_FINALIZER()
* @brief Macro @ref CORRADE_DEPRECATED(), @ref CORRADE_DEPRECATED_ALIAS(), @ref CORRADE_DEPRECATED_NAMESPACE(), @ref CORRADE_DEPRECATED_ENUM(), @ref CORRADE_DEPRECATED_FILE(), @ref CORRADE_DEPRECATED_MACRO(), @ref CORRADE_IGNORE_DEPRECATED_PUSH, @ref CORRADE_IGNORE_DEPRECATED_POP, @ref CORRADE_UNUSED, @ref CORRADE_FALLTHROUGH, @ref CORRADE_THREAD_LOCAL, @ref CORRADE_CONSTEXPR14, @ref CORRADE_ALWAYS_INLINE, @ref CORRADE_NEVER_INLINE, @ref CORRADE_LIKELY(), @ref CORRADE_UNLIKELY(), @ref CORRADE_FUNCTION, @ref CORRADE_LINE_STRING, @ref CORRADE_AUTOMATIC_INITIALIZER(), @ref CORRADE_AUTOMATIC_FINALIZER()
*/

#include "Corrade/configure.h"
Expand Down Expand Up @@ -402,7 +402,7 @@ always suppresses all inlining. Example usage:
@snippet Utility.cpp CORRADE_ALWAYS_INLINE
@see @ref CORRADE_NEVER_INLINE
@see @ref CORRADE_NEVER_INLINE, @ref CORRADE_LIKELY(), @ref CORRADE_UNLIKELY()
*/
#ifdef CORRADE_TARGET_GCC
#define CORRADE_ALWAYS_INLINE __attribute__((always_inline)) inline
Expand All @@ -425,7 +425,7 @@ elsewhere. Example usage:
@snippet Utility.cpp CORRADE_NEVER_INLINE
@see @ref CORRADE_ALWAYS_INLINE
@see @ref CORRADE_ALWAYS_INLINE, @ref CORRADE_LIKELY(), @ref CORRADE_UNLIKELY()
*/
#ifdef CORRADE_TARGET_GCC
#define CORRADE_NEVER_INLINE __attribute__((noinline))
Expand All @@ -435,6 +435,73 @@ elsewhere. Example usage:
#define CORRADE_NEVER_INLINE
#endif

/** @hideinitializer
@brief Mark an if condition as likely to happen
@m_since_latest
Since branch predictors of contemporary CPUs do a good enough job already, the
main purpose of this macro is to affect assembly generation and instruction
cache use in hot loops --- for example, when a certain condition is likely to
happen each iteration, the compiler may put code of the @cpp else @ce branch in
a "cold" section of the code, ensuring the more probable code path stays in the
cache:
@snippet Utility.cpp CORRADE_LIKELY
Defined as the C++20 @cpp [[likely]] @ce attribute on GCC >= 9 (and on Clang 12
and MSVC 2019 16.6+ if compiling under C++20). On older versions of GCC and on
Clang in C++11/14/17 mode the macro expands to @cpp __builtin_expect() @ce; on
older MSVC the macro is a pass-through.
@attention
@parblock
Since the C++20 @cpp [[likely]] @ce attribute is expected to be put *after* the
@cpp if() @ce parentheses while @cpp __builtin_expect() @ce is meant to be
* *inside*, the macro has to be written as above. Doing
@cpp if(CORRADE_LIKELY(condition)) @ce would work on compilers that don't
understand the @cpp [[likely]] @ce attribute, but breaks in C++20 mode.
@endparblock
It's recommended to use this macro only if profiling shows its advantage. While
it may have no visible effect in most cases,
[wrong suggestion can lead to suboptimal performance](https://stackoverflow.com/a/35940041).
Similar effect can be potentially achieved by
[reordering the branches in a certain way](https://stackoverflow.com/q/46833310),
but the behavior is highly dependent on compiler-specific heuristics.
@see @ref CORRADE_UNLIKELY(), @ref CORRADE_ASSUME(),
@ref CORRADE_ALWAYS_INLINE, @ref CORRADE_NEVER_INLINE
*/
/* Using > 201703 because MSVC 16.6 reports 201705 when compiling as C++20 */
#if (defined(CORRADE_TARGET_GCC) && __GNUC__ >= 9) || (CORRADE_CXX_STANDARD > 201703 && ((defined(CORRADE_TARGET_CLANG) && !defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ >= 12) || (defined(CORRADE_TARGET_MSVC) && _MSC_VER >= 1926)))
#define CORRADE_LIKELY(...) (__VA_ARGS__) [[likely]]
#elif defined(CORRADE_TARGET_GCC)
#define CORRADE_LIKELY(...) (__builtin_expect((__VA_ARGS__), 1))
#else
#define CORRADE_LIKELY(...) (__VA_ARGS__)
#endif

/** @hideinitializer
@brief Mark an if condition as unlikely to happen
@m_since_latest
An inverse to @ref CORRADE_LIKELY(), see its documentation for more
information about suggested use and expected impact on performance. Useful to
mark boundary conditions in tight loops, for example:
@snippet Utility.cpp CORRADE_UNLIKELY
@see @ref CORRADE_ASSUME(), @ref CORRADE_ALWAYS_INLINE,
@ref CORRADE_NEVER_INLINE
*/
/* Using > 201703 because MSVC 16.6 reports 201705 when compiling as C++20 */
#if (defined(CORRADE_TARGET_GCC) && __GNUC__ >= 9) || (CORRADE_CXX_STANDARD > 201703 && ((defined(CORRADE_TARGET_CLANG) && !defined(CORRADE_TARGET_APPLE_CLANG) && __clang_major__ >= 12) || (defined(CORRADE_TARGET_MSVC) && _MSC_VER >= 1926)))
#define CORRADE_UNLIKELY(...) (__VA_ARGS__) [[unlikely]]
#elif defined(CORRADE_TARGET_GCC)
#define CORRADE_UNLIKELY(...) (__builtin_expect((__VA_ARGS__), 0))
#else
#define CORRADE_UNLIKELY(...) (__VA_ARGS__)
#endif

/** @hideinitializer
@brief Function name
@m_since{2019,10}
Expand Down
11 changes: 11 additions & 0 deletions src/Corrade/Utility/Test/CMakeLists.txt
Expand Up @@ -173,6 +173,17 @@ if(NOT CMAKE_CXX_FLAGS MATCHES "-std=")
CORRADE_CXX_STANDARD 17
FOLDER "Corrade/Utility/Test")
endif()

# Copied verbatim from src/Corrade/Test/CMakeLists.txt, please keep in sync
if((CMAKE_CXX_COMPILER_ID STREQUAL "GNU" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "8.0") OR
(CMAKE_CXX_COMPILER_ID STREQUAL "Clang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "6.0") OR
(CMAKE_CXX_COMPILER_ID STREQUAL "AppleClang" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "10.0") OR
(CMAKE_CXX_COMPILER_ID STREQUAL "MSVC" AND NOT CMAKE_CXX_COMPILER_VERSION VERSION_LESS "19.15"))
corrade_add_test(UtilityMacrosCpp20Test MacrosCpp20Test.cpp)
set_target_properties(UtilityMacrosCpp20Test PROPERTIES
CORRADE_CXX_STANDARD 20
FOLDER "Corrade/Utility/Test")
endif()
endif()

# It should return with a non-zero exit code
Expand Down
63 changes: 63 additions & 0 deletions src/Corrade/Utility/Test/MacrosCpp20Test.cpp
@@ -0,0 +1,63 @@
/*
This file is part of Corrade.
Copyright © 2007, 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016,
2017, 2018, 2019, 2020, 2021
Vladimír Vondruš <mosra@centrum.cz>
Permission is hereby granted, free of charge, to any person obtaining a
copy of this software and associated documentation files (the "Software"),
to deal in the Software without restriction, including without limitation
the rights to use, copy, modify, merge, publish, distribute, sublicense,
and/or sell copies of the Software, and to permit persons to whom the
Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included
in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
DEALINGS IN THE SOFTWARE.
*/

#include "Corrade/TestSuite/Tester.h"
#include "Corrade/Utility/Macros.h"

namespace Corrade { namespace Utility { namespace Test { namespace {

struct MacrosCpp20Test: TestSuite::Tester {
explicit MacrosCpp20Test();

void likelyUnlikely();
};

MacrosCpp20Test::MacrosCpp20Test() {
addTests({&MacrosCpp20Test::likelyUnlikely});
}

void MacrosCpp20Test::likelyUnlikely() {
/* The LIKELY/UNLIKELY macros are on MSVC only when compiling as C++20, and
on Clang they use the new syntax only when compiling as C++20 */

int a = 3;

/* Test that the macro can handle commas */
if CORRADE_LIKELY(std::is_same<decltype(a), int>::value && a < 5) {
a += 1;
}

/* Missugestion, but should still go through */
if CORRADE_UNLIKELY(std::is_same<decltype(a), int>::value && a < 5) {
a += 1;
}

CORRADE_COMPARE(a, 5);
}

}}}}

CORRADE_TEST_MAIN(Corrade::Utility::Test::MacrosCpp20Test)
18 changes: 18 additions & 0 deletions src/Corrade/Utility/Test/MacrosTest.cpp
Expand Up @@ -48,6 +48,7 @@ struct MacrosTest: TestSuite::Tester {
void fallthrough();
void cxxStandard();
void alwaysNeverInline();
void likelyUnlikely();
void function();
void lineString();

Expand All @@ -64,6 +65,7 @@ MacrosTest::MacrosTest() {
&MacrosTest::fallthrough,
&MacrosTest::cxxStandard,
&MacrosTest::alwaysNeverInline,
&MacrosTest::likelyUnlikely,
&MacrosTest::function,
&MacrosTest::lineString,

Expand Down Expand Up @@ -182,6 +184,22 @@ void MacrosTest::alwaysNeverInline() {
CORRADE_COMPARE(alwaysInline() + neverInline(), 42);
}

void MacrosTest::likelyUnlikely() {
int a = 3;

/* Test that the macro can handle commas */
if CORRADE_LIKELY(std::is_same<decltype(a), int>::value && a < 5) {
a += 1;
}

/* Missugestion, but should still go through */
if CORRADE_UNLIKELY(std::is_same<decltype(a), int>::value && a < 5) {
a += 1;
}

CORRADE_COMPARE(a, 5);
}

/* Needs another inner anonymous namespace otherwise Clang complains about a
missing prototype (UGH) */
namespace SubNamespace { namespace {
Expand Down

0 comments on commit 2ab85d1

Please sign in to comment.