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?

vDSP基礎解説 - 信号処理の隠れた英雄

Last updated at Posted at 2025-11-27

シリーズ: Apple Silicon AI技術スタック 完全解説
難易度: ★★☆☆☆(初級〜中級)
想定読者: オーディオ/信号処理をする人、リアルタイム処理が必要な人

TL;DR

  • vDSPはAccelerateフレームワーク内のデジタル信号処理ライブラリ
  • FFT、畳み込み、ベクトル演算などが超高速
  • 音声処理、オーディオ分析、科学計算で威力を発揮
  • GPU転送オーバーヘッドがないため、リアルタイム処理に最適

GPUだけじゃない:CPUの最適化も重要

ここまでGPU関連の技術を見てきた。でも、全てをGPUで処理すればいいというわけではない。

  • データ転送のオーバーヘッド
  • レイテンシ要件
  • 省電力性
  • 小規模データ

こういった場面では、CPUで処理した方が効率的なことも多い。そんなとき頼りになるのがvDSPだ。


vDSPとは?

vDSPはAccelerateフレームワークの一部で、デジタル信号処理に特化したライブラリ。

Apple公式のプログラミングガイドによると:

"The vDSP API provides mathematical functions for applications such as speech, sound, audio, and video processing, diagnostic medical imaging, radar signal processing, seismic analysis, and scientific data processing."

(vDSP APIは、音声、サウンド、オーディオ、ビデオ処理、診断医療画像、レーダー信号処理、地震分析、科学データ処理などのアプリケーション向けの数学関数を提供します)

出典:Apple Developer - vDSP Programming Guide

名前の由来は "vector Digital Signal Processing"。ベクトル演算を使った高速デジタル信号処理、ということ。


Accelerateフレームワーク全体像

vDSPを理解するには、まずAccelerateフレームワークの全体像を把握しておこう:

"The Accelerate framework provides high-performance, energy-efficient computation on the CPU by leveraging its vector-processing capability."

(Accelerateフレームワークは、CPUのベクトル処理能力を活用して、高性能でエネルギー効率の高い計算を提供します)

出典:Apple Developer - Accelerate Overview

Accelerateのコンポーネント

Accelerate Framework
├── vDSP      - デジタル信号処理(FFT、畳み込み等)
├── vImage    - 画像処理
├── BLAS      - 基本線形代数(ベクトル・行列演算)
├── LAPACK    - 高度な線形代数(固有値、連立方程式等)
├── BNNS      - CPU上のニューラルネットワーク
├── vForce    - ベクトル化された数学関数
└── Sparse    - スパース行列演算

これらすべてが、Apple Siliconのベクトル演算ユニット(ARM NEON)を活用して高速化されている。


vDSPでできること

vDSPの機能は多岐にわたる:

"The vDSP functions operate on real and complex data types. The functions include data type conversions, fast Fourier transforms (FFTs), and vector-to-vector and vector-to-scalar operations."

(vDSP関数は実数と複素数データ型に対して動作します。関数にはデータ型変換、高速フーリエ変換(FFT)、ベクトル対ベクトルおよびベクトル対スカラー演算が含まれます)

出典:Apple Developer - About the vDSP API

主な機能カテゴリ

カテゴリ 機能 用途例
フーリエ変換 FFT、DFT、DCT スペクトル分析、圧縮
畳み込み 1D/2D畳み込み、相関 フィルタリング、特徴検出
ベクトル演算 加減乗除、内積、ノルム 数値計算全般
Biquadフィルタ IIRフィルタ オーディオイコライザ
データ変換 Float32⇔Float64、Int⇔Float データ前処理

実践:FFTでオーディオ周波数分析

オーディオ信号の周波数成分を分析するFFTは、vDSPの代表的なユースケースだ。

C言語スタイル(低レベル)

import Accelerate

let n = 1024  // サンプル数
let log2n = vDSP_Length(log2(Float(n)))

// FFTセットアップを作成(事前計算、再利用可能)
guard let fftSetup = vDSP_create_fftsetup(log2n, FFT_RADIX2) else {
    fatalError("FFT setup failed")
}
defer { vDSP_destroy_fftsetup(fftSetup) }

// 入力信号(実数)
var signal: [Float] = Array(repeating: 0, count: n)
// サイン波を生成(例:440Hz)
for i in 0..<n {
    signal[i] = sin(2.0 * .pi * 440.0 * Float(i) / 44100.0)
}

// Split Complex形式に変換
var realPart = [Float](repeating: 0, count: n/2)
var imagPart = [Float](repeating: 0, count: n/2)

realPart.withUnsafeMutableBufferPointer { realBP in
    imagPart.withUnsafeMutableBufferPointer { imagBP in
        var splitComplex = DSPSplitComplex(
            realp: realBP.baseAddress!,
            imagp: imagBP.baseAddress!
        )
        
        signal.withUnsafeBytes { signalBytes in
            let signalPtr = signalBytes.bindMemory(to: DSPComplex.self).baseAddress!
            vDSP_ctoz(signalPtr, 2, &splitComplex, 1, vDSP_Length(n/2))
        }
        
        // FFT実行
        vDSP_fft_zrip(fftSetup, &splitComplex, 1, log2n, FFTDirection(kFFTDirection_Forward))
        
        // マグニチュード計算
        var magnitudes = [Float](repeating: 0, count: n/2)
        vDSP_zvabs(&splitComplex, 1, &magnitudes, 1, vDSP_Length(n/2))
        
        // 最大値のインデックス(=支配的な周波数)
        var maxMagnitude: Float = 0
        var maxIndex: vDSP_Length = 0
        vDSP_maxvi(magnitudes, 1, &maxMagnitude, &maxIndex, vDSP_Length(n/2))
        
        let dominantFrequency = Float(maxIndex) * 44100.0 / Float(n)
        print("Dominant frequency: \(dominantFrequency) Hz")
    }
}

Swift Overlayスタイル(モダン)

最近のvDSPはSwiftフレンドリーなAPIも提供している:

import Accelerate

let signal: [Float] = ... // 入力信号
let n = signal.count

// FFTを作成
let fft = vDSP.FFT(
    log2n: vDSP_Length(log2(Float(n))),
    radix: .radix2,
    ofType: DSPSplitComplex.self
)!

// フォワードFFT
var realPart = [Float](repeating: 0, count: n/2)
var imagPart = [Float](repeating: 0, count: n/2)

signal.withUnsafeBufferPointer { signalPtr in
    var splitComplex = DSPSplitComplex(realp: &realPart, imagp: &imagPart)
    fft.forward(input: signalPtr.baseAddress!, output: &splitComplex)
}

// マグニチュード計算(Swift風)
let magnitudes = zip(realPart, imagPart).map { sqrt($0*$0 + $1*$1) }

Biquadフィルタ:オーディオイコライザの実装

Logic ProやGarageBandのようなオーディオアプリでイコライザを実装するなら、Biquadフィルタがある:

import Accelerate

// Biquadフィルタの係数(ローパスフィルタの例)
// これらの係数は目的の周波数特性に応じて計算
let b0: Float = 0.292893218813
let b1: Float = 0.585786437627
let b2: Float = 0.292893218813
let a1: Float = 0.0
let a2: Float = 0.171572875254

// フィルタ係数配列
var coefficients: [Float] = [b0, b1, b2, a1, a2]

// 遅延要素(フィルタの状態、ステレオなら2チャンネル分)
var delays = [Float](repeating: 0, count: 2)

// 入力オーディオ
var inputSignal: [Float] = ... 
var outputSignal = [Float](repeating: 0, count: inputSignal.count)

// フィルタ処理
vDSP_biquad(
    &coefficients,
    &delays,
    &inputSignal, 1,
    &outputSignal, 1,
    vDSP_Length(inputSignal.count)
)

リアルタイムオーディオ処理で、この手の計算が毎フレーム必要になる。vDSPなしでは考えられない。

マルチバンドイコライザ

複数のBiquadフィルタを連結することで、マルチバンドイコライザを実装できる:

// 5バンドイコライザの係数(各バンドごとに5係数)
var allCoefficients: [Float] = [
    // バンド1: 60Hz
    b0_1, b1_1, b2_1, a1_1, a2_1,
    // バンド2: 230Hz
    b0_2, b1_2, b2_2, a1_2, a2_2,
    // ... 続く
]

var delays = [Float](repeating: 0, count: 2 * 5)  // 5セクション × 2遅延

// マルチセクションBiquad
vDSP_biquadm(
    &allCoefficients,
    &delays,
    &inputSignal, 1,
    &outputSignal, 1,
    vDSP_Length(inputSignal.count),
    5  // セクション数
)

ベクトル演算:高速な配列処理

単純な配列演算もvDSPなら超高速:

import Accelerate

let a: [Float] = [1, 2, 3, 4, 5]
let b: [Float] = [5, 4, 3, 2, 1]
var result = [Float](repeating: 0, count: 5)

// 要素ごとの加算
vDSP_vadd(a, 1, b, 1, &result, 1, vDSP_Length(5))
// result = [6, 6, 6, 6, 6]

// 要素ごとの乗算
vDSP_vmul(a, 1, b, 1, &result, 1, vDSP_Length(5))
// result = [5, 8, 9, 8, 5]

// スカラー乗算
var scalar: Float = 2.0
vDSP_vsmul(a, 1, &scalar, &result, 1, vDSP_Length(5))
// result = [2, 4, 6, 8, 10]

// 内積
var dotProduct: Float = 0
vDSP_dotpr(a, 1, b, 1, &dotProduct, vDSP_Length(5))
// dotProduct = 35

// 総和
var sum: Float = 0
vDSP_sve(a, 1, &sum, vDSP_Length(5))
// sum = 15

// 平均
var mean: Float = 0
vDSP_meanv(a, 1, &mean, vDSP_Length(5))
// mean = 3.0

「forループで書けばいいじゃん」と思うかもしれないが、vDSPはSIMD命令(NEON)を使って並列処理する。特に大きな配列では圧倒的な差が出る。


vDSP vs Neural Engine / GPU

「でも、GPUやNeural Engineの方が速いんじゃないの?」という疑問があるだろう。

Apple Developer Forumsでの回答:

"vDSP and veclib DONT use ANE."

(vDSPとveclibはANE(Neural Engine)を使用しません)

出典:Apple Developer Forums

これは制限ではなく、むしろ設計上の選択だ。

vDSPの強み

特徴 vDSP (CPU) GPU Neural Engine
レイテンシ 最小 転送オーバーヘッドあり 転送オーバーヘッドあり
性能予測 安定 バッチサイズ依存 モデル依存
省電力 小規模データで有利 大規模で有利 ML特化
可用性 常に利用可能 デバイス依存 A11以降

使い分けガイド

小さなデータ(< 数千要素)
  → vDSP / Accelerate

リアルタイム処理(オーディオ等)
  → vDSP(レイテンシ最小)

大規模バッチ処理
  → GPU / MPS

ML推論
  → Core ML / Neural Engine

機械学習との関係:前処理・後処理

機械学習パイプラインでもvDSPは活躍する。

音声認識の前処理例

// 音声認識モデルの入力を作る流れ

// 1. 音声波形を取得
let waveform: [Float] = ... // 44100Hzのオーディオ

// 2. プリエンファシス(vDSP)
var emphasized = [Float](repeating: 0, count: waveform.count)
let alpha: Float = 0.97
for i in 1..<waveform.count {
    emphasized[i] = waveform[i] - alpha * waveform[i-1]
}

// 3. フレーム分割とウィンドウ関数適用(vDSP)
let frameSize = 400
let hopSize = 160
var window = [Float](repeating: 0, count: frameSize)
vDSP_hann_window(&window, vDSP_Length(frameSize), Int32(vDSP_HANN_NORM))

// 4. 各フレームにFFT(vDSP)
for frameStart in stride(from: 0, to: emphasized.count - frameSize, by: hopSize) {
    var frame = Array(emphasized[frameStart..<frameStart+frameSize])
    var windowedFrame = [Float](repeating: 0, count: frameSize)
    vDSP_vmul(frame, 1, window, 1, &windowedFrame, 1, vDSP_Length(frameSize))
    
    // FFT実行...
}

// 5. メルフィルタバンク適用(vDSP行列演算)
// ...

// 6. Core MLモデルに入力
let mlInput = ...
let prediction = try model.prediction(input: mlInput)

前処理をvDSPで高速化し、推論をCore ML/Neural Engineで実行。これがApple流の最適パイプラインだ。


パフォーマンス Tips

1. セットアップを再利用

// ❌ 毎回作成(遅い)
for _ in 0..<1000 {
    let fftSetup = vDSP_create_fftsetup(log2n, FFT_RADIX2)
    // 使用...
    vDSP_destroy_fftsetup(fftSetup)
}

// ✅ 一度作成して再利用
let fftSetup = vDSP_create_fftsetup(log2n, FFT_RADIX2)!
for _ in 0..<1000 {
    // fftSetupを使用...
}
vDSP_destroy_fftsetup(fftSetup)

2. ストライドを活用

インターリーブデータも効率的に処理:

// ステレオオーディオ(LRLRLR...)から左チャンネルだけ処理
let stereo: [Float] = ...
var leftMagnitudes = [Float](repeating: 0, count: stereo.count / 2)

// ストライド2で左チャンネルのみアクセス
vDSP_vabs(stereo, 2, &leftMagnitudes, 1, vDSP_Length(stereo.count / 2))

3. インプレース演算

可能な場合は同じバッファに結果を書き込む:

var data: [Float] = ...

// インプレース(メモリ節約)
vDSP_vabs(data, 1, &data, 1, vDSP_Length(data.count))

まとめ:GPUだけが全てじゃない

機械学習の文脈でCPU演算が軽視されがちだが、vDSPのような高度に最適化されたライブラリは依然として重要だ。

特に:

  • リアルタイムオーディオ処理
  • 信号の前処理・後処理
  • レイテンシクリティカルな処理
  • 小〜中規模のベクトル演算

こういった場面では、vDSPが最適解になる。Accelerateフレームワーク全体を含めて、Apple Siliconの機械学習スタックの重要な一角を担っている。


次に読む


参考文献

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?