Juliaプログラミング:BLAS.dotのエラーと解決策【トラブルシューティング】
LinearAlgebra.BLAS.dot()
とは
LinearAlgebra.BLAS.dot()
は、Juliaの LinearAlgebra
モジュール内にある BLAS
サブモジュールの関数で、2つのベクトルの**ドット積(内積)**を計算するために使用されます。
-
ドット積(内積): 2つのベクトル x=[x_1,x_2,...,x_n] と y=[y_1,y_2,...,y_n] のドット積は、対応する要素の積の合計として定義されます。 x⋅y=i=1∑n​xi​yi​=x1​y1​+x2​y2​+⋯+xn​yn​ 複素数のベクトルの場合、最初のベクトルの要素は共役(conjugate)されます。 x⋅y=i=1∑n​xi​ˉ​yi​ ここで barx_i は x_i の複素共役です。
なぜ BLAS.dot()
を使うのか?
Juliaには、より一般的な dot()
関数(x ⋅ y
とも書ける)が提供されていますが、LinearAlgebra.BLAS.dot()
を直接呼び出すことで、BLASの最適化されたルーチンを明示的に利用できます。
- 低レベルな制御:
BLAS.dot()
は、ベクトルの要素数、各ベクトル内の要素間の間隔(stride)などを直接指定できるため、より低レベルな制御が可能です。一般的なdot()
関数は、これらの詳細を内部で自動的に処理します。 - パフォーマンス: BLASルーチンは、CPUのキャッシュ効率、SIMD(単一命令複数データ)命令、マルチスレッド処理などを最大限に活用するように高度に最適化されています。これにより、特に大規模なベクトルや行列の計算において、Juliaで書かれた純粋なループよりもはるかに高速な実行が期待できます。
基本的な dot()
関数と比較した例を以下に示します。
using LinearAlgebra
using BenchmarkTools # パフォーマンス計測用
# ベクトルの定義
n = 10_000_000
x = rand(n)
y = rand(n)
println("通常の dot() 関数:")
@btime dot($x, $y)
println("\nLinearAlgebra.BLAS.dot() 関数:")
# BLAS.dot(n, X, incx, Y, incy) の形式
# n: 要素数
# X, Y: ベクトル(配列)
# incx, incy: 各ベクトルの要素間の間隔(通常は1)
@btime LinearAlgebra.BLAS.dot($n, $x, 1, $y, 1)
出力例(環境によって異なります)
通常の dot() 関数:
5.485 ms (0 allocations: 0 bytes)
LinearAlgebra.BLAS.dot() 関数:
5.234 ms (0 allocations: 0 bytes)
この例では、LinearAlgebra.BLAS.dot()
がわずかに速いか、同等のパフォーマンスを示すことがわかります。Juliaの通常の dot()
関数も内部的にBLASを呼び出すように最適化されているため、多くの場合は明示的に BLAS.dot()
を呼び出す必要はありません。しかし、特定の状況や、低レベルな最適化を試みる場合には有用です。
- 一般的に、Juliaの通常の
dot()
関数を使用しても十分なパフォーマンスが得られますが、LinearAlgebra.BLAS.dot()
を明示的に使用することで、より低レベルな制御や、場合によってはさらなる最適化の機会が得られます。 - BLASは、線形代数演算を高速に実行するための標準的な低レベルライブラリであり、JuliaはデフォルトでOpenBLASを利用します。
LinearAlgebra.BLAS.dot()
は、Juliaで2つのベクトルのドット積を計算するための、BLAS(Basic Linear Algebra Subprograms)に基づいた高性能な関数です。
よくあるエラーとトラブルシューティング
DimensionMismatch エラー (引数の次元不一致)
エラーメッセージ例
ERROR: DimensionMismatch("dot product arguments have lengths 3 and 2")
原因
BLAS.dot()
は、2つのベクトルのドット積を計算するため、両方のベクトルが同じ長さである必要があります。長さが異なるベクトルを渡した場合にこのエラーが発生します。
トラブルシューティング
入力ベクトルの長さが同じであることを確認してください。
using LinearAlgebra.BLAS
x = [1.0, 2.0, 3.0]
y = [4.0, 5.0] # 長さが異なる
# 誤った例
# BLAS.dot(length(x), x, 1, y, 1) # -> DimensionMismatch エラー
# 正しい例(同じ長さのベクトルを用意する)
y_correct = [4.0, 5.0, 6.0]
result = BLAS.dot(length(x), x, 1, y_correct, 1)
println(result) # 32.0
MethodError (引数の型が正しくない)
エラーメッセージ例
ERROR: MethodError: no method matching dot(::Int64, ::Array{Int64,1}, ::Int64, ::Array{Int64,1}, ::Int64)
トラブルシューティング
入力ベクトルの要素の型が、Float32
、Float64
、ComplexF32
、ComplexF64
のいずれかであることを確認してください。必要に応じて型変換を行ってください。
using LinearAlgebra.BLAS
x_int = [1, 2, 3]
y_int = [4, 5, 6]
# 誤った例
# BLAS.dot(length(x_int), x_int, 1, y_int, 1) # -> MethodError
# 正しい例(Float64に変換)
x_float = convert(Array{Float64}, x_int)
y_float = convert(Array{Float64}, y_int)
result = BLAS.dot(length(x_float), x_float, 1, y_float, 1)
println(result) # 32.0
# もしくは、最初から浮動小数点数で定義する
x_new = [1.0, 2.0, 3.0]
y_new = [4.0, 5.0, 6.0]
result_new = BLAS.dot(length(x_new), x_new, 1, y_new, 1)
println(result_new) # 32.0
n (要素数) の指定ミス
原因
BLAS.dot()
の最初の引数 n
は、計算対象となるベクトルの有効な要素数を指定します。もしこの n
が実際のベクトル長を超えていたり、小さすぎたりすると、予期しない結果やエラー(場合によってはセグメンテーションフォルトなど)を引き起こす可能性があります。
トラブルシューティング
n
は必ずベクトルの実際の長さ、または計算したいサブベクトルの長さに一致させるようにしてください。通常は length(x)
を使用するのが安全です。
using LinearAlgebra.BLAS
x = [1.0, 2.0, 3.0, 4.0]
y = [5.0, 6.0, 7.0, 8.0]
# 誤った例 (nが長すぎる)
# BLAS.dot(5, x, 1, y, 1) # -> 予期せぬ動作やエラー(BoundsErrorなど)
# 誤った例 (nが短すぎる)
result_short = BLAS.dot(2, x, 1, y, 1)
println(result_short) # 1.0*5.0 + 2.0*6.0 = 17.0 (部分的な計算になる)
# 正しい例
result_full = BLAS.dot(length(x), x, 1, y, 1)
println(result_full) # 1.0*5.0 + 2.0*6.0 + 3.0*7.0 + 4.0*8.0 = 5.0 + 12.0 + 21.0 + 32.0 = 70.0
incx, incy (ストライド) の理解不足
原因
incx
と incy
は、それぞれのベクトルにおいて、次の要素へアクセスする際の「間隔(ストライド)」を指定します。通常は1を指定しますが、ベクトルの一部をスキップしたり、行列の列(または行)をベクトルとみなして操作する際に1以外の値を指定することがあります。誤ったストライド値を指定すると、計算対象がずれてしまったり、メモリ領域外にアクセスしようとしてエラーになる可能性があります。
トラブルシューティング
基本的なドット積では incx=1
, incy=1
を使用してください。特定の配列のサブビューや、より複雑なメモリアクセスパターンを扱う場合は、ストライドの概念をしっかり理解して正しく設定する必要があります。
using LinearAlgebra.BLAS
# 例: 偶数番目の要素のみでドット積を計算する場合
x = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]
y = [7.0, 8.0, 9.0, 10.0, 11.0, 12.0]
# xの2番目の要素から、2つおきに要素を取得 (2.0, 4.0, 6.0)
# yの2番目の要素から、2つおきに要素を取得 (8.0, 10.0, 12.0)
# 有効な要素数 n = 3
# xの開始位置はインデックス1 (Juliaは1ベースインデックス)
# yの開始位置はインデックス1 (Juliaは1ベースインデックス)
# JuliaのBLAS.dotはポインタとオフセットで指定するため、少し複雑
# 通常、JuliaではSubArray (view) を使った方が安全で分かりやすい
# BLAS.dot(n, X, incx, Y, incy) は生の配列とストライドを取る
# SubArrayを明示的に作成し、BLASの自動ディスパッチに任せる方が安全
x_sub = view(x, 2:2:length(x)) # [2.0, 4.0, 6.0]
y_sub = view(y, 2:2:length(y)) # [8.0, 10.0, 12.0]
# この場合、一般的なdot()関数が内部でBLASを最適に利用してくれる
result_sub = dot(x_sub, y_sub)
println(result_sub) # 2.0*8.0 + 4.0*10.0 + 6.0*12.0 = 16.0 + 40.0 + 72.0 = 128.0
# BLAS.dotを直接使う場合 (より低レベル)
# JuliaのBLAS.dotはポインタではなく配列とストライドを取るので、
# この場合はincx=1, incy=1 で配列全体を渡し、nで制御するのが一般的。
# 特定の開始インデックスから計算したい場合は、`view`を使い、
# そのviewをdot()に渡すのがJuliaらしいアプローチ。
LinearAlgebra.BLAS の衝突
原因
ごく稀に、using LinearAlgebra
と using LinearAlgebra.BLAS
の両方を使っている場合、dot
という名前が衝突することがあります。しかし、現在のJuliaの設計では、LinearAlgebra.dot
が優先され、BLAS.dot
は通常 LinearAlgebra.BLAS.dot
のように完全修飾された形で呼び出すため、この問題はほとんど発生しません。
トラブルシューティング
もし dot
を明示的に呼び出したい場合は、必ず LinearAlgebra.BLAS.dot()
のようにモジュール名を付けて呼び出すことで、意図しない関数の呼び出しを防ぐことができます。
- dot() (一般的な関数) との比較
ほとんどの場合、LinearAlgebra.dot(x, y)
(またはx ⋅ y
)を使用することが推奨されます。これは、内部的に最適なBLASルーチンを呼び出すように最適化されており、ユーザーがn
,incx
,incy
といった低レベルな引数を気にする必要がないためです。パフォーマンスが問題になる場合を除き、まず一般的なdot()
を試してください。 - Juliaのバージョン
使用しているJuliaのバージョンによって、関数の引数や動作が微妙に異なる場合があります。公式ドキュメントやREPLの?BLAS.dot
で現在のバージョンのヘルプを確認してください。 - 簡単な例で試す
複雑なコードで問題が発生した場合、まず短いベクトルや簡単な型でBLAS.dot()
が正しく動作するかどうかを試してみてください。 - 次元を確認
size(x)
やlength(x)
を使って、入力ベクトルの次元と長さを確認してください。 - 型を確認
typeof(x)
やeltype(x)
を使って、入力ベクトルの型と要素の型を確認してください。 - using LinearAlgebra を確認
LinearAlgebra.BLAS.dot()
を使用する前に、using LinearAlgebra
もしくはusing LinearAlgebra.BLAS
がスクリプトの冒頭にあることを確認してください。
基本的な使用法 (Float64 の場合)
最も一般的な使用例です。Float64
は Julia のデフォルトの浮動小数点型であり、BLAS が最も得意とする型の一つです。
using LinearAlgebra # LinearAlgebra モジュールをインポート
# BLAS.dot は LinearAlgebra モジュール内にあります
# 1. 2つのベクトルの定義
# 要素の型はFloat64 (1.0のように小数点をつけることで明示)
x = [1.0, 2.0, 3.0]
y = [4.0, 5.0, 6.0]
# 2. BLAS.dot() を使ってドット積を計算
# 引数: (要素数 n, ベクトル X, Xのストライド incx, ベクトル Y, Yのストライド incy)
# 通常、incx と incy は 1 を指定します。
n = length(x) # ベクトルの長さ (3)
result = LinearAlgebra.BLAS.dot(n, x, 1, y, 1)
# 3. 結果の表示
println("ベクトル x: ", x)
println("ベクトル y: ", y)
println("x と y のドット積 (BLAS.dot): ", result)
# 検算: 1*4 + 2*5 + 3*6 = 4 + 10 + 18 = 32.0
出力
ベクトル x: [1.0, 2.0, 3.0]
ベクトル y: [4.0, 5.0, 6.0]
x と y のドット積 (BLAS.dot): 32.0
Float32 (単精度浮動小数点数) の場合
Float32
は Float64
よりもメモリ使用量が少なく、一部の計算で高速になる場合があります。
using LinearAlgebra
# Float32 型のベクトルの定義 (f0 のように F をつけることで明示)
x_f32 = [1.0f0, 2.0f0, 3.0f0]
y_f32 = [4.0f0, 5.0f0, 6.0f0]
n_f32 = length(x_f32)
result_f32 = LinearAlgebra.BLAS.dot(n_f32, x_f32, 1, y_f32, 1)
println("\nベクトル x (Float32): ", x_f32)
println("ベクトル y (Float32): ", y_f32)
println("x_f32 と y_f32 のドット積 (BLAS.dot): ", result_f32)
出力
ベクトル x (Float32): Float32[1.0, 2.0, 3.0]
ベクトル y (Float32): Float32[4.0, 5.0, 6.0]
x_f32 と y_f32 のドット積 (BLAS.dot): 32.0f0
ComplexF64 (複素数) の場合
複素数のドット積の場合、BLAS は最初のベクトルの要素の共役(conjugate)を取ってから積を計算します。
using LinearAlgebra
# 複素数ベクトルの定義
x_complex = [1.0 + 2.0im, 3.0 + 4.0im]
y_complex = [5.0 + 6.0im, 7.0 + 8.0im]
n_complex = length(x_complex)
result_complex = LinearAlgebra.BLAS.dot(n_complex, x_complex, 1, y_complex, 1)
println("\nベクトル x (ComplexF64): ", x_complex)
println("ベクトル y (ComplexF64): ", y_complex)
println("x_complex と y_complex のドット積 (BLAS.dot): ", result_complex)
# 検算:
# (1 - 2im) * (5 + 6im) = 5 + 6im - 10im + 12 = 17 - 4im
# (3 - 4im) * (7 + 8im) = 21 + 24im - 28im + 32 = 53 - 4im
# 合計: (17 - 4im) + (53 - 4im) = 70 - 8im
出力
ベクトル x (ComplexF64): ComplexF64[1.0 + 2.0im, 3.0 + 4.0im]
ベクトル y (ComplexF64): ComplexF64[5.0 + 6.0im, 7.0 + 8.0im]
x_complex と y_complex のドット積 (BLAS.dot): 70.0 - 8.0im
incx
と incy
は、ベクトル内の要素間の間隔(ストライド)を指定します。通常は1ですが、例えば行列の列をベクトルとみなす場合や、特定の要素をスキップしたい場合に使用します。
この例では、偶数インデックスの要素のみを抽出し、それらのドット積を計算します。
using LinearAlgebra
data_x = [1.0, 2.0, 3.0, 4.0, 5.0, 6.0]
data_y = [7.0, 8.0, 9.0, 10.0, 11.0, 12.0]
# data_x の偶数番目の要素 (2.0, 4.0, 6.0)
# data_y の偶数番目の要素 (8.0, 10.0, 12.0)
# これらの3つの要素のドット積を計算したい
# 注意: BLAS.dot は配列の先頭からの相対オフセットとストライドで計算します。
# JuliaのBLAS.dotは通常、生のポインタを渡す代わりに配列を渡す形式を推奨します。
# 複雑なストライドやオフセットを伴う計算は、Juliaのview()関数や一般的なdot()関数を使う方が安全で自然です。
# 以下は、低レベルなBLASの概念を理解するための例として示します。
# xの2番目の要素から開始し、2つおきに3つの要素
# yの2番目の要素から開始し、2つおきに3つの要素
# BLAS.dot(n, X, incx, Y, incy)
# n: 計算する要素のペアの数 = 3
# X: ベクトル X (配列)
# incx: X内の要素間隔 = 2 (2.0 -> 4.0 -> 6.0)
# Y: ベクトル Y (配列)
# incy: Y内の要素間隔 = 2 (8.0 -> 10.0 -> 12.0)
# 注意: JuliaのBLAS.dotは配列を渡す形式であり、
# 内部的に適切なポインタとオフセットを計算します。
# この例では、配列全体を渡し、ストライドで制御します。
# しかし、開始オフセットを直接指定する引数はありません。
# そのため、特定のサブ配列から計算したい場合は、`view`を使う方がJulia的です。
# 例: view() を使って特定のサブ配列のドット積を計算 (推奨される方法)
x_even = view(data_x, 2:2:length(data_x)) # [2.0, 4.0, 6.0]
y_even = view(data_y, 2:2:length(data_y)) # [8.0, 10.0, 12.0]
# view を dot() 関数に渡すと、内部で最適なBLASルーチンが呼び出されます。
result_even_view = LinearAlgebra.dot(x_even, y_even)
println("\nview() を使った偶数要素のドット積: ", result_even_view)
# 検算: 2*8 + 4*10 + 6*12 = 16 + 40 + 72 = 128.0
view() を使った偶数要素のドット積: 128.0
- 例えば、
x' * y
やdot(x, y)
は、内部でBLAS.dot
を含む最適化されたルーチンを使用しています。 LinearAlgebra.BLAS.dot()
を直接呼び出すことは、低レベルな最適化を試みる際に役立ちますが、通常はLinearAlgebra.dot()
関数(または中置演算子の⋅
)を使用することが推奨されます。Julia の高レベルなdot()
関数は、与えられた引数(Vector
,view
,Adjoint
など)に基づいて、最適な BLAS ルーチンを自動的に選択して呼び出します。これにより、パフォーマンスを損なうことなく、より簡潔で安全なコードを書くことができます。
LinearAlgebra.dot() (一般的なドット積関数)
最も推奨される、そして最も一般的に使用される代替方法です。LinearAlgebra.dot()
は、ユーザーが明示的に BLAS の低レベルな引数(n
, incx
, incy
)を指定することなく、高レベルなインターフェースを提供します。内部的には、Julia はこの関数が呼び出されたときに、最適な BLAS ルーチンを自動的にディスパッチ(選択して呼び出す)します。
using LinearAlgebra # 通常、LinearAlgebra.dot() はこの using で利用可能
x = [1.0, 2.0, 3.0]
y = [4.0, 5.0, 6.0]
# LinearAlgebra.dot() を使用
result_dot = LinearAlgebra.dot(x, y)
println("LinearAlgebra.dot(x, y): ", result_dot)
# 複素数の場合も同様
x_complex = [1.0 + 2.0im, 3.0 + 4.0im]
y_complex = [5.0 + 6.0im, 7.0 + 8.0im]
result_complex_dot = LinearAlgebra.dot(x_complex, y_complex)
println("LinearAlgebra.dot(x_complex, y_complex): ", result_complex_dot)
利点
- 汎用性
Vector
だけでなく、view
やAdjoint
(転置行列)などの様々な抽象的な配列型に対しても機能する。 - 自動最適化
Julia が最適な BLAS ルーチンを自動的に選択するため、ほとんどの場合BLAS.dot()
を直接呼び出すのと同等のパフォーマンスが得られる。 - 安全性
n
,incx
,incy
のような低レベルな引数を誤って指定するリスクがない。 - 簡潔さ
コードが短く、読みやすい。
いつ使うべきか
ほとんどのドット積計算において、この方法が推奨されます。パフォーマンスが懸念される場合でも、まずは LinearAlgebra.dot()
を試すべきです。
中置演算子 ⋅ (dot product operator)
LinearAlgebra.dot()
と機能的には同じですが、より数学的な表記に近い形で記述できるのが特徴です。Unicode のドット積記号(⋅
)を使用します。REPL で \cdot
と入力し、Tab キーを押すと ⋅
に変換されます。
using LinearAlgebra
x = [1.0, 2.0, 3.0]
y = [4.0, 5.0, 6.0]
# ドット積演算子を使用 (x \cdot y と入力して Tab)
result_operator = x ⋅ y
println("x ⋅ y: ", result_operator)
利点
- 簡潔さ
コードが非常に短くなる。 - 可読性
数学的な表記に慣れている人にとって、非常に直感的で読みやすい。
いつ使うべきか
LinearAlgebra.dot()
と同じシナリオで、特にコードの可読性を高めたい場合に適しています。
adjoint(x) * y または x' * y (行列乗算としてのドット積)
ベクトル x の共役転置(adjoint)とベクトル y の行列積としてドット積を表現することもできます。x'
は Julia で adjoint(x)
の糖衣構文(シンタックスシュガー)です。この操作も内部的には BLAS ルーチン(特に gemv
や dot
)を利用するように最適化されます。
using LinearAlgebra
x = [1.0, 2.0, 3.0]
y = [4.0, 5.0, 6.0]
# x の共役転置と y の積としてドット積を表現
result_adjoint_mul = x' * y
println("x' * y: ", result_adjoint_mul)
# 複素数の場合
x_complex = [1.0 + 2.0im, 3.0 + 4.0im]
y_complex = [5.0 + 6.0im, 7.0 + 8.0im]
result_complex_adjoint_mul = x_complex' * y_complex
println("x_complex' * y_complex: ", result_complex_adjoint_mul) # 結果は ComplexF64 の場合と同じ
利点
- 簡潔さ
コードが短く、特に線形代数の文脈で自然に感じる。 - 汎用性
行列とベクトルの積の一般的な表記法の一部として理解しやすい。
いつ使うべきか
ドット積を行列演算の一部として自然に表現したい場合や、線形代数の表記に慣れている場合に適しています。ただし、dot()
関数の方が意図が明確で、純粋なドット積計算には優先されることが多いです。
パフォーマンスが最も重要な数値計算では、Julia の組み込み関数が C や Fortran に匹敵する速度を出すように設計されています。しかし、教育目的でドット積の定義を理解するためや、非常に特殊なメモリレイアウトや計算パターンでマイクロ最適化を試みる場合(ほとんどないですが)、手動でループを記述することも可能です。
x = [1.0, 2.0, 3.0]
y = [4.0, 5.0, 6.0]
# 手動ループでのドット積計算
function manual_dot(v1::Vector{T}, v2::Vector{T}) where T<:Real
if length(v1) != length(v2)
throw(DimensionMismatch("Vectors must have the same length"))
end
sum_val = zero(T) # 初期値をゼロにする (型Tに対応)
@inbounds for i in eachindex(v1) # @inbounds で範囲チェックを省略し、高速化
sum_val += v1[i] * v2[i]
end
return sum_val
end
result_manual = manual_dot(x, y)
println("manual_dot(x, y): ", result_manual)
# 複素数の場合 (共役が必要)
function manual_dot_complex(v1::Vector{T}, v2::Vector{T}) where T<:Complex
if length(v1) != length(v2)
throw(DimensionMismatch("Vectors must have the same length"))
end
sum_val = zero(T)
@inbounds for i in eachindex(v1)
sum_val += conj(v1[i]) * v2[i] # 最初の要素を共役にする
end
return sum_val
end
x_complex = [1.0 + 2.0im, 3.0 + 4.0im]
y_complex = [5.0 + 6.0im, 7.0 + 8.0im]
result_manual_complex = manual_dot_complex(x_complex, y_complex)
println("manual_dot_complex(x_complex, y_complex): ", result_manual_complex)
利点
- 究極の制御
非常に特殊な状況で、特定の最適化や動作が必要な場合にカスタマイズできる。 - 透明性
計算プロセスがコードで直接確認できるため、教育的。
欠点
- エラーの可能性
手動で境界チェックや型チェックを行う必要がある。 - パフォーマンス
ほとんどの場合、最適化された BLAS ルーチン(dot()
やBLAS.dot()
が使用するもの)よりも遅くなる可能性が高い。特に大規模なベクトルでは顕著。
いつ使うべきか
組み込み関数や BLAS ルーチンでは対応できない非常にニッチなケースか、アルゴリズムの動作を完全に理解するために自作する場合に限られます。
方法 | 説明 | 主な利点 | 主な使用シナリオ |
---|---|---|---|
LinearAlgebra.dot() | 最も推奨される高レベル関数。内部で BLAS を自動選択。 | 簡潔さ、安全性、自動最適化、汎用性 | ほとんどのドット積計算 |
x ⋅ y (\cdot Tab) | LinearAlgebra.dot() の中置演算子形式。数学的表記。 | 可読性、簡潔さ | LinearAlgebra.dot() と同じ。特に可読性重視の場合 |
x' * y | ベクトルの共役転置と行列積。BLAS を利用。 | 汎用性、線形代数表記との一貫性 | 行列演算の一部としてドット積を表現したい場合 |
手動ループ (for ループ) | ドット積の定義を直接コードで実装。 | 透明性、究極の制御 | 教育目的、非常に特殊な最適化が必要な場合 |
LinearAlgebra.BLAS.dot() (元の質問) | BLAS の低レベルインターフェースを直接呼び出す。引数が多い。 | 特定の低レベル制御、パフォーマンス検証 | 稀なケース。特定の BLAS API に直接アクセスしたい場合 |