代表的な面接トピック

C++コーディング面接:C++26 std::simd を用いて移植性のあるベクトル化コードをどのように書くか?

コーディング難しい
Offer.cc 編集チーム公開日 更新日

質問

N個のfloatに対する要素ごとの変換を、好ましくはC++26 std::simdを用いて実装し、Nがベクトル幅の倍数でない場合やランタイムハードウェアが適していない場合でも正しさを維持してください。マスク、末尾処理、アライメント、例外的な値、コンパイラサポート、およびパフォーマンスの証明について説明してください。

プロンプトとコンテキスト

Nがベクトル幅の倍数でない可能性があり、入力にNaNが含まれる可能性がある配列に対して、y[i] = a * x[i] + b を実装してください。C++26のデータ並列型を使用し、末尾処理、アライメント、マスク、コンパイラサポート、スカラーフォールバック、そしてその最適化が効果的であることをどのように証明するかを説明してください。

面接官がテストするポイント

  • std::simd が移植性のあるデータ並列抽象化であり、特定の固定命令を保証するものではないことを理解しているか。
  • ベクトル値、マスク、固定サイズ、ネイティブ幅を区別し、末尾の部分バッチを適切に処理できるか。
  • アライメントが合っていないアクセスに関する前提、エイリアシング違反、範囲外アクセス、および不正確なNaNの扱いを回避できるか。
  • C++26およびツールチェーンのサポートがまだ一様でない中で、機能テスト(feature testing)とフォールバックパスを準備できるか。
  • ソースコードだけで高速化を主張するのではなく、ベンチマーク、正しさのテスト、ハードウェアカウンタを使用できるか。

明確化のための質問

  1. 対象のコンパイラと標準ライブラリはC++26のsimdヘッダーを実装していますか、それとも実験的な名前空間のみですか?
  2. 入力と出力がエイリアス(重複)する可能性はありますか。また、NaN、無限大、丸めの要件は何ですか?
  3. Nの範囲、要素の型、許容誤差、および対象のCPU/GPU命令セットは何ですか?
  4. このループはメモリ帯域幅に制約されていますか。また、ベクトル化の複雑さを正当化できるほど呼び出し頻度は高いですか?
  5. 固定のABI幅が必要ですか、それとも実装が対象ハードウェアに応じたネイティブ幅を選択してもよいですか?

30秒の回答フレームワーク

「まずsimdヘッダーの実装状況と数値契約を確認し、次に std::simd でバッチをロードし、積和演算を適用してストアします。メインループは完全なベクトルを処理し、マスクまたはスカラーのループが範囲外アクセスなしで末尾を処理します。ロードタグは、想定上のキャストではなく実際のアライメントに一致させる必要があります。スカラーの参照実装を保持し、機能テストとビルドマトリクスを使用してC++26、実験的実装、またはスカラーフォールバックを選択します。最後に、同一の入力でNaN、誤差、および境界の挙動をテストし、固定ベンチマークとハードウェアカウンタを用いてスループットと帯域幅を比較します。」

ステップバイステップの詳細解説

1. データ並列抽象化の選択

C++26のデータ並列型は、複数の要素に対する単一の操作を表現するためのベクトル値とマスクを提供します。std::simd のレーン数は実装および対象ハードウェアによって選択されます。メモリ配置(レイアウト)やインターフェースの安定性が必要な場合にのみ、固定サイズ型を選択してください。標準の抽象化により、コンパイラはSIMDレジスタまたはその他の適切な実装にマッピングできます。

2. フルバッチループの記述

V をベクトル型とし、i から i + V::size() までの完全なバッチを処理します。関数の規約でインプレース操作が許可されていない限り、入力と出力が重複してはならず、呼び出し元は有効な範囲を提供する必要があります。任意のポインタがアライメントされていると仮定しないでください。ロードタグは実際の保証されたアライメントと一致している必要があります。

cpp
template<class V>
void axpb_simd(const float* x, float* y, std::size_t n, float a, float b) {
  const V va(a), vb(b);
  std::size_t i = 0;
  for (; i + V::size() <= n; i += V::size()) {
    V vx(&x[i], std::element_aligned_tag{});
    (vx * va + vb).copy_to(&y[i], std::element_aligned_tag{});
  }
  for (; i < n; ++i) y[i] = a * x[i] + b;
}

3. 末尾処理にマスクを使用する

スカラーの末尾ループは監査が最も容易です。末尾処理が頻繁に発生する場合は、アクティブマスクを作成し、有効なレーンのみをロードおよびストアします。非アクティブなレーンが範囲外アクセスや副作用を引き起こさないように、マスクは読み取りと書き込みの両方を制限する必要があります。数回のスカラー反復を排除するために境界処理の明確さを犠牲にしないでください。

4. NaN、誤差、例外セマンティクスの定義

ベクトル化を行う前に、NaNの伝播、無限大、および丸めの要件を定義してください。ベクトル化により演算の順序が変わる可能性があるため、スカラーとベクトルの結果がビット単位で自動的に同一になるとは限りません。テストでは許容誤差と特殊値の挙動を比較する必要があります。ビジネス要件として厳密なIEEE準拠の結果や例外フラグの順序が必要な場合は、SIMDを選択する前にコンパイラの浮動小数点オプションとライブラリのセマンティクスを検証してください。

5. 標準化パスとフォールバックパスの提供

C++26のsimdヘッダーに対する機能テストマクロは __cpp_lib_simd ですが、ツールチェーンのサポートが遅れている場合があります。機能検出後、ビルドは標準型、実装独自の実験的インターフェース、またはスカラーテンプレートを選択する必要があります。コンパイラ固有のプライベートなベクトル型を公開APIに露出させないでください。すべてのパスで同一の正しさのテストを実行する必要があります。

6. パフォーマンスと限界の証明

スカラー、SIMD、異なる幅を比較するベンチマークにおいて、N、データ分布、コンパイラオプション、およびスレッド数を固定します。スループット、レイテンシ、キャッシュミス、ベクトル命令比率、およびメモリ帯域幅を記録します。N=1,000、空の入力、アライメントされていないアドレス、およびNaNデータを個別にテストします。メモリ帯域幅や呼び出しオーバーヘッドがボトルネックである場合、SIMDは役に立たない可能性があり、シンプルな実装を維持すべきです。

優れた回答例

「私は std::simd を移植性のあるデータ並列抽象化として扱い、固定のレーン数や命令を仮定しません。メインループは V::size() 個の完全なバッチを処理し、アライメントされていない可能性のある配列には element_aligned_tag を使用します。末尾はスカラーのループまたは読み書きを制限するマスクを使用します。まずエイリアシング、NaNの伝播、誤差の要件を定義し、機能テストを用いてC++26、実験的実装、またはスカラーテンプレートを選択します。N=1,000、空の入力、アライメントされていないアドレス、NaNをテストし、ハードウェアカウンタでスループット、キャッシュの挙動、帯域幅を比較します。メモリがループのボトルネックとなっている場合は、無理にベクトル化しません。」

よくある間違い

  • std::simd を固定幅レジスタとして扱う → コードが単一のCPUに依存する → 実装選択の幅または明示的な固定幅を意図的に使い分ける。
  • 末尾処理で完全なベクトルをロードする → 範囲外アクセスが発生する → マスクまたはスカラーの末尾ループを使用する。
  • アライメントを仮定する → 未定義動作やロードの低速化につながる → ロードタグを実際の保証に一致させる。
  • 平均値のみを比較する → NaN、無限大、丸めの違いが見落とされる → 特殊値と誤差の契約を定義する。
  • ベクトル命令を確認しただけで成功と判断する → メモリ帯域幅が支配的である可能性がある → 固定のベンチマークとカウンタでそれを証明する。

フォローアップ質問と回答

固定幅とネイティブ幅はどのように選びますか?

ネイティブ幅は、実装が対象ハードウェアに応じたレジスタ幅を選択できるようにするものであり、通常はスループットに適しています。固定幅は、安定したレイアウト、ABI、またはプラットフォーム間で再現可能な挙動が必要な場合に適しています。選択する前にABI、データレイアウト、ベンチマークを確認してください。固定幅自体はパフォーマンスを保証するものではありません。

なぜ末尾処理に常にマスクを使用しないのですか?

マスクを使用するとループの構造は1つに保たれますが、構築、ロード、ストアのオーバーヘッドが増加する可能性があります。短い末尾であればスカラーコードの方が監査が容易です。末尾の割合が大きい場合は、測定によってマスクとスカラーの末尾処理を比較してください。

コンパイラがループをスカラー化していないことをどのように証明しますか?

生成されたアセンブリや最適化レポートを確認し、ベクトル命令、スループット、キャッシュの挙動に関するハードウェアカウンタと照合します。ソースコードの確認や1回の実行時間計測だけでは不十分です。コンパイラ、ターゲットフラグ、データサイズを一定に保って検証します。

本番環境のコンパイラでsimdヘッダーが利用できない場合はどうしますか?

機能テストとビルドマトリクスを使用して、スカラーとしての正しさを維持しながらサポートされている実装を選択します。プライベートなベクトル型をパブリックインターフェースにコピーしないでください。ツールチェーンのアップグレード後に標準パスを有効にし、同じ境界テストおよび数値テストを再利用します。

公開情報ソース

関連する質問

関連面接ツール

コーディング問題にはスクリーンショットを使用

問題をキャプチャし、制約条件、解法アプローチ、コード、エッジケース、計算量の順に進めます。

ツールを見る