コンパイラ組み込み関数とビット演算

最終更新: 2022-03-03 23:02:53 +0800 +0800
原文は英語で書かれています。あなたはAI翻訳版を読んでいます。

GCC と Clang は、フロントエンドが直接認識する演算に対して、組み込み関数形式のスペルを提供します。ビットカウント組み込み関数は、選択したターゲットと最適化コンテキストに応じて、ターゲット命令、ライブラリシーケンス、または最適化された式へ変換されます。ソースコードでは、移植性とゼロ入力時の契約を明示するインターフェースを選択してください。

GNU 形式のビット組み込み関数 #

GCC と Clang は、unsigned int、unsigned long、unsigned long long に対して、次の関数群をサポートしています。

操作unsigned intunsigned longunsigned long long
セットビット数__builtin_popcount__builtin_popcountl__builtin_popcountll
先頭のゼロビット数__builtin_clz__builtin_clzl__builtin_clzll
末尾のゼロビット数__builtin_ctz__builtin_ctzl__builtin_ctzll

戻り値の型は int で、サフィックスによって引数の幅を選択します。論理的な幅が重要な場合は、整数昇格に依存せず、意図を明示してキャストしてください。一般的な ABI では unsigned long の幅が異なるため、アルゴリズムで特定の幅が必要な場合は、unsigned long long の値に対して ll 形式を使用するか、固定幅の標準関数を使用してください。

__builtin_clz(0) と __builtin_ctz(0) は未定義動作です。ゼロになる可能性がある場合は、先に検査してください。

cpp
#include <limits>

unsigned trailing_or_width(unsigned value) {
    return value == 0 ? std::numeric_limits<unsigned>::digits
                      : static_cast<unsigned>(__builtin_ctz(value));
}

popcount(0) は定義されており、ゼロを返します。ただし、一貫した標準インターフェースを使用すると、関連する演算ごとに異なる契約を作らずに済みます。

移植性のある C++ では C++20 の <bit> を優先する #

C++20 では、<bit> において符号なし整数型向けに std::popcount、std::countl_zero、std::countr_zero が提供されます。GNU の先頭および末尾のゼロビット組み込み関数とは異なり、これらのカウント関数は、ゼロに対する結果を型の値ビット数として定義します。

cpp
#include <bit>
#include <cstdint>

constexpr std::uint32_t bits = 0b101101;
static_assert(std::popcount(bits) == 4);
static_assert(std::countl_zero(std::uint32_t{0}) == 32);
static_assert(std::countr_zero(std::uint32_t{16}) == 4);

ライブラリ機能の境界をサポートする必要がある場合は、<bit> をインクルードした後に __cpp_lib_bitops を確認してください。C++20 では、この機能に対して値 201907L が規定されています。コンパイラ固有のコードでは、使用をガードした後にのみ __has_builtin で組み込み関数の利用可能性を検査してください。

cpp
#if defined(__cpp_lib_bitops) && __cpp_lib_bitops >= 201907L
// Use std::popcount, std::countl_zero, and std::countr_zero.
#elif defined(__has_builtin)
#  if __has_builtin(__builtin_popcount)
#    define PROJECT_HAS_BUILTIN_POPCOUNT 1
#  endif
#endif

GCC と Clang はこれらの特定の組み込み関数を文書化しているため、両方を明示的にサポートするプロジェクトでは、代わりにコンパイラファミリーの判定を一元化し、他のコンパイラ向けに移植可能なフォールバックを保持することもできます。__has_builtin が存在すると仮定してはならず、公開 API が強制していないゼロの前提条件を持つ組み込み関数の選択に使用してはなりません。

コード生成と定数評価 #

パフォーマンスが重要なターゲットでは、生成されたコードを検査してください。組み込み関数のスペルだけでは、単一命令への変換は保証されないためです。ハードウェアサポート、ターゲット機能、オペランドの幅、周囲の制御フロー、最適化レベルによって変換先が決まります。標準の <bit> 関数は、準拠実装上ではオプティマイザーに同等の意味情報を提供しながら、移植性を維持し、オペランドとライブラリ実装が許す場合には定数評価でも使用できます。C++20 より前のライブラリや、測定によって確認されたターゲット固有の差に対しては、組み込み関数のフォールバックが適切な場合があります。ただし、文書化された前提条件を持つ、幅を考慮した単一のラッパーの背後に配置してください。

コメント

GitHub で閲覧・コメントする(投稿にはログインが必要です)