モジュール jdk.incubator.vector
パッケージ jdk.incubator.vector

クラスVector<E>

java.lang.Object
jdk.incubator.vector.Vector<E>
型パラメータ:
E - ETYPEのボックス版、ベクトルの要素型
直系の既知のサブクラス:
ByteVector, DoubleVector, FloatVector, IntVector, LongVector, ShortVector

public abstract class Vector<E> extends Object
A bytelongfloatなどの一部の固定要素型である、固定数の「レーン」の順序。 各レーンには、要素タイプの独立した値が含まれます。 ベクトルに対する演算は通常レーンワイズであり、一部のスカラー演算子(「加算」など)が参加ベクトルのレーンに分散され、通常、レーンに様々なスカラー結果が含まれるベクトル結果が生成されます。 サポートしているプラットフォームで実行すると、ハードウェアでレーンワイズ演算をパラレルに実行できます。 この並列度スタイルは、「単一指図複数データ」 (SIMD)並列度と呼ばれます。

SIMD形式のプログラミングでは、ベクトル・レーン内のほとんどの演算は無条件ですが、関連付けられたVectorMaskの制御下で、blend()などのマスクされた演算を使用して条件付き実行の効果を得ることができます。 厳密にレーンワイズ・フロー以外のデータ移動は、多くの場合、関連するVectorShuffleの制御下で、クロスレーン演算を使用して実現されます。 レーン・データまたはベクトル全体(あるいはその両方)は、様々な種類のレーンワイズ「変換」またはバイト単位の再フォーマットreinterpretationsを使用して再フォーマットでき、多くの場合、入力ベクトルとは異なる代替ベクトル・フォーマットを選択する反射VectorSpeciesオブジェクトの制御下で再フォーマットされます。

Vector<E>では、すべての要素タイプに共通のベクトル演算(メソッド)のセットを宣言します。 これらの一般的な演算には、レーン値への汎用アクセス、データの選択と移動、再フォーマット、およびすべてのプリミティブ型に共通する特定の算術演算子と論理演算子の(加算や比較など)が含まれます。

Vectorのパブリック・サブタイプ」は、特定の要素タイプに対応します。 これらは、レーン値へのボックス化されていないアクセス、整数要素型の値に対するビット単位の演算、浮動小数点要素型の値に対するトランザクション演算など、その要素型に固有のさらなる演算を宣言します。

add演算子などの一部のレーンワイズ演算は、フル・サービス名前付き演算として定義されます。この場合、Vectorの対応するメソッドはマスキングされたオーバーロードおよびマスキングされていないオーバーロードになり、(サブクラス)は(サブクラスを返す)および追加のスカラー・ブロードキャスト・オーバーロード(マスク済とマスク解除済の両方)もオーバーライドします。 min演算子などのその他のレーンワイズの演算は、部分的にサービスされる(フル・サービスではありません)名前付き演算として定義されます。ここでは、Vectorまたはサブクラス(あるいはその両方)の対応するメソッドによって、可能性のあるすべてのオーバーロードが提供され、(一般的に、スカラー・ブロード・キャストの過負荷を伴うマスクされていないバリアント)がオーバーライドされます。 最後に、レーンワイズすべての演算の(前述のように指定されたもの、または名前のないメソッドに関するもの)には、対応するoperator tokenVectorOperatorsで静的定数として宣言されています。 各演算子トークンは、ADD演算子トークン用のa + bなど、演算のシンボリックJava式を定義します。 「単項レーンワイズ」演算などの一般的なレーンワイズ演算トークン受入れメソッドは、Vectorで提供され、フル・サービス名前付き演算と同じバリアントになります。

このパッケージには、サポートされている各要素タイプに対応するVectorのパブリック・サブタイプが含まれます: ByteVectorShortVectorIntVectorLongVectorFloatVectorおよびDoubleVector

ETYPEと呼ばれるベクトルの「要素型」は、プリミティブ型byte, short, int, long, floatまたはdoubleのいずれかです。

Vector<E>Eタイプは、ETYPEboxedバージョンです。 たとえば、タイプがVector<Integer>の場合、EパラメータはIntegerで、ETYPEintです。 このようなベクトルでは、各レーンにプリミティブint値が含まれます。 このパターンは、他のプリミティブ型にも適用されます。 (「Java言語仕様」5.1.7および5.1.8の項も参照してください。)

ベクトルのlengthはレーン数で、そこに含まれるレーンの数です。 この数値は、コンテキストが属するベクトルを明確にするときにVLENGTHとも呼ばれます。 各ベクトルには独自の固定VLENGTHがありますが、ベクトルのインスタンスごとに長さが異なる場合があります。 VLENGTH は、ベクトル演算の基礎となるVLENGTHスカラー演算子のスカラー実行と比較して、単一のベクトル演算のSIMDパフォーマンス向上を見積もるため、重要な数値です。

Shapesとspecies

ベクトルの情報容量は、ベクトルのVSHAPEとも呼ばれるベクトル・シェイプによって決定されます。 可能な各VSHAPEは、VectorShape列挙のメンバーによって表され、そのシェイプのすべてのベクトルで共通に共有される実装形式を表します。 したがって、ベクトルの「ビット単位のサイズ」は、ベクトルのシェイプに応じて決定されます。

一部のJavaプラットフォームでは、単一のシェイプにのみ特別なサポートが提供されますが、その他のプラットフォームでは複数のシェイプがサポートされます。 一般的なプラットフォームでは、このAPIで記述されているすべてのシェイプがサポートされているとはかぎりません。 このため、ほとんどのベクトル演算は単一の入力シェイプで機能し、出力時に同じシェイプを生成します。 シェイプを変更する演算は、そのようなshape-changingとして明確に文書化されていますが、ほとんどの演算はシェイプ不変です。これにより、単一シェイプのみをサポートする不利なプラットフォームを回避できます。 現在のJavaプラットフォームには、一般的なSIMD計算用の「優先シェイプ」、または特定のレーン・タイプで使用可能な最大のシェイプを検出する問合せがあります。 移植性を確保するために、このAPIを使用するコードは、サポートされているシェイプを問い合せてから、選択したシェイプ内のシェイプ不変演算ですべてのデータを処理する必要があります。

要素タイプとベクトル・シェイプの一意の組合せによって、一意のベクトル種が決まります。 ベクトル種は、同じシェイプのすべてのベクトルとETYPEで共有されるVectorSpecies<E>の固定インスタンスによって表されます。

特に明記されていないかぎり、レーンワイズ・ベクトル演算では、すべてのベクトル入力がまったく同じVSHAPEおよびVLENGTHを持つ必要があります。つまり、これらの入力はまったく同じ種である必要があります。 これにより、対応するレーンを明確にペアにすることができます。 check()メソッドは、このチェックを明示的に実行する簡単な方法を提供します。

ベクトル・シェイプ、VLENGTHおよびETYPEはすべて相互に制約されるため、「各レーンのビット・サイズ」VLENGTH時間はベクトル・シェイプのビット・サイズと常に一致する必要があります。 したがって、ベクトルの「再解釈」の長さは、レーン・サイズの半分であるか、シェイプを変更した場合にのみ倍増することがあります。 同様に、ベクトルを再解釈すると、長さが半分になるか、ベクトルのシェイプが変更された場合にのみ、レーン・サイズが倍増する可能性があります。

Vectorサブタイプ

Vectorは、すべての要素タイプ(加算など)に共通のベクトル演算(メソッド)のセットを宣言します。 具象要素型を持つVectorのサブクラスは、その要素型(レーン内の要素値へのアクセス、整数要素型の値に対する論理演算、浮動小数点要素型の値に対する置換演算など)に固有の追加演算を宣言します。 Vectorには、サポートされている要素タイプのセット(ByteVectorShortVectorIntVectorLongVectorFloatVectorおよびDoubleVector)に対応する6つの抽象サブクラスがあります。 これらのクラスは、型固有の演算とともに、ベクトル値(Vectorのインスタンス)の作成をサポートします。 サポートされている種に対応するstatic定数を公開し、これらの型のstaticメソッドは通常、パラメータとして種を取ります。 たとえば、FloatVector.fromArrayは、指定されたfloat配列からロードされた要素を使用して、指定された種のfloatベクトルを作成して返します。 実行時コンパイラによってVector値が最適に作成および使用されるように、Speciesインスタンスをstatic finalフィールドに保持することをお薦めします。

型指定ベクトル・クラスによって定義される静的定数の例として、定数FloatVector.SPECIES_256はレーンがfloatであり、ベクトル・サイズが256ビットである一意の種です。 ここでも、定数FloatVector.SPECIES_PREFERREDは、現在実行中のJavaプラットフォームでfloatベクトル・レーンの処理を最適にサポートする種です。

別の例として、DoubleVector.broadcast(dsp, 0.5)を呼び出すことで(double)0.5のブロードキャスト・スカラー値を取得できますが、結果のベクトルの種(したがって、シェイプと長さ)を選択するには、引数dspが必要です。

レーンワイズ演算

ベクトルに対する演算を定義する場合は、「レーン」という用語を使用します。 ベクトル内のレーンの数は、保持するスカラー要素の数です。 たとえば、floatおよびシェイプS_256_BIT型のベクトルには、32*8=256以降8つのレーンがあります。

ベクトルに対するほとんどの演算はレーンワイズです。つまり、演算は基礎となるスカラー演算子で構成され、入力ベクトルの個別のレーンごとに繰り返されます。 同じ型の追加のベクトル引数がある場合、それらのレーンは最初の入力ベクトルのレーンに整列されます。 (これらはすべて共通のVLENGTHを持っている必要があります。) ほとんどのレーンワイズの操作では、レーンワイズの操作による出力結果は、操作への入力のVLENGTHと等しいVLENGTHになります。 したがって、このようなレーンワイズ操作は基本定義でlength-invariantです。

長さ不変性の原則は別の基本原則と結合され、ほとんどの長さ不変なレーンワイズ操作もshape-invariantです。これは、レーンワイズ操作の入力と出力には共通のVSHAPEがあることを意味します。 論理結果(不変のVLENGTHを使用)が不変のVSHAPEに収まらないために原則が競合する場合、結果の拡張および縮小は「特殊な表記規則」で明示的に処理されます。

ベクトル演算は様々なカテゴリにグループ化でき、その動作は通常、基礎となるスカラー演算子によって指定できます。 次の例では、ETYPEは演算(int.classなど)の要素タイプで、EVectorは対応する具体的なベクトル・タイプ(IntVector.classなど)です。

  • w = v0. neg()などの「レーンワイズ単項」演算は、単項スカラー演算子をレーン全体に分散して入力ベクトルを取り、同じタイプおよびシェイプの結果ベクトルを生成します。 入力ベクトルaのレーンごとに、基礎となるスカラー演算子がレーン値に適用されます。 結果は、同じレーンのベクトル結果に配置されます。 次の疑似コードは、この演算カテゴリの動作を示しています:
    
     ETYPE scalar_unary_op(ETYPE s);
     EVector a = ...;
     VectorSpecies<E> species = a.species();
     ETYPE[] ar = new ETYPE[a.length()];
     for (int i = 0; i < ar.length; i++) {
         ar[i] = scalar_unary_op(a.lane(i));
     }
     EVector r = EVector.fromArray(species, ar, 0);
     
  • w = v0. add(v1)などの「レーンワイズ二項」演算は、二項スカラー演算子をレーン全体に分散して2つの入力ベクトルを取り、同じタイプおよびシェイプの結果ベクトルを生成します。 abの両方の入力ベクトルのレーンごとに、基礎となるスカラー演算子がレーン値に適用されます。 結果は、同じレーンのベクトル結果に配置されます。 次の疑似コードは、この演算カテゴリの動作を示しています:
    
     ETYPE scalar_binary_op(ETYPE s, ETYPE t);
     EVector a = ...;
     VectorSpecies<E> species = a.species();
     EVector b = ...;
     b.check(species);  // must have same species
     ETYPE[] ar = new ETYPE[a.length()];
     for (int i = 0; i < ar.length; i++) {
         ar[i] = scalar_binary_op(a.lane(i), b.lane(i));
     }
     EVector r = EVector.fromArray(species, ar, 0);
     
  • 単項演算および二項演算からの一般化では、レーンワイズn項演算はN入力ベクトルv[j]を使用して、n項スカラー演算子をレーン全体に分散し、同じ型およびシェイプの結果ベクトルを生成します。 w = v0. fma(v1,v2)などのいくつかの三項演算を除き、このAPIではレーンワイズn項演算はサポートされていません。 すべての入力ベクトルv[j]のレーンごとに、基礎となるスカラー演算子がレーン値に適用されます。 結果は、同じレーンのベクトル結果に配置されます。 次の疑似コードは、この演算カテゴリの動作を示しています:
    
     ETYPE scalar_nary_op(ETYPE... args);
     EVector[] v = ...;
     int N = v.length;
     VectorSpecies<E> species = v[0].species();
     for (EVector arg : v) {
         arg.check(species);  // all must have same species
     }
     ETYPE[] ar = new ETYPE[a.length()];
     for (int i = 0; i < ar.length; i++) {
         ETYPE[] args = new ETYPE[N];
         for (int j = 0; j < N; j++) {
             args[j] = v[j].lane(i);
         }
         ar[i] = scalar_nary_op(args);
     }
     EVector r = EVector.fromArray(species, ar, 0);
     
  • w0 = v0. convert(VectorOperators.I2D, 0)などの「レーンワイズ変換」演算は、単項スカラー変換演算子をレーン全体に分散して入力ベクトルを取り、変換された値の論理結果を生成します。 論理結果(または少なくともその一部)は、入力ベクトルと同じシェイプのベクトルで表されます。

    他のレーンワイズ演算とは異なり、変換では、レーン・タイプを入力(domain)タイプから出力(範囲)タイプに変更できます。 レーンのサイズは、タイプに応じて変わる場合があります。 サイズ変更を管理するために、レーンワイズ変換メソッドでは、「他の場所で説明されている」partパラメータの管理下で「部分的な結果」を製品化できます。 (前述の例に従うと、変換されたレーン値の別のグループをw1 = v0.convert(VectorOperators.I2D, 1)として取得できます。)

    次の疑似コードは、intからdoubleへの変換の特定の例におけるこの演算カテゴリの動作を示しており、シェイプの不変性を維持するために下位または上位レーンの(partに依存)が保持されています:

    
     IntVector a = ...;
     int VLENGTH = a.length();
     int part = ...;  // 0 or 1
     VectorShape VSHAPE = a.shape();
     double[] arlogical = new double[VLENGTH];
     for (int i = 0; i < limit; i++) {
         int e = a.lane(i);
         arlogical[i] = (double) e;
     }
     VectorSpecies<Double> rs = VSHAPE.withLanes(double.class);
     int M = Double.BITS / Integer.BITS;  // expansion factor
     int offset = part * (VLENGTH / M);
     DoubleVector r = DoubleVector.fromArray(rs, arlogical, offset);
     assert r.length() == VLENGTH / M;
     

  • e = v0. reduceLanes(VectorOperators.ADD)などの「レーンを越える削減」演算は、入力ベクトルのすべてのレーン要素で動作します。 累積関数は、スカラー結果を生成するためにすべてのレーン要素に適用されます。 リダクション演算が連想的な場合、結果は、指定された連想スカラー二項演算およびアイデンティティ値を使用して、任意の順序でレーン要素を演算することで累積できます。 それ以外の場合は、リダクション演算によって累積の順序が指定されます。 次の疑似コードは、この演算カテゴリが連想型の場合の動作を示しています:
    
     ETYPE assoc_scalar_binary_op(ETYPE s, ETYPE t);
     EVector a = ...;
     ETYPE r = <identity value>;
     for (int i = 0; i < a.length(); i++) {
         r = assoc_scalar_binary_op(r, a.lane(i));
     }
     
  • w = v0. rearrange(shuffle)などの「レーンを越える移動」演算は、入力ベクトルのすべてのレーン要素に対して動作し、データ依存の方法で出力ベクトルの「異なるレーン」に移動します。 移動は、VectorShuffleや、移動の起点を定義するスカラー索引などの補助データムによって制御されます。 次の擬似コードは、シャッフルの場合のこの演算カテゴリの動作を示しています:
    
     EVector a = ...;
     Shuffle<E> s = ...;
     ETYPE[] ar = new ETYPE[a.length()];
     for (int i = 0; i < ar.length; i++) {
         int source = s.laneSource(i);
         ar[i] = a.lane(source);
     }
     EVector r = EVector.fromArray(a.species(), ar, 0);
     
  • 「マスクされた演算」は、前のいずれかの演算(レーンワイズまたはクロス・レーン)のバリエーションであり、この演算は余分な末尾のVectorMask引数を取ります。 レーンではマスクが設定されていますが、マスク引数がないかのように動作しますが、マスクが設定解除されているレーンでは、基礎となるスカラー演算は抑制されます。 マスクされた演算は、「他の場所の詳細」で説明されています。
  • マスクされたレーンワイズ二項演算の非常に特殊なケースは、「ブレンド」です。これは、マスクmに応じて一方の入力からレーン値を選択して、2つの入力ベクトルaおよびbでレーンワイズ演算を行います。 mが設定されているレーンでは、bの対応する値が結果に選択されます。それ以外の場合は、aの値が選択されます。 したがって、ブレンドは、ベクトル化されたバージョンのJava三項選択式m?b:aとして機能します :
    
     ETYPE[] ar = new ETYPE[a.length()];
     for (int i = 0; i < ar.length; i++) {
         boolean isSet = m.laneIsSet(i);
         ar[i] = isSet ? b.lane(i) : a.lane(i);
     }
     EVector r = EVector.fromArray(species, ar, 0);
     
  • m = v0. lt(v1)などの「レーンワイズ二項テスト」演算は、二項スカラー比較をレーン全体に分散し、ブールのベクトルではなく「ベクトル・マスク」を生成する、2つの入力ベクトルを取ります。 abの両方の入力ベクトルのレーンごとに、基礎となるスカラー比較演算子がレーン値に適用されます。 結果として得られるbooleanは、同じレーンのベクトル・マスク結果に配置されます。 次の疑似コードは、この演算カテゴリの動作を示しています:
    
     boolean scalar_binary_test_op(ETYPE s, ETYPE t);
     EVector a = ...;
     VectorSpecies<E> species = a.species();
     EVector b = ...;
     b.check(species);  // must have same species
     boolean[] mr = new boolean[a.length()];
     for (int i = 0; i < mr.length; i++) {
         mr[i] = scalar_binary_test_op(a.lane(i), b.lane(i));
     }
     VectorMask<E> m = VectorMask.fromArray(species, mr, 0);
     
  • 二項比較と同様に、m = v0. test(IS_FINITE)などの「レーンワイズ単項テスト」演算は、スカラー述語(a test function)をレーン全体に分散して入力ベクトルを取り、「ベクトル・マスク」を生成します。

ベクトル演算が前述のカテゴリのいずれにも属さない場合、メソッドのドキュメントでは、入力ベクトルのレーンの処理方法を明示的に指定し、必要に応じて擬似コードを使用した動作を示します。

ほとんどのレーンワイズ二項および比較演算では、ベクトルのかわりにスカラーを2番目の入力として受け入れる便利なオーバーロードが提供されます。 この場合、スカラー値は「ブロードキャスト」によってベクトルにプロモートされ、最初の入力と同じレーン構造になります。 たとえば、doubleベクトルのすべてのレーンをスカラー値1.1で乗算する場合、式v.mul(1.1)は、v.mul(v.broadcast(1.1))v.mul(DoubleVector.broadcast(v.species(), 1.1))などの明示的なブロードキャスト演算と同等の式よりも簡単に演算できます。 特に指定がないかぎり、スカラー・バリアントは常に、適切なbroadcast演算を使用して、各スカラー値が最初のベクトル入力と同じ種のベクトルに最初に変換されるかのように動作します。

マスクされた演算

多くのベクトル演算では、オプションのmask引数を受け入れ、基礎となるスカラー演算子に含めるレーンを選択します。 存在する場合、mask引数はメソッド引数リストの最後に表示されます。

mask引数の各レーンは、setまたはunset状態のブールです。 mask引数が設定されていないレーンの場合、ベースとなるスカラー演算子は抑制されます。 この方法では、マスク・レーンが設定されていない場合を除き、ベクトル演算でSIMD並列性を失わずにスカラー制御フロー演算をエミュレートできます。

マスクによって抑止された演算は、基礎となるスカラー演算子で可能性がある場合でも、ソートの例外または副作用を引き起こすことはありません。 たとえば、範囲外の配列要素にアクセスしたり、整数値をゼロで除算したりするように見える未設定のレーンは無視されます。 抑制されたレーンの値は、演算全体の結果に参加または表示されることはありません。

抑制された演算に対応する結果レーンには、次のように、特定の演算に依存するデフォルト値が入力されます:

  • マスクされた演算が単項演算、二項演算、n項演算または論理演算の場合、抑制されたレーンは、「ブレンド」による場合と同様に最初のベクトル・オペランド(つまり、メソッド・コールを受信するベクトル)から入力されます。
  • マスクされた演算が別のベクトルからのメモリー負荷またはslice()である場合、抑制されたレーンはロードされず、すべてゼロ・ビットで構成されるETYPEのデフォルト値が入力されます。 未設定のレーンは、仮想的に対応するメモリーのロケーションが(配列索引範囲外であるため)に存在しない場合でも、例外を引き起こすことはありません。
  • 演算が、レーン索引を提供するオペランドを持つクロス・レーン演算である場合(VectorShuffleまたはVectorタイプ)、抑制されたレーンは計算されず、ゼロのデフォルト値が入力されます。 通常、無効なレーン索引はIndexOutOfBoundsExceptionを要求しますが、レーンが設定されていない場合は、索引に関係なくゼロ値が静止して置換されます。 このルールは、マスクされたメモリー・ロードの前述のルールに似ています。
  • マスクされた演算がメモリー・ストアまたは別のベクトルへのunslice()である場合、抑制されたレーンは格納されず、対応するメモリーまたはベクトルのロケーションの(もしあれば)は変更されません。

    (Note: 抑制されたレーンでは、競合状態などのメモリー効果は発生しません。 つまり、実装では、未設定のレーンの既存の値は機密的に書き換えられません。 Javaメモリー・モデルでは、メモリー変数を現在の値に再割当てしても操作は行われません。別のスレッドから競合するストアが静止して元に戻される可能性があります。)

  • マスクされた演算がリダクションの場合、抑制されたレーンはリダクションで無視されます。 すべてのレーンが抑制されると、特定のリダクション演算に応じて適切なニュートラル値が返され、そのメソッドのマスクされたバリアントによってドキュメント化されます。 (つまり、ユーザーは、オール・アン・セット・マスクを使用してダミー・ベクトルのリダクションを実行することで、プログラムでニュートラル値を取得できます。)
  • マスクされた演算が比較演算の場合、抑制された入力値に関係なく、抑制された比較演算がfalseを返したかのように、結果のマスクの抑制された出力レーン自体が設定解除されます。 実際には、比較演算がマスクされずに実行された後、結果が制御マスクと交差していました。
  • マスキングされたレーン横断運動などのその他の場合、マスキングの特定の効果はメソッドのマスク・バリアントによってドキュメント化されます。

たとえば、2つの入力ベクトルaおよびbでのマスクされた二項演算では、マスクが設定解除されているレーンの二項演算が抑制され、aからの元のレーン値が保持されます。 次の疑似コードは、この動作を示しています:


 ETYPE scalar_binary_op(ETYPE s, ETYPE t);
 EVector a = ...;
 VectorSpecies<E> species = a.species();
 EVector b = ...;
 b.check(species);  // must have same species
 VectorMask<E> m = ...;
 m.check(species);  // must have same species
 boolean[] ar = new boolean[a.length()];
 for (int i = 0; i < ar.length; i++) {
     if (m.laneIsSet(i)) {
         ar[i] = scalar_binary_op(a.lane(i), b.lane(i));
     } else {
         ar[i] = a.lane(i);  // from first input
     }
 }
 EVector r = EVector.fromArray(species, ar, 0);
 

レーン順序およびバイト順序

特定のベクトルに格納されるレーン値の数は、その「ベクトル長さ」またはVLENGTHと呼ばれます。 ベクトル・レーンは、0という番号の最初のレーン、1という番号の次のレーン、VLENGTH-1という番号の最後のレーンなどで、最初から最後まで「順次」を順序付けたものとみなすと便利です。 これは一時的な順序であり、下位番号のレーンは上位番号の(後で)レーンより前とみなされます。 このAPIは、"left"、"right"、"high"、"low"などの空間用語よりもこれらの用語を優先して使用します。

時間的な用語は、ベクトルに対して適切に機能します。これは、(通常は)がワークロード要素の長いシーケンスで小さい固定サイズのセグメントを表し、ワークロードが概念的に時間順にトラバースされるためです。 (これはmentalモデルです: マルチ・コアの分割統治技術は除外されません。) したがって、スカラー・ループがベクトル・ループに変換されると、ワークロード内の隣接スカラー・アイテム(1つ前、もう1つ後)は単一のベクトル(1つ前、もう1つ後)内の隣接レーンとして終了します。 ベクトル境界では、前のベクトルの最後のレーン・アイテムは、直後のベクトルの最初のレーン・アイテムである(および直前)に隣接します。

ベクトルは空間用語でも考慮されることがあります。空間用語では、最初のレーンは仮想用紙の端に配置され、後続のレーンはその横に順番に表示されます。 空間的な用語を使用する場合、すべての方向は同等に配置できます: ベクトル表記法の中には、左から右にレーンを表示するものもあれば、右から左にレーンを表示するものもあれば、上から下にレーンを表示するものもあります。 スペース(left、right、high、low)のかわりに(before、after、first、last)の時間の言語を使用すると、誤解を避ける可能性が高くなります。

ベクトル・レーンに関して空間言語より時間的に優先するもう1つの理由は、"left"、"right"、"high"および"low"という用語がスカラー値のビット間の関係を説明するために広く使用されていることです。 指定された型の左端または最上位のビットは符号ビットである可能性が高く、右端または最下位のビットは算術的に最下位ビットである可能性が高くなります。 これらの用語をベクトル・レーンに適用すると、混乱が生じます。ただし、2つの隣接するベクトル・レーンがある場合、1つのレーンが近傍よりも何らかの形で有意義なアルゴリズムを見つけることは比較的まれであり、このような場合でも、どのネイバーがより重要であるかを知る一般的な方法はありません。

用語をまとめると、ベクトルの情報構造は、内部的に空間的に("low"から"high"または"right"から"left"のいずれか)で順序付けされたビット文字列のレーン("first", "next", "前", "後で", "last", etc.)の一時的なシーケンスとして表示されます。 レーンのプリミティブ値は、通常の方法でこれらのビット文字列からデコードされます。 ほとんどのベクトル演算子は、ほとんどのJavaスカラー演算子と同様に、プリミティブ値をアトミック値として扱いますが、一部の演算子では内部ビット文字列構造が明らかになります。

ベクトルがメモリーからロードされるか、メモリーに格納される場合、ベクトル・レーンの順序は、メモリー・コンテナの固有の順序を持つ「常に一貫性がある」です。 これは、バイト・オーダーの詳細のために個々のレーン要素が"バイト・スワップ"の対象であるかどうかにかかわらず当てはまります。 したがって、ベクトルのスカラー・レーン要素は"スワップされたバイト"である可能性がありますが、レーンの順序変更を実行する明示的なメソッド・コールを除き、レーン自体は順序変更されません。

ベクトル・レーン値が同じタイプのJava変数に格納されている場合、ベクトル・ハードウェアの実装でこのようなスワッピングが必要な場合にのみバイト・スワッピングが実行されます。 したがって、無条件で不可視です。

このAPIは、有用なフィクションとして、ベクトル・レーンのバイトが「リトル・エンディアン順序」の大きいレーンのスカラーに構成されているという一貫した状況を表します。 つまり、ベクトルをJavaバイト配列に格納すると、ネイティブ・メモリーの順序や、ベクトル・ユニット・レジスタ内のバイト順序(もしあれば)に関係なく、すべてのプラットフォームでベクトル・レーン値の連続バイトがリトル・エンディアン順で表示されます。

この仮想リトル・エンディアン順序は、ベクトル・ビットを変更せずにレーン境界が破棄および再描画されるような方法で「再解釈キャスト」を適用した場合にも表示されます。 このような演算では、隣接する2つのレーンが単一の新しいレーン(またはその逆)にバイトを提供し、2つのレーンの順序によって単一レーン内のバイトの算術順序が決定されます。 この場合、リトル・エンディアン規則によって移植可能な結果が提供されるため、すべてのプラットフォームで、以前のレーンでは下位(右方向)ビットがコントリビュートされ、後続のレーンでは上位(左向き)ビットがコントリビュートされる傾向があります。 ByteVectorとその他の非バイト・ベクトル間の「再解釈キャスト」では、移植可能なセマンティクスを明確にするためにこの規則が使用されます。

レーンの順序をレーンごとのバイト順序に関連付けるリトル・エンディアンのフィクションは、同等のビッグ・エンディアンのフィクションよりも若干推奨されます。これは、一部の関連する式(特にレーン構造の変更後にバイト数を再番号付けする式)がはるかに単純であるためです。 もっとも古いバイトは、リトル・エンディアン規則が使用されている場合にかぎり、レーン構造のすべての変更において不変的に早いバイトです。 この根本的な原因は、スカラー内のバイトに、最も重要度の低い(右端)から最も重要度の高い(左端)までの番号が付けられ、その逆も同様ではないことです。 符号ビットの番号がゼロ(一部のコンピュータと同様)である場合、このAPIはビッグ・エンディアンのフィクションに到達し、ベクトル・バイトの統一アドレスを作成します。

メモリー操作

前述のように、ベクトルはメモリーからロードして戻すことができます。 オプションのマスクを使用すると、読み取りまたは書き込みの対象となる個々のメモリー・ロケーションを制御できます。 ベクトルのシェイプによって、ベクトルが占有するメモリー量が決まります。 実装には通常、マスキングがない場合、レーンはスカラー型の配列内の単一スカラー値の稠密な(gap-free)系列と同じように、バック・トゥ・バック値の稠密なシーケンスとしてメモリーに格納されるというプロパティがあります。 このような場合、メモリーの順序はレーンの順序に正確に対応します。 最初のベクトル・レーン値は、メモリー内の最初の位置を占有し、ベクトルの長さまで続きます。 さらに、ストアド・ベクトル・レーンのメモリー順序は、Java配列またはMemorySegmentでの索引値の増加に対応しています。

ベクトルを保持する配列またはセグメント内で、ストレージされたベクトル値を単一のプリミティブ値として読み書きできるように、レーン・ストレージのバイト順序が選択され、ベクトル内のレーンワイズ値と同じ値が生成されます。 このことは、ベクトルの内側のレーンの値がリトル・エンディアン順に格納されるという便利なフィクションとは無関係です。

たとえば、FloatVector.fromArray(fsp,fa,i)は、float配列faからロードされた要素を使用して、特定の種のfspのfloatベクトルを作成して返します。 最初のレーンはfa[i]からロードされ、最後のレーンはfa[i+VL-1]からロードされます。VLは、種fspから導出されたベクトルの長さです。 次に、fv=fv.add(fv2)は、同じ種のfspのベクトルfv2を使用して、その種のfspの別のfloatベクトルを生成します。 次に、mnz=fv.compare(NE, 0.0f)は結果がゼロかどうかをテストし、マスクmnzを生成します。 ゼロ以外のレーンの(これらのレーンのみ)は、文fv.intoArray(fa,i,mnz)を使用して元の配列要素に格納できます。

拡張、縮小および結果の一部

ベクトルはサイズが固定されているため、多くの場合、演算の論理結果が提案された出力ベクトルの物理サイズと異なる場合があります。 移植性が高く、可能なかぎり予測可能なユーザー・コードを推薦するために、このAPIには、このような「サイズ変更」ベクトル演算の設計に対する体系的なアプローチがあります。

基本原則として、一般的には明確にマークされていないかぎり、レーンワイズの操作はlength-invariantです。 長さ不変とは、VLENGTHレーンが演算に入ると、同じ数のレーンが破棄されず、余分なパディングも行われないことを意味します。

別の原則として、特に明らかにマークされていないかぎり、最初のレーンワイズ演算の時制がシェイプ不変でもある場合があります。 シェイプ不変性とは、VSHAPEが一般的な計算に対して一定であることを意味します。 計算全体で同じシェイプを維持することで、十分なベクトル・リソースが効率的に使用されるようになります。 (一部のハードウェア・プラットフォームでは、シェイプを変更すると、余分なデータ移動指示、メモリー内のラウンドトリップ、パイプライン・バブルなどの不要な影響が発生する可能性があります。)

これらの原則間の張力は、演算によって生成される「論理結果」が、必要な出力VSHAPEに対して大きすぎる場合に発生します。 また、論理結果が出力VSHAPEの容量より小さい場合、物理出力ベクトルには論理結果とパディングが混在している必要があるため、論理結果の位置は問題になります。

最初のケースでは、大きすぎる論理結果が小さすぎる出力VSHAPEに詰め込まれる場合、データは「拡張した」と言います。 つまり、「拡張演算」が原因で出力シェイプがオーバーフローしています。 大きな出力VSHAPEに適合する小さな論理結果の2番目のケースでは、データは縮小があり、「縮小演算」は出力シェイプに余分なゼロ・レーンを埋め込む必要があります。

どちらの場合も、論理結果サイズ(ビット単位)と実際の出力シェイプのビット・サイズの間で「拡張率」または「縮小率」を測定するパラメータMを使用できます。 ベクトル・シェイプが変更され、レーンのサイズが変更されていない場合、Mは論理結果に対する出力シェイプの積分的な比率にすぎません。 (「最大シェイプ」を除き、ベクトル・サイズはすべて2の累乗であるため、比率Mは常に整数です。 整数以外の比率の場合、値Mは次の整数に切り上げられ、同じ一般的な考慮事項が適用されます。)

論理結果が物理出力シェイプより大きい場合、このようなシェイプの変更は結果レーン(論理結果の1/M以外のすべて)を必然的に削除する必要があります。 論理サイズが出力より小さい場合、シェイプを変更すると、パディング(物理出力の1/M以外のすべて)のゼロ埋めレーンが導入されます。 レーンがドロップされた最初のケースは拡張で、2番目のケースにパディング・レーンが追加されたケースは縮小です。

同様に、シェイプを不変のままにし、レーンのサイズをMの比率で変更するレーンワイズの変換演算について考えてみます。 論理結果が出力(または入力)より大きい場合、この変換では出力のVLENGTHレーンをMだけ減らし、論理結果レーンの1/Mを除くすべてを削除する必要があります。 以前と同様に、レーンのドロップは拡張部分のハルマルクです。 レーンのサイズをMの比率で縮小するレーンワイズ演算では、余白を加算する必要があるため、余分なレーンにゼロの埋込み値を入力して、VLENGTHを同じファクタMで増やす必要があります。

レーン変換およびシェイプ変更の両方を実行する一方の演算で、レーン・サイズとコンテナ・サイズの両方を変更することもできます(ややわかりにくい)。 これを行うと、同じルールが適用されますが、論理結果サイズは、入力サイズにレーン変更サイズからの拡張または縮小率を掛けた積になります。

完全性のために、サイズ変更が行われない場合の頻度が高いケースについて「インプレース演算」を話すこともできます。 インプレース演算では、データは切り捨てやパディングなしで論理出力から物理コンテナに単純にコピーされます。 この場合、比率パラメータMはunityです。

縮小と拡張の分類は、論理結果の相対サイズおよび物理出力コンテナによって異なります。 入力コンテナのサイズは、分類を変更せずに、他の2つの値のいずれかより大きくすることも小さくすることもできます。 たとえば、128ビットのシェイプから256ビットのシェイプへの変換は多くの場合縮小になりますが、byteからlongへの変換と組み合せると、論理結果のサイズが1024ビットになるため、拡張になります。 また、この例は、論理結果が特定のプラットフォームでサポートされているベクトル・シェイプに対応している必要がないことも示しています。

レーンワイズ・マスキングされた演算は、部分演算の生成とみなすことができますが、(このAPI内)は拡張または縮小として分類されません。 配列からのマスクされたロードでは、部分ベクトルが確実に生成されますが、この部分結果が縮小された意味のある"論理出力ベクトル"はありません。

これらの用語は、出力コンテナのサイズに関連して拡張または縮小する「コンテナ・サイズ」ではなく、dataであるため、注意が必要です。 したがって、128ビットの入力を512ビットのベクトルにサイズ変更すると、縮小に影響します。 128ビットのペイロードのサイズは変更されていませんが、新しい512ビット・ホームで"より小さく見える"と言うことができ、これにより状況の実際の詳細が取得されます。

ベクトル・メソッドがそのデータを拡張する可能性がある場合は、partと呼ばれる追加のintパラメータまたは"パーツ番号"を受け入れます。 部品番号は[0..M-1]の範囲内である必要があります。ここで、Mは拡張率です。 部品番号は、論理結果からいずれかのM連続する不連続レーンのサイズのブロックを選択し、物理出力ベクトルをこのレーンのブロックで埋めます。

具体的には、拡張の論理結果から選択されたレーンには、[R..R+L-1]の範囲内の番号が付けられます。ここで、Lは物理出力ベクトルのVLENGTHで、ブロックの起点Rpart*Lです。

同様の縮小が、そのデータを縮小する可能性のあるベクトル・メソッドにも適用されます。 このようなメソッドでは、追加の部品番号パラメータ(partを再度呼び出しました)も受け入れられます。このパラメータは、物理出力ベクトル内のレーンのM隣接するディスジョイントの同サイズのブロックのいずれかを縮小データ・レーンをステアします。 残りのレーンはゼロで埋めるか、メソッドで指定されたとおりに埋められます。

具体的には、[R..R+L-1]の範囲で番号付けされたレーンにデータが入力されます。ここで、Lは論理結果ベクトルのVLENGTHであり、ブロックの原点Rは、部品番号で選択されたLの倍数(具体的には|part|*L)です。

縮小の場合、部品番号は正でない範囲の[-M+1..0]内にある必要があります。 この規則が採用されているのは、一部のメソッドではデータ依存のメソッドで拡張と縮小の両方を実行でき、部品番号の余分な符号がエラー・チェックとして機能するためです。 ベクトル・メソッドが部品番号を取り、インプレース演算の(縮小と拡大のいずれでもない)を実行するために呼び出される場合、partパラメータは正確にゼロである必要があります。 許可された範囲外の部品番号は、索引付け例外を示します。 いずれの場合も、部品番号ゼロが有効であり、論理結果の先頭からできるだけ多くのレーンを保持し、物理出力コンテナの先頭に配置する演算に対応しています。 多くの場合、これは望ましいデフォルトであるため、部品番号ゼロはすべての場合において安全であり、ほとんどの場合に役立ちます。

このAPIの様々なサイズ変更演算は、次のようにデータを縮小または拡張します:

  • 出力の「要素サイズ」Mの係数より大きい(またはより小さい)場合、Vector.convert()はオペランドを比率Mで拡張(または縮小)します。 入力と出力の要素サイズが同じ場合、convert()はインプレース演算です。
  • Vector.convertShape()は、論理結果のビット・サイズが出力シェイプのビット・サイズより大きい(またはより小さい)場合、オペランドを比率Mで拡張(または縮小)します。 論理結果のサイズは、出力の「要素サイズ」にその入力のVLENGTHを掛けたものとして定義されます。 変更されたレーンのサイズの比率に応じて、論理サイズは、演算が拡張であるか縮小であるかに関係なく、入力ベクトルより大きいか小さい(様々な場合)になります。
  • Vector.castShape()convertShape()の便利なメソッドであるため、拡張または縮小としての分類はconvertShape()の場合と同じです。
  • Vector.reinterpretShape()は、入力の「ベクトル・ビット・サイズ」Mの係数でより小さい(それぞれ、より大きいサイズにドロップされます)出力コンテナにクラムされている場合の、比率Mによる拡張(それぞれ縮小)です。 それ以外の場合は、インプレース演算です。 このメソッドは、レーンの境界を消去および再描画したり、シェイプを修正できる再解釈キャストであるため、入力ベクトル・レーン・サイズおよびレーン・カウントは、拡張または縮小としての分類とは無関係です。
  • 単一の入力スライスが連続する複数のバックグラウンド・ベクトル内のどこかに配置および挿入されるため、unslice()メソッドはM=2の比率で拡張されます。 パーツ番号は、挿入されたスライスによって更新された最初または2番目のバックグラウンド・ベクトルを選択します。 対応するslice()メソッドは、unslice()メソッドとは逆ですが、そのデータを縮小しないため、部品番号を必要としません。 これは、slice()では、2つの入力ベクトルから抽出された正確なVLENGTHレーンのスライスが提供されるためです。
拡張または縮小演算が実行される前に、VectorSpecies上のメソッドpartLimit()を使用して、提案された拡張または縮小のために部品パラメータの制限値を問い合せることができます。 partLimit()から返される値は、拡張の場合は正の値、縮小の場合は負の値、インプレース演算の場合はゼロです。 その絶対値はパラメータ Mであるため、関連するメソッドの有効な部品番号引数に対する排他的な制限として機能します。 したがって、拡張の場合、partLimit()Mは部品番号の排他的な上限ですが、縮小の場合、partLimit()-Mは排他的なlower制限です。

レーン境界を越えたデータの移動

レーンの再描画や種の変更を行わないクロス・レーン方式は、より定期的に構造化され、より簡単に理由を理解できます。 操作は次のとおりです。
  • 連結されたベクトルのペア内の指定された原点からVLENGTHフィールドの連続したスライスを抽出するslice()ファミリのメソッド。
  • 指定された原点で連結されたベクトルのペアにVLENGTHフィールドの連続したスライスを挿入する、unslice()ファミリのメソッド。
  • メソッドのrearrange()ファミリ。いずれかまたは複数の入力ベクトルから任意のVLENGTHレーンのセットを選択し、任意の順序でアセンブルします。 レーンの選択および順序は、搬送元レーンを搬送先レーンにマッピングするルーティング表として機能するVectorShuffleオブジェクトによって制御されます。 VectorShuffleでは、数学的順列および他の多くのデータ移動パターンをエンコードできます。
  • 入力ベクトルから最大VLENGTHレーンを選択し、レーン順にアセンブルするcompress(VectorMask)およびexpand(VectorMask)メソッド。 レーンの選択はVectorMaskで制御され、レーン要素のマッピングは、レーンの順序で圧縮または拡張、発信元レーンと着信先レーンで制御されます。

ベクトル演算の中には、レーンワイズではなく、レーン境界を越えてデータを移動するものがあります。 SIMDコードでは通常、このような演算はまれですが、低レベルでデータ形式を演算する特定のアルゴリズムや、複雑なローカル・パターンでの移動にSIMDデータが必要になる場合があります。 (大規模なデータ配列の小さいウィンドウでのローカル移動は比較的異常ですが、一部の高度にパターン化されたアルゴリズムによってコールされます。) このAPIでは、このようなメソッドは常に明確に認識できるため、より単純なレーンワイズ推論を残りのコードに確実に適用できます。

場合によっては、ベクトル・レーンの境界が破棄され、"最初から再描画"によって、特定の入力レーンのデータが複数の出力レーンを介して分散された(複数の部分)に表示されたり、複数の入力レーンの(逆)データが単一の出力レーンに統合されたりすることがあります。 レーン境界を再描画できる基本的なメソッドは、reinterpretShape()です。 このメソッドの上に構築され、reinterpretAsBytes()reinterpretAsInts()などの特定の便利なメソッドでは、同じベクトル全体のシェイプを維持しながら、レーン境界が(潜在的)で再描画されます。

スカラー結果を生成または消費する演算は、非常に単純なクロス・レーン演算とみなすことができます。 reduceLanes()ファミリのメソッドは、メソッドのすべてのレーン(またはマスク選択されたレーン)をまとめて折りたたみ、単一の結果を返します。 逆に、broadcastメソッド・ファミリは、スカラーから出力ベクトルのすべてのレーンまで、反対方向の横断レーンと考えることができます。 lane(I)withLane(I,E)などの単一レーンのアクセス・メソッドも、非常に単純なクロス・レーン演算とみなされる場合があります。

同様に、バイト配列との間で非バイト・ベクトルを移動するメソッドは、ベクトル・レーンを別々のバイトに分散するか、配列バイトから統合された(反対方向)に分散する必要があるため、クロス・レーン演算とみなすことができます。

実装上のノート:

ハードウェア・プラットフォームの依存性と制限

Vector APIは、ベクトル・ハードウェア・レジスタやベクトル・ハードウェア命令などの使用可能なハードウェア・リソースを使用して、単一命令複数データ(SIMD)のスタイルでの計算を高速化します。 このAPIは、複数のSIMDハードウェア・プラットフォームを効果的に使用するように設計されています。

このAPIは、SIMD計算用の特殊なハードウェア・サポートが含まれていないJavaプラットフォームでも正しく機能します。 Vector APIでは、このようなプラットフォームで特別なパフォーマンス上の利点が得られない可能性があります。

現在、実装は次のものに最適化されています:

  • AVX-512までのAVX2をサポートするIntel x64プラットフォーム。 AVX-512でのマスク・レジスタを使用したマスキングおよびハードウェア命令の受け入れは、現在サポートされていません。
  • NEONをサポートするARM AArch64プラットフォーム。 APIはARM SVE命令が(128から2048ビットのベクトル・サイズ)をサポートできるように設計されていますが、現時点ではこのような命令の実装や一般的なマスキング機能はありません。
現在、実装では、マスキングされていないレーンワイズ演算を式a.blend(a.lanewise(op, b), m)のようにblendで構成することで、クロス・プラットフォーム方式でのマスキングされたレーンワイズ演算がサポートされています。ここで、aおよびbはベクトル、opはベクトル演算子、mはマスクです。

現在の実装では、浮動小数点変換関数(SINLOGなどの演算子)に最適なベクトル化命令はサポートされていません。

プリミティブのボクシングなし

Vector<Integer>などのベクトル・タイプは、ボックス化されたInteger値で動作するように見えますが、ボックス化に関連付けられたオーバーヘッドは、intなどの実際のETYPEのレーン値で各Vectorサブタイプが内部的に機能することによって回避されます。

値ベースのクラスとアイデンティティ演算

Vectorは、そのサブタイプおよびVectorMaskVectorShuffleなどのヘルパー・タイプの多くとともに、value-basedクラスです。

作成したベクトルは、「1つのレーンが変更された場合」のみであっても変更されません。 レーン値の新しい構成を保持するために、常に新しいベクトルが作成されます。 ミューチュアル・メソッドを使用できないことは、値ベースのクラスとしてすべてのベクトルのオブジェクト・アイデンティティを抑制した結果として必要です。

Vectorでは、次のようになります。 ==などのアイデンティティ依存の演算では、予期しない結果が発生したり、パフォーマンスが低下する可能性があります。 equalsはアイデンティティ依存のメソッドではないため、v.equals(w)v==wよりも高速である可能性が高くなります。 また、これらのオブジェクトは、ローカルおよびパラメータにもstatic final定数としても格納できますが、意味的には有効ですが、他のJavaフィールドまたは配列要素に格納すると、パフォーマンスが低下する可能性があります。