JUCEアプリケーションのベンチマーク測定

JUCEでオーディオのアプリケーション・プラグインを作るとき、 その性能が大事になってくるケースも多いかと思います。 そこでGoogle Benchmarkを使ったベンチマークの測定が可能なJUCEプロジェクトをCMakeを使って作ります。

今回のリポジトリこちら

今回はプロジェクトの設定にあたって

  • 巷にあるテンプレートリポジトリに頼らずやる
  • 最小限の設定にして、できるだけシンプルにする

という方針でやります。

事前に用意するもの

参考までに最終コードの動作確認は - macOS 12.0 + clang 13.0.0 - ubuntu 21.10 + gcc 11.2.0 - Windows 10 + Visual Stduio 2019のMSVC

で行いました。

空のGitHubリポジトリの用意

普通にリポジトリ用意します。

リポジトリにJUCEの追加(ダウンロード)

git submoduleを使ってJUCEのリポジトリをサブモジュールとしてlib/JUCEディレクトリに追加します。

# プロジェクトルート (.gitファイルがあるフォルダ) にcdコマンドで事前に移動してください

$ git submodule add https://github.com/juce-framework/JUCE lib/JUCE

これでJCUEのmasterブランチが追加されますが、より安定しているリリースバージョン(今回は6.1.2)を 使います。

$ cd lib/JUCE
$ git checkout 6.1.2
# "HEAD is now at 46ea87973 JUCE version 6.1.2"と表示されるはず。

ここまでの変更を一旦コミットします。

$ cd ../../ # プロジェクトルートに戻る
$ git add .
$ git commit -m "Add JUCE 6.1.2"

これでサブモジュールの追加は完了です。

ここまでのコミット

アプリ・プラグイン本体のコードテンプレートの生成

JuceにはProjucerというツールが用意されていて JUCEアプリケーション(ベンチマークではなく本体)の骨組みを簡単に生成できます。 今回はVST3などのオーディオプラグインを作ります。

Projucerの用意

./lib/JUCE/README.md (要するJUCEリポジトリのREADME.md)に従ってProjucerをビルドをします。

$ cd lib/JUCE # サブモジュールとして追加したJUCEのディレクトリに移動。
$ cmake . -B cmake-build -DCMAKE_BUILD_TYPE=Release -DJUCE_BUILD_EXTRAS=ON # Configureする
$ cmake --build cmake-build --config Release --target Projucer # JUCEプロジェクト生成アプリをビルド

.lib/JUCE/cmake-build/extras/Projucer/Projucer_artefacts/Release/以下にProjucerの実行ファイルが (Projucer.appとかProjucer.exeとかの名前で) 生成されているはずです。

Projucerを使ったコードテンプレートの生成

以下の手順でプラグインのコードテンプレートを生成します。

  • Projucerを開きます
  • Plug-in->Basicをクリック
  • Project Nameを入力
  • Path to Modulesから./lib/JUCEを選択
  • Create Projectをクリックして適当なフォルダにプロジェクト生成。

projucer_project_name

  • Save and Open in IDEをクリック

projucer_save_and_open_ide

これで保存したプロジェクトのSourceディレクトリ内にPluginEditor.cpp, PluginEditor.h, PluginProcessor.cpp, PluginProcessor.hの4つのファイルが生成されているはずです。

参考: https://docs.juce.com/master/tutorial_new_projucer_project.html

プラグイン本体のCMakeプロジェクトの作成

まず生成されたSourceディレクトリ内の4つのファイルをリポジトリ./srcにコピーしてください。

次にプロジェクトルート用とsrcフォルダ以下にそれぞれCMakeLists.txtを作ります。 変更内容はこちらのコミットで見れます。

公式ドキュメント: https://github.com/juce-framework/JUCE/blob/master/docs/CMake%20API.md

参考: https://qiita.com/tomoyanonymous/items/97cae1b83805ebcc2d00

以下のコマンドでプラグインがビルドできます。

$ cmake . -DCMAKE_BUILD_TYPE=Release -B cmake-build
$ cmake --build cmake-build --config Release --target JuceBenchmark_Standalone # プラグイン(スタンドアローン版)をビルド

ちなみにビルド可能なターゲットの一覧はgccやclangを使っている場合、$ cmake --build cmake-build --target helpで確認できます。

ベンチマーク対象のクラスを作成

Google Benchmarkでベンチマークをとるには測定対象をC++のクラスとして用意する必要があります。 このコミットFancyFxという名前のクラスを作りました。

ベンチマークの作成

プロジェクトルートのCMakeLists.txtにGoogle Benchmarkとその依存ライブラリのGoogle Testを追加します。

# Google Benchmark and its dependency
# https://stackoverflow.com/questions/55376111/how-to-build-and-link-google-benchmark-using-cmake-in-windows
include(FetchContent)

FetchContent_Declare(googletest
    GIT_REPOSITORY https://github.com/google/googletest.git
    GIT_TAG release-1.11.0) # 2021年12月での最新バージョン

FetchContent_Declare(googlebenchmark
    GIT_REPOSITORY https://github.com/google/benchmark.git
    GIT_TAG v1.6.0) # 2021年12月での最新バージョン

FetchContent_MakeAvailable(
    googletest
    googlebenchmark)

同じファイルの最後に以下の様に追記して、これから作る./benchmarkディレクトリをプロジェクトに追加します。

add_subdirectory(benchmark)

benchmarkディレクトリ内にCMakeLists.txtを作ります。

# JUCEのコンソールアプリとしてCMakeターゲットを追加
juce_add_console_app(JuceBenchmark_Benchmark)

# Google Benchmarkをリンク
target_link_libraries(JuceBenchmark_Benchmark PUBLIC
     benchmark)

target_compile_definitions(JuceBenchmark_Benchmark
    PUBLIC
    JUCE_WEB_BROWSER=0 # いらないものは使用しないための設定
    JUCE_USE_CURL=0) # いらないものは使用しないための設定

target_sources(JuceBenchmark_Benchmark PRIVATE
        Main.cpp) # 測定対象のコード。次に追加します。

# JUCE関連のライブラリをリンク(必要に応じて足したり消したりしてください。)
target_link_libraries(JuceBenchmark_Benchmark PUBLIC
        juce::juce_audio_basics
        juce::juce_audio_devices
        juce::juce_audio_formats
        juce::juce_audio_plugin_client
        juce::juce_audio_processors
        juce::juce_audio_utils
        juce::juce_core
        juce::juce_data_structures
        juce::juce_dsp
        juce::juce_events
        juce::juce_graphics
        juce::juce_gui_basics
        juce::juce_gui_extra
        )

juce_generate_juce_header(JuceBenchmark_Benchmark)

Main.cppにベンチマークの中身を書きます。 ここは単なる普通のGoogle Benchmarkの使い方です。

#include <JuceHeader.h>
#include <benchmark/benchmark.h>
#include <cmath>
#include "../src/FancyFx.h"

//==============================================================================
// Constants

constexpr double SAMPLE_RATE = 44100.0;
constexpr double MAX_TIME_SEC = 2.0;
constexpr double PI = 3.14159265359;
constexpr double FREQ_A = 440.0;
constexpr int NUM_SAMPLE = static_cast<int> (SAMPLE_RATE) * static_cast<int> (MAX_TIME_SEC);
constexpr int NUM_CHANNEL = 2;

//==============================================================================

// benchmark::Fixtureを継承
class FancyFxFixture : public benchmark::Fixture
{
public:
    FancyFxFixture() : buffer(NUM_CHANNEL, NUM_SAMPLE), fancy_fx() {}

    // Google Benchmarkフィクスチャ特有のセットアップ
    void SetUp (::benchmark::State& state) override
    {
        // ベンチマーク用のインプットバッファを用意。
        for (int channel = 0; channel < buffer.getNumChannels(); ++channel)
        {
            auto* channelData = buffer.getWritePointer (channel);
            for (int i = 0; i < buffer.getNumSamples(); i++)
            {
                // 440 Hz sin wave
                channelData[i] = channelData[i] * std::sin(2.0 * PI * FREQ_A * i / SAMPLE_RATE);
            }
        }
    }

    // Google Benchmarkフィクスチャ特有のテアダウン
    void TearDown (::benchmark::State& state) override
    {
    }

    // 計測対象コード。名前は任意。
    void render()
    {
        fancy_fx.render (buffer);
    }

    //==============================================================================
private:
    juce::AudioBuffer<float> buffer;
    FancyFx fancy_fx;
    
};

// Google Benchmark独自のお作法
BENCHMARK_F (FancyFxFixture, render)
(benchmark::State& state)
{
    for (auto _ : state)
    {
        render();
    }
}

BENCHMARK_MAIN();

ベンチマーク追加のコミットはこちら

以下のようにすればベンチマークを実行できます。

$ cmake . -DCMAKE_BUILD_TYPE=Release -B cmake-build # Configureする
$ cmake --build cmake-build --config Release --target JuceBenchmark_Benchmark # ベンチマークをビルド
$ ./cmake-build/benchmark/JuceBenchmark_Benchmark_artefacts/Release/JuceBenchmark_Benchmark # 実行(拡張子はOSによって違うので適宜調節してください。)

結果の例

Run on (10 X 24.1212 MHz CPU s)
CPU Caches:
  L1 Data 64 KiB (x10)
  L1 Instruction 128 KiB (x10)
  L2 Unified 4096 KiB (x5)
Load Average: 7.95, 5.90, 4.00
----------------------------------------------------------------
Benchmark                      Time             CPU   Iterations
----------------------------------------------------------------
FancyFxFixture/render      17418 ns        17418 ns        38999

トラブルシューティング

コンパイル失敗

コンパイラ, Google Benchmark, Google Testのバージョンの組み合わせ次第で 失敗することがあったのでその辺りを見直すといいかもしれません。

CPUスケーリングの警告

次のような警告が出ることがあります。

***WARNING*** CPU scaling is enabled, the benchmark real time measurements may be noisy and will incur extra overhead.

この場合はドキュメントに従って、 ベンチマーク実行前と後でそれぞれ下の様に設定すれば良い様です。

sudo cpupower frequency-set --governor performance
sudo cpupower frequency-set --governor powersave

まとめ

数字として性能がわかると安心ですね。