0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

SIMDって何?8つのOSSから学ぶ、速くする場所とデータの並べ方

0
Posted at

SIMDって何?8つのOSSから学ぶ、速くする場所とデータの並べ方

前半から少しずつレベルを上げます。最初の章だけでも「SIMDってこういうものか」と分かるように書きました。途中で難しくなったら、そこで読み終えて大丈夫です。後半で分からない言葉が出てきたら、気になった箇所をあとでAIに聞きながら読むのもおすすめです。

1章:SIMDって何?まずは同じ作業をまとめて行う

1.1 いつもの計算をまとめてみる

4つの数にそれぞれ10を足すとします。

入力:   1   2   3   4
出力:  11  12  13  14

普通のプログラムでは、1個ずつ処理します。

for (int i = 0; i < 4; ++i) {
    out[i] = in[i] + 10;
}

CPUには、複数の数をひとまとめにして足し算する命令があります。それを使う考え方がSIMDです。

通常:  1個目を足す → 2個目を足す → 3個目を足す → 4個目を足す
SIMD:  [1, 2, 3, 4] + [10, 10, 10, 10]

「同じ命令を複数のデータへ同時に適用する」と覚えれば、最初は十分です。SIMDはSingle Instruction, Multiple Dataの略で、ベクトル化と呼ばれたりもします。Intelのベクトル化入門にも、この考え方が簡潔に説明されています。

1.2 ごく単純な使い方

SIMDを直接使うC++の例を、雰囲気が伝わる形で書くとこうなります。実際の型や関数名はCPU命令セットや使うライブラリによって異なります。

// 説明用の疑似コード: 4個のfloatをまとめて扱う
vector4 a = load4(input);       // input[0..3] をまとめて読む
vector4 b = add(a, splat(10));  // 4つ全部に10を足す
store4(output, b);              // 4個まとめて書く

ここでのvector4は説明用です。実際のC++ではCPU命令を直接扱う型や、SIMDを抽象化するライブラリの型を使います。基本は「まとめて読む、まとめて計算する、まとめて書く」です。

SIMDでまとめる個数はいつも4個とは限りません。CPUによって一度に扱える数や命令が異なります。そこで後半では、幅を抽象化する方法や、CPUに合う実装を選ぶ方法を見ます。

1.3 なぜ速くなる?どれくらい速くなる?

通常の計算では、CPUは1回の命令で少数の値を処理します。SIMD命令は、1つの命令で複数の値(レーン)に同じ演算を行うため、たとえば4レーンなら、条件が揃えば1命令で4個分の加算を進められます。ここで速くなる理由は「仕事を4回分まとめて実行し、命令の回数を減らせる」ことです。

ただし、4レーンだからプログラム全体が4倍速くなる、とは限りません。SIMDにできない処理やメモリアクセス、データの並べ替え、端数処理などは残ります。仮に実行時間の半分だけがSIMD化でき、その部分が8倍速くなったとしても、全体の改善は2倍未満です。

期待できる速さは、対象カーネル、データ量、CPU、コンパイラによって変わります。理論上のレーン数ぶんの向上は、条件のよい部分処理に対する上限の目安です。アプリ全体では、効果がほとんどない場合から数倍になる場合まで幅があります。Intelもベクトル化の説明で、理論値と実アプリの速度向上を区別し、測定を勧めています(ベクトル化の効果を認識・測定する、Amdahlの法則を含む性能分析手法)。

1.4 どんな作業がまとめやすい?

画像の全画素を少し明るくする、数千個の数を足し合わせる、文字列から特定の文字を探す、といった処理は同じルールを大量のデータへ適用します。

一方、1個目の結果を見て2個目の処理が決まる仕事や、データごとに行き先が違う仕事は、そのままではまとめにくくなります。コンパイラの自動ベクトル化も、ループの依存関係やメモリ配置を見て安全性と採算性を判断します。LLVMのベクトル化解説では、ループと連続命令列に対する方式を紹介しています。

ここまでのまとめ

SIMDとは、同じ計算を複数のデータへまとめて適用するCPUの仕組みです。「まとめて読む・計算する・書く」と理解すれば、まずは十分です。速さの目安はレーン数だけで決まらず、SIMD化できる処理の割合とデータの扱いにも左右されます。

今回取り上げるOSS

  • Google Highway:C++のSIMD抽象化ライブラリです。
  • SimSIMD:C/C++を中心にベクトル距離・内積を高速計算します。
  • pulp:RustのポータブルSIMDライブラリです。
  • OpenCV.js:C++製OpenCVをJavaScript/WebAssemblyから使う仕組みです。
  • Pillow-SIMD:C拡張で画像処理を高速化するPython画像ライブラリです。
  • simdjson:C++の高速JSONパーサーです。
  • SwissTable(Abseil):C++のハッシュテーブル設計・実装です。
  • Intel Embree:C++を中心とするレイトレーシング用カーネル群です。

2章:SIMD、GPU、それ以外。どの高速化を考える?

2.1 SIMDを試す価値がある処理

次のような処理はSIMDの候補です。

  • 画像や音声を先頭から同じ式で処理している
  • 配列同士を足す、掛ける、比べる
  • ベクトルの内積や距離を何度も計算する
  • バイト列から特定の文字やビットパターンを探す
  • たくさんのレイや図形に同じ交差計算を行う

目安は「同じ処理を、連続したデータに、何度も行っているか」です。

2.2 遅さの原因によって別の改善を選ぶ

SIMDは万能の高速化手段ではありません。遅さの原因を見極めることが大事です。次の表は、遅さの原因と、先に考えるべき改善策の例です。

遅さの原因・仕事の形 先に考えること
毎回メモリを確保している 使い回す、まとめて確保する
データベースやネットワークを待っている 問い合わせ回数、通信量、キャッシュを見直す
同じ結果を何度も計算している キャッシュ化を考える
一つの計算が前の結果を待つ アルゴリズムを変え、依存をほどく
多数の独立した計算を長時間続けられる GPUやスレッド並列を検討する
データがメモリ上に散らばっている 配列化、インデックス化、アクセスを整理する

SIMDはCPUの計算命令をまとめる手段です。ネットワーク待ち、ファイル読み書き、不要な処理を直接解決するものではありません。

GPUが有力になりやすい仕事

GPUは多数の計算を同時に走らせ、全体の処理量を稼ぐ装置です。グラフィックス、巨大な行列・テンソル計算、機械学習の学習や推論、大きな画像・動画の一括処理が代表例です。入力が大きく、GPU上にデータを置いたまま何段もの計算を続けられるほど、GPUの処理量を活かしやすくなります。

ただし、「この分野ならどんな入力でも必ずGPUが速い」とは言えません。同じ行列計算でも、サイズ、GPUやCPUの世代、ライブラリ、データ転送の有無で結果は変わります。CPUは少数の処理を低い待ち時間で進め、GPUは多数の軽い処理で総処理量を伸ばす設計、と捉えるとよいでしょう。

CPUでもGPUでも実装できる仕事

中規模の画像処理、数値シミュレーション、音声処理、データ分析などは、条件によってCPUでもGPUでも実装できます。計算だけでなく、データをどこに置くかも考えます。

CPU上のデータ ──転送──> GPU ──計算──> CPUへ結果を戻す

少量のデータをGPUへ送り、すぐCPUへ戻すと、転送や起動にかかる時間が計算を上回ることがあります。最初にまとめてGPUへ送り、複数の処理をGPU上で続けてから結果を戻せるなら、転送コストを薄めやすくなります。CUDA公式ガイドも、ホストとGPU間の転送を減らし、小さな転送をまとめるよう勧めています。CUDA C++ Best Practices Guide

GPUを用意しにくい環境ではCPU SIMDが候補になる

サーバー構成、組み込み機器、ブラウザ、配布先PC、コストや電力の制約によって、GPUを前提にできないことがあります。その場合、CPUの複数コアを使う並列化やSIMDが候補です。GPUのないサーバーで大量のJSONを読む、CPU上で画像を少量ずつ加工する、といった例があります。simdjsonやPillow-SIMDは、CPUでデータ並列性を使う設計例です。

GPUがある分野でもCPU SIMDは役立ちます。前処理、短いデータ、GPU転送前の変換など、CPU側で完結する処理に使えます。GPUとCPU SIMDは、処理段階ごとに分担できます。

CPU SIMDが実用上有利になりやすい場面

「SIMDがGPUより常に速い分野」とは決められません。ただし、次の条件ではCPU SIMDが有力になりやすいです。

  • 数個から小さな塊のデータをすぐ処理して返す。GPUへの転送・起動コストを回収しにくい。
  • CPUメモリ上の小さなデータを一度だけ処理する。計算量より転送量が目立ちやすい。
  • CPU上で分岐や検索を続ける。SwissTableのような検索では、CPU SIMDで候補を絞り、候補キーをCPU上ですぐ確認できる。
  • GPUがない、またはGPU利用が許されない環境。

小さなJSONを1件ずつ読む処理や、短いリアルタイム音声処理も、低遅延が大事でデータがCPU上にあるならCPU SIMDが有力です。処理を大量にまとめてGPU上で続けられる場合には結果が変わり得ます。CPUは少数処理の遅延を抑え、GPUは多数の処理量を伸ばすという違いと、CPU/GPU間の転送コストを踏まえて比べます。CUDA C++ Best Practices Guide

2.3 迷ったら、まず測る

「遅い」と感じても、原因が計算とは限りません。まず処理時間を測り、どの関数に時間を使っているか調べます。SIMDやGPUのためのデータ変換が、計算時間の節約を上回る場合もあります。比較するときは、データ準備、転送、起動、計算、後処理まで含めます。

ここまでのまとめ

大量の同型演算ならSIMDやGPUを候補にします。GPUは大きな仕事をまとめて処理できると有利になりやすく、少量・低遅延でCPU上にデータがあるならCPU SIMDが有力です。待ち時間や確保が主因なら、別の改善を先に考えます。

3章:SIMDのためにデータをどう並べる?

ここからは、実際にSIMDを学びたい方向けに進みます。CPUがまとめて複数の値を計算できても、値がメモリ上で離れていたらまとめて読む準備が必要です。

3.1 すでに連続しているデータ

配列のように同じ種類の値が続いている形は、SIMDと相性がよいです。

float values[] = { ... }  →  [x0][x1][x2][x3][x4][x5]...

画像の1行、音声サンプル、数値配列が例です。Google Highway、pulp、SimSIMDの基本カーネルは、このような連続配列をよく扱います。

3.2 フィールドを分ける(AoSからSoAへ)

位置と速度を持つ粒子を、次のように持っているとします。

struct Particle { float x, y, z, vx, vy, vz; };
Particle p[N];

xだけを計算するとき、xの値はメモリ上で飛び飛びです。フィールドごとに配列を分けると、同じ値を続けて読めます。

struct Particles {
    float x[N], y[N], z[N];
    float vx[N], vy[N], vz[N];
};

前者はAoS(Array of Structures、構造体の配列)、後者はSoA(Structure of Arrays、配列の構造体)です。大切なのは、同じ計算に使う値が隣り合っているかです。SoAへの変換時間や、ほかの処理で扱いにくくなるコストも含めて判断します。

3.3 値を小さくして、一命令に多く詰める

画素は赤・緑・青・透明度をそれぞれ8ビットで持つことがあります。1要素が小さいので、1命令に多くの画素を載せられます。

数値計算でも、元データをint8やfloat16で保存し、計算時にint16やfloat32へ広げて積和する方法があります。SimSIMDは型ごとのカーネルや累積型を使い分け、Pillow-SIMDは画素をunpack(広げる)、計算し、pack(詰め直す)処理を使います。

詰めれば必ず速いわけではありません。値の範囲、丸め誤差、変換コストを確認します。

3.4 SIMD用の中間データを作る

データをそのまま並べ替えにくい場合、SIMDで扱いやすい中間表現を作る方法があります。

  • simdjsonは入力をブロックに分け、構造文字の位置をbitmaskやindexにします。
  • SwissTable(Abseil)は値本体と別に制御byteを持ち、候補slotを先に絞ります。
  • Intel Embreeはレイの属性をレーンに並べた内部表現やBVHを交差計算へ渡します。

中間データを作ると準備、メモリ使用量、後処理が増えます。SIMDカーネルだけでなく処理全体を測ります。4章では、これらの考え方を実装パターンに分けて見ます。

4章:SIMDを実装する方法は一つではない

ここでは「コードをどの程度CPU命令に近づけて書くか」で整理します。まず幅を抽象化する方法、次に命令を直接使う方法、最後に言語・実行環境の境界を見ます。SIMDの対象と、命令の書き方は別の判断です。

4.1 幅を抽象化し、SIMDを狙う:Google Highwayとpulp

Google Highway(C++)とpulp(Rust)は、CPU命令名を毎行書かず、ロード、加算、FMA、maskなどの操作でカーネルを書けるようにします。Highwayはタグと関数で幅・ISAを扱い、pulpはtraitを使ってSIMD実装を渡し、sliceのベクトル部分と末尾部分を分けます。

for each block of N values:
    load N values
    add N values
    store N results
process the remaining values

同じアルゴリズムを別のCPU向けに展開しやすくなります。複数CPUで動かし、長く保守する汎用カーネルに向きます。ただし抽象化しただけで必ずSIMDになるわけではありません。連続データ、コンパイラが理解できるループ、末尾処理が必要です。

4.2 intrinsicで命令を直接選ぶ:SimSIMDとPillow-SIMD

SimSIMD(C/C++)は距離や内積、Pillow-SIMD(C)は画像のホットループに対象を絞ります。AVX2、SSE、NEONなどのintrinsic(組み込み関数)を明示し、ロード、比較、積和、shuffle、packを組み合わせます。

// AVX2でfloat32を8要素ずつ内積する考え方
__m256 acc = _mm256_setzero_ps();
for (size_t i = 0; i + 8 <= n; i += 8) {
    __m256 a8 = _mm256_loadu_ps(a + i);
    __m256 b8 = _mm256_loadu_ps(b + i);
    acc = _mm256_fmadd_ps(a8, b8, acc);
}

直接書くと命令やデータの詰め方を制御できます。その分、CPUごとの実装、fallback、末尾処理、正しさの確認が必要です。対象CPUと処理が明確で、プロファイル上重要だと分かった後に向きます。

4.3 WebAssembly:OpenCV.jsとWasm SIMD

OpenCV.js(OpenCV公式リポジトリ)では、JavaScriptから画像データを扱いますが、画像処理カーネルの多くはC++側にあります。WebAssembly SIMDを使えるビルド・実行環境なら、C++のHAL(ハードウェア差を吸収する層)からWasm SIMDへつながります。

WASM SIMDを直接書く例(C)

WebAssembly SIMDは128ビット幅のベクトル命令を提供します。EmscriptenではC/C++からwasm_simd128.hを使い、-msimd128を付けてコンパイルできます。

#include <wasm_simd128.h>
#include <stddef.h>

void add_f32(const float* a, const float* b, float* out, size_t n) {
    size_t i = 0;
    for (; i + 4 <= n; i += 4) {
        v128_t va = wasm_v128_load(a + i);
        v128_t vb = wasm_v128_load(b + i);
        v128_t sum = wasm_f32x4_add(va, vb);
        wasm_v128_store(out + i, sum);
    }
    for (; i < n; ++i) out[i] = a[i] + b[i];
}

ビルド例はemcc add.c -O3 -msimd128 -o add.jsです。生成されたJavaScript glue codeとWasmモジュールをブラウザやNode.jsで動かします。ブラウザ側の対応とfallbackの要否も確認します。Emscriptenは通常のC/C++ループを自動ベクトル化する方法も用意しているため、intrinsicを必ず書く必要はありません。Emscripten: Using SIMD with WebAssembly

画像カーネル自体が速くても、JavaScriptとの受け渡し、データコピー、Matの確保が支配的なら全体は速くなりません。Web向けではブラウザ、ビルド設定、メモリ境界も設計の一部です。

4.4 Python:NumPyやNumbaからCPU SIMDを使う

Pythonでは通常、SIMDレジスタを直接操作せず、配列ライブラリやJITコンパイラを通して利用します。

import numpy as np

a = np.arange(1_000_000, dtype=np.float32)
b = np.full_like(a, 10.0)
out = np.empty_like(a)

np.add(a, b, out=out)

Pythonコードは配列演算を指定し、SIMDを実行するのはNumPy内部のカーネルです。実際の命令はNumPyのビルド、CPU、型、入力サイズに依存します。NumPy公式資料は、CPU機能に応じた複数カーネルと実行時dispatchを説明しています。NumPy CPU/SIMD optimizations

Numbaでは、Python関数をCPU向けufuncへコンパイルできます。

import numpy as np
from numba import vectorize, float32

@vectorize([float32(float32, float32)], target="cpu")
def add_one_pair(x, y):
    return x + y

out = add_one_pair(a, b)

NumbaもSIMD命令を直接指定する書き方ではなく、コンパイラへ最適化を任せます。SIMD命令が生成されたか、速くなったかは環境に依存するため確認が必要です。Numba vectorize documentation

4章のまとめ

抽象化、intrinsic、Wasm、Python配列APIは、SIMDの書き方が異なります。どれを選んでも、データ配置、処理量、末尾、呼び出し境界を考える必要があります。次章では、元のアルゴリズム自体をSIMD向きへ組み替える例を見ます。

5章:一見SIMDにしにくい仕事をどう組み替える?

SIMDは、数値配列だけに使うものではありません。処理全体をそのままベクトル化できなくても、候補をまとめて判定する部分を作る方法があります。

5.1 文字を分類してから文法を読む:simdjson

simdjsonは入力文字をブロック単位で分類し、JSONの構造文字候補をbitmaskやindexにします。その後のstageで文法と値を解釈します。

SIMDが効くのは文字分類、UTF-8検査、構造位置の抽出です。文法の依存関係、数値変換、文字列の利用まで一度にSIMDになるわけではありません。入力paddingや中間indexを用意する分のコストもあります。詳しい前段・stage 1・後段・stage 2の流れはAppendix Aで説明します。

5.2 キーの前にmetadataを見る:SwissTable

SwissTable(Abseil)は値とは別に制御byte列を持ち、ハッシュの一部で候補を絞ります。SIMDが効くのは、連続した制御byte群の比較と状態判定です。ハッシュ計算、probeの制御、候補に対する実際のoperator==は残ります。

つまり、キー比較そのものをSIMDで速くするより、キー比較を行う候補数を減らします。Appendix Bでは、検索の前段・SIMD段階・後段に分けて説明します。

5.3 数式は揃え、探索の発散は別に扱う:Intel Embree

Intel Embreeはレイトレーシング向けに、レイと境界箱・三角形などの交差式をベクトル型で計算します。laneごとの有効・無効をmaskで保ち、SSEやAVXなどCPU機能に応じたカーネルを選びます。

SIMDに向くのは交差判定の数式です。レイごとにBVHの違う枝へ進む探索、ポインタ追跡、ユーザーcallbackはレーンの動きが揃いにくく、SIMDの効率を落とします。数式カーネルと、そこへたどり着く探索の両方を設計します。

5章のまとめ

simdjsonとSwissTableは、SIMD化しにくそうな処理の一部を見つけ、候補抽出や中間表現を設けています。Embreeは計算をベクトル化しながら、不規則な探索の限界を扱います。SIMDの対象を見つけるために、データ構造や処理順序を変えるのも設計手段です。

6章:直接書く?抽象化に任せる?

どれか一つが常に正解ではありません。

方針 合う状況 負担
高水準のSIMD抽象(Highway、pulpの考え方) 複数CPUで動かしたい、長く保守する汎用カーネル 抽象APIやビルド構成を理解する
コンパイラにベクトル化を促す通常ループ 単純な配列ループで、まず低コストに試したい 生成命令を測定・確認する
intrinsicを直接使う 対象CPUとホットカーネルが明確で、細部を制御したい ISAごとの実装、fallback、保守が増える
データ構造・処理段階から変える 分岐、検索、メモリ配置が性能を決める 変換や中間データ、API設計も変わる

新しいプログラムなら、まず正しいscalar版とテストを用意し、処理時間を測ります。単純なループはコンパイラの自動ベクトル化を試します。複数ISA対応が必要なら抽象化を検討し、特定カーネルが支配的だと分かったらintrinsicを検討します。候補を減らせるなら、SwissTableやsimdjsonのようにデータ構造や処理段階を変える方が効く場合もあります。

7章:新しいプログラムを設計するときのチェックポイント

  • どの関数が実際に遅いか測ったか
  • 同じ処理を繰り返す部分があるか
  • データは連続しているか。必要ならSoAや中間表現へ変えられるか
  • 1要素を小さくするなら、値の範囲・精度・overflowを許容できるか
  • SIMDの末尾部分(tail)をどう処理するか
  • CPUが違っても動くfallbackがあるか
  • データ変換やdispatchを含む全体時間を測ったか
  • 小さい入力、ランダム入力、分岐が偏る入力も測ったか

8章:まとめ

SIMDは、同じ計算を複数のデータへまとめて行う仕組みです。まずは配列加算で「まとめて読む・計算する・書く」と理解すれば十分です。

使いどころは、処理の形と遅さの原因から考えます。大量の同型演算ならSIMDやGPUを候補にし、GPUは大きな仕事をまとめて実行できると有利になりやすく、少量・低遅延でCPUにデータがあるならCPU SIMDが有力です。待ち時間や確保が主因なら別の改善を考えます。

深く学ぶなら、データの並べ方、候補の絞り方、CPU差の吸収、SIMD化しない境界まで設計します。8つのOSSの実例が示すのは、命令だけでなく、SIMDが働けるデータと処理の形を作る工夫です。


Appendix:上級者向け解説とコード

本文ではパターンと要点を紹介しました。このAppendixでは、simdjsonとSwissTableについて、前段・SIMD処理・後段を含む全体の流れを追います。続けて、抽象化カーネルやlane maskのコード例を載せます。コードは要約・短縮例です。再利用時は各OSSのライセンスと周辺コードを確認してください。

Appendix A:simdjson―前段・SIMDカーネル・後段

JSON parserは、文字列の内側か外側か、escape文字か、といった状態に依存するので、全体を一度にSIMD化するのは難しい処理です。simdjsonは、処理を分類と文法解釈に分けます。

入力準備: padded buffer(末尾の安全な読み出し領域)
   ↓
前段 / stage 1
  blockをload
  quote・backslash・空白・構造文字を分類
  文字列内外・escape状態をmask/carryで更新
  UTF-8を検査
  構造位置をindex配列へ書く
   ↓
後段 / stage 2
  indexを順に読み、JSONの文法と値を解釈
  DOMならtape/値を作る。On-Demandなら要求された値を読む

stage 1は、複数byteの分類やUTF-8検査、bitmask生成をまとめて行えるためSIMD向きです。block境界をまたぐquoteやescapeの状態はcarryとして次blockへ渡します。SIMD maskはそのまま構文木にならず、構造位置のindexへ変換されます。

stage 2はindexを逐次消費し、ネスト、数値、文字列など依存関係の強い文法を解釈します。DOMではtapeや値の格納も必要です。On-Demand APIは全体を先に構築せず、要求された値を読むことで後段の作業を抑えます。したがって速度の設計点はstage 1だけでなく、「並列分類できる前段」と「逐次性が残る後段」の境界にもあります。simdjson parse_many設計資料

Appendix B:SwissTable―hash・候補抽出・キー確認

ハッシュ表検索は、hashを計算し、衝突先を探し、キーの等価性を確認します。SwissTableは検索全体をSIMD化するのでなく、逐次検索の中に制御byte群を一括で見る段階を入れています。

前段(1キー)
  hash(key)を計算
  H1でprobe開始位置、H2で候補判定byteを作る
  現在のprobe groupを選ぶ
   ↓
SIMD段階(group単位)
  制御byte群をload
  H2一致位置を比較してcandidate bitmaskを作る
  empty/deleted位置のmaskも作る
   ↓
後段(候補slot)
  maskの立ったslotだけkey equality
  一致すれば返す
  なければemptyで終了、または次groupをprobe

SIMDが効くのは制御byteの連続比較と状態判定です。hash計算やprobe group選択は通常スカラーです。後段の候補ごとのoperator==もスカラーで、文字列なら長さや内容に応じた比較になります。ただしH2で候補を先に絞るため、多くのslotは重い比較へ進みません。

制御byteの符号bitやempty/deleted値も比較とmask生成を意識して設計されています。大事なのはキー比較自体をSIMD化することではなく、キー比較を必要とする候補を少なくすることです。Abseil Swiss Tables設計資料

Appendix C:Google Highway―幅に依存しない積和

言語:C++(Google Highway API)

template <typename T>
HWY_INLINE T DotProductSIMD(const T* HWY_RESTRICT a,
                            const T* HWY_RESTRICT b, size_t n) {
  using D = hn::ScalableTag<T>;
  const D d;
  using V = hn::Vec<D>;
  const size_t N = hn::Lanes(d);
  V sum = hn::Zero(d);
  size_t i = 0;
  for (; i + N <= n; i += N)
    sum = hn::MulAdd(hn::Load(d, a + i), hn::Load(d, b + i), sum);
  if (i < n)
    sum = hn::MulAdd(hn::LoadN(d, a + i, n - i),
                     hn::LoadN(d, b + i, n - i), sum);
  return hn::ReduceSum(d, sum);
}

Appendix D:pulp―Rust traitでSIMD処理を渡す

言語:Rust

use pulp::{Arch, Simd, WithSimd};

struct TimesThree<'a>(&'a mut [f64]);
impl<'a> WithSimd for TimesThree<'a> {
    type Output = ();
    #[inline(always)]
    fn with_simd<S: Simd>(self, simd: S) {
        let (head, tail) = S::as_mut_simd_f64s(self.0);
        let three = simd.splat_f64s(3.0);
        for x in head { *x = simd.mul_f64s(three, *x); }
        for x in tail { *x *= 3.0; }
    }
}
fn scale(v: &mut [f64]) { Arch::new().dispatch(TimesThree(v)); }

Appendix E:SwissTable―制御byteから候補maskを得る

言語:C++(SSE2 intrinsicを使った短縮例)

BitMaskType Match(h2_t hash) const {
  auto wanted = _mm_set1_epi8(static_cast<char>(hash));
  auto equal = _mm_cmpeq_epi8(wanted, ctrl); // 16 byteを並列比較
  return BitMaskType(_mm_movemask_epi8(equal));
}

Appendix F:Embree―lane maskを保つ交差判定

言語:C++(Embreeのベクトル型を使った短縮例)

vbool<M> valid = valid0;
const vfloat<M> den = dot(tri_Ng, ray_dir);
const vfloat<M> U = dot(cross(tri_v0 - ray_org, ray_dir), tri_e2);
const vfloat<M> V = dot(cross(tri_v0 - ray_org, ray_dir), tri_e1);
valid &= (den != vfloat<M>(0.0f)) & (U >= 0.0f) &
         (V >= 0.0f) & (U + V <= abs(den));
if (none(valid)) return false;

実コードでは符号処理、tnear/tfar判定、hit情報、filterも扱います。上記はlaneごとの有効判定を示す短縮例です。

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?