instance24の基数制約は、21個あります。
このような多数の次元をラベル設定法で行うのは、常識外です。
頑張って、18個まで縮小することは出来ました。各ラベルは、18個の累計状態を維持すればよいことになりますが、18個の演算を行うことは大変です。一個づつ演算するよりは、18個を一回で演算したい訳ですが、そのようなSSE命令はありません。
そこで、そのようなルーチンを作成してもらいました。
#include <emmintrin.h> // SSE2
// A, B は 16バイト (unsigned char[16]) を想定
static inline int any_a_gt_b_u8_16(const unsigned char *A, const unsigned char *B)
{
// 16バイト読み込み(アラインされていなくてもOK)
__m128i a = _mm_loadu_si128((const __m128i*)A);
__m128i b = _mm_loadu_si128((const __m128i*)B);
// unsigned 比較をするために 0x80 を XOR して符号付き空間へ写像
const __m128i offset = _mm_set1_epi8((char)0x80);
a = _mm_xor_si128(a, offset);
b = _mm_xor_si128(b, offset);
// a > b (符号付き) をバイトごとに比較
__m128i gt = _mm_cmpgt_epi8(a, b);
// どこか1つでも a[i] > b[i] があればビットが立つ
int mask = _mm_movemask_epi8(gt);
return mask != 0; // true: 一つでも A > B の項がある
}
演算クロック数は?結論から言うと、この 16 バイト比較(ロード → XOR → 比較 → movemask)の総コストは、おおむね 6〜10 サイクル程度で収まります。
SSE2 世代でも十分に速く、パイプラインに素直に乗る処理です。
🔍 内訳(SSE2 の典型的レイテンシ/スループット)
以下は Intel/AMD の一般的な SSE2 実装のレイテンシ・スループットを基準にした概算です。
命令 レイテンシ スループット 備考 _mm_loadu_si1284–5 cycles 1/cycle メモリヒット前提 _mm_xor_si1281 cycle 1/cycle ALU _mm_cmpgt_epi81 cycle 1/cycle ALU _mm_movemask_epi81–3 cycles 1/cycle SSE→GPR 転送がボトルネック
if ((A+B)>C) return true;で同じロジックを実装して。A/B/Cは、unsigned char 16bytes A+Bは、ベクトル演算、1Byteでも成立すればTrueを返す。了解。
「A+B を 16 バイト SIMD 加算し、1 バイトでも (A[i] + B[i]) > C[i] があれば true」
これを SSE2 世代で最速になるように実装します。
🚀 最速 SSE2 実装(unsigned 8bit 加算 → unsigned 比較)
SSE2 には unsigned 比較命令が無いため、
比較は XOR 0x80 で signed 空間へ写像してから _mm_cmpgt_epi8 を使います。
✅ 最速コード
c#include <emmintrin.h>
static inline int any_a_plus_b_gt_c_u8_16(
const unsigned char *A,
const unsigned char *B,
const unsigned char *C)
{
__m128i a = _mm_loadu_si128((const __m128i*)A);
__m128i b = _mm_loadu_si128((const __m128i*)B);
__m128i c = _mm_loadu_si128((const __m128i*)C);
// A + B (wrap-around OK: unsigned char 加算は 8bit wrap で正しい)
__m128i sum = _mm_add_epi8(a, b);
// unsigned 比較のために 0x80 XOR
const __m128i off = _mm_set1_epi8((char)0x80);
__m128i sum_s = _mm_xor_si128(sum, off);
__m128i c_s = _mm_xor_si128(c, off);
// sum > c ?
__m128i gt = _mm_cmpgt_epi8(sum_s, c_s);
return _mm_movemask_epi8(gt) != 0;
}
同じくA+=B;をunsigned char [16]で。
今回は A += B(16 バイトの unsigned char ベクトル加算)を SSE2 で最速にするコードですね。
結論:
_mm_add_epi8 を使って A = A + B を 16 バイト同時に更新するのが SSE2 で最速です。
A[i] = (A[i] + B[i]) mod 256(unsigned char の wrap-around)も SSE2 の挙動と完全一致します。
🚀 最速 SSE2 実装(A += B; を 16 バイト同時に実行)
c#include <emmintrin.h>
static inline void add_u8_16(unsigned char *A, const unsigned char *B)
{
__m128i a = _mm_loadu_si128((const __m128i*)A);
__m128i b = _mm_loadu_si128((const __m128i*)B);
a = _mm_add_epi8(a, b); // 16 バイト同時に A += B
_mm_storeu_si128((__m128i*)A, a);
}
最速 SSE2(16-byte aligned 専用)A += B
c#include <emmintrin.h>
static inline void add_u8_16_aligned(unsigned char *A, const unsigned char *B)
{
__m128i a = _mm_load_si128((const __m128i*)A); // aligned load
__m128i b = _mm_load_si128((const __m128i*)B); // aligned load
a = _mm_add_epi8(a, b); // 16 バイト同時に A += B(8bit wrap)
_mm_store_si128((__m128i*)A, a); // aligned store
}
最速 SSE2(16-byte aligned 専用)A+B > C 判定
アラインされているので、
_mm_load_si128 / _mm_store_si128 を使い、
loadu を完全排除して最速化します。
c#include <emmintrin.h>
static inline int any_a_plus_b_gt_c_u8_16_aligned(
const unsigned char *A,
const unsigned char *B,
const unsigned char *C)
{
__m128i a = _mm_load_si128((const __m128i*)A); // aligned load
__m128i b = _mm_load_si128((const __m128i*)B); // aligned load
__m128i c = _mm_load_si128((const __m128i*)C); // aligned load
// A + B (unsigned 8bit wrap-around)
__m128i sum = _mm_add_epi8(a, b);
// unsigned 比較のため signed 空間へ写像
const __m128i off = _mm_set1_epi8((char)0x80);
__m128i sum_s = _mm_xor_si128(sum, off);
__m128i c_s = _mm_xor_si128(c, off);
// sum > c ?
__m128i gt = _mm_cmpgt_epi8(sum_s, c_s);
return _mm_movemask_epi8(gt) != 0;
}最速 SSE2(16-byte aligned)A -= B
unsigned char の減算は
A[i] = (A[i] - B[i]) mod 256
つまり 8bit wrap-around subtract です。
SSE2 の _mm_sub_epi8 がまさにこの動作をします。
✅ 最速コード(16-byte aligned 専用)
c#include <emmintrin.h>
static inline void sub_u8_16_aligned(unsigned char *A, const unsigned char *B)
{
__m128i a = _mm_load_si128((const __m128i*)A); // aligned load
__m128i b = _mm_load_si128((const __m128i*)B); // aligned load
a = _mm_sub_epi8(a, b); // 16 バイト同時に A -= B(8bit wrap)
_mm_store_si128((__m128i*)A, a); // aligned store
}
最速 SSE2(16-byte aligned)A += B(18 バイト版)
c#include <emmintrin.h>
static inline void add_u8_18_aligned(unsigned char *A, const unsigned char *B)
{
// 先頭 16 バイトを SSE2 で最速処理
__m128i a0 = _mm_load_si128((const __m128i*)A);
__m128i b0 = _mm_load_si128((const __m128i*)B);
a0 = _mm_add_epi8(a0, b0);
_mm_store_si128((__m128i*)A, a0);
// 残り 2 バイトをスカラで処理
A[16] = (unsigned char)(A[16] + B[16]);
A[17] = (unsigned char)(A[17] + B[17]);
}
🚀 最速 SSE2(16-byte aligned)A -= B(18 バイト版)
c#include <emmintrin.h>
static inline void sub_u8_18_aligned(unsigned char *A, const unsigned char *B)
{
// 先頭 16 バイトを SSE2 で最速処理
__m128i a0 = _mm_load_si128((const __m128i*)A);
__m128i b0 = _mm_load_si128((const __m128i*)B);
a0 = _mm_sub_epi8(a0, b0);
_mm_store_si128((__m128i*)A, a0);
// 残り 2 バイトをスカラで処理
A[16] = (unsigned char)(A[16] - B[16]);
A[17] = (unsigned char)(A[17] - B[17]);
}
6 バイト(16-byte aligned)版
c#include <emmintrin.h>
static inline int all_eq_u8_16_aligned(const unsigned char *A, const unsigned char *B)
{
__m128i a = _mm_load_si128((const __m128i*)A);
__m128i b = _mm_load_si128((const __m128i*)B);
__m128i x = _mm_xor_si128(a, b); // 差分ビット
__m128i z = _mm_setzero_si128();
__m128i eq = _mm_cmpeq_epi8(x, z); // x == 0 ?
return _mm_movemask_epi8(eq) == 0xFFFF; // 全バイト 0 なら A==B
}
18 バイト(先頭 16 バイトは 16-byte aligned)版
c#include <emmintrin.h>
static inline int all_eq_u8_18_aligned(const unsigned char *A, const unsigned char *B)
{
// 先頭 16 バイトを SSE2 で比較
__m128i a0 = _mm_load_si128((const __m128i*)A);
__m128i b0 = _mm_load_si128((const __m128i*)B);
__m128i x = _mm_xor_si128(a0, b0);
__m128i z = _mm_setzero_si128();
__m128i eq = _mm_cmpeq_epi8(x, z);
if (_mm_movemask_epi8(eq) != 0xFFFF)
return 0; // A!=B → false
// 残り 2 バイトをスカラで比較
if (A[16] != B[16]) return 0;
if (A[17] != B[17]) return 0;
return 1; // 全 18 バイト一致 → true
}
速 SSE2(16-byte aligned)A+B > C(18 バイト版)
c#include <emmintrin.h>
static inline int any_a_plus_b_gt_c_u8_18_aligned(
const unsigned char *A,
const unsigned char *B,
const unsigned char *C)
{
// --- 先頭 16 バイトを SSE2 で処理 ---
__m128i a0 = _mm_load_si128((const __m128i*)A);
__m128i b0 = _mm_load_si128((const __m128i*)B);
__m128i c0 = _mm_load_si128((const __m128i*)C);
// A + B(8bit wrap)
__m128i sum0 = _mm_add_epi8(a0, b0);
// unsigned 比較のため signed 空間へ写像
const __m128i off = _mm_set1_epi8((char)0x80);
__m128i sum_s = _mm_xor_si128(sum0, off);
__m128i c_s = _mm_xor_si128(c0, off);
// sum > c ?
__m128i gt0 = _mm_cmpgt_epi8(sum_s, c_s);
if (_mm_movemask_epi8(gt0))
return 1; // 先頭 16 バイトで成立
// --- 残り 2 バイトをスカラで処理 ---
unsigned char s16 = (unsigned char)(A[16] + B[16]);
if (s16 > C[16]) return 1;
unsigned char s17 = (unsigned char)(A[17] + B[17]);
if (s17 > C[17]) return 1;
return 0; // 全 18 バイトで不成立
}