URLパーザのadaを使ってみる
他のURLパーザだとここらへんでしょうか。
- cpp-netlib
- lurlparser
- skyr-url
- Uriparser
私はいつもはskyr-urlを使っています。
お試しするに際して、conanというパッケージマネージャーを使います。
C++のパッケージマネージャー嫌いな人、vcpkgみたいな他のパッケージマネージャー使っている人ごめんなさい。
conanのメインのパッケージリポジトリであるconan-center-indexにはadaのパッケージがあるので、これを使います。
[requires]
ada/2.3.0
[generators]
CMakeToolchain
CMakeDeps
conanfile.txtが存在する同じディレクトリで以下のコマンドを実行します。
conan install . -of build --build=missing
同じディレクトリにCMakeLists.txtも作成します。
cmake_minimum_required(VERSION 3.9)
project(study_ada LANGUAGES CXX)
include(CheckIPOSupported)
check_ipo_supported(RESULT ipo_supported OUTPUT error)
if(ipo_supported)
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION TRUE)
endif()
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)
find_package(ada REQUIRED CONFIG)
foreach(idx RANGE 1 99)
if(idx LESS 10)
set(idx "0${idx}")
endif()
if(EXISTS "${CMAKE_SOURCE_DIR}/test${idx}.cpp")
add_executable(test${idx} test${idx}.cpp)
target_link_libraries(test${idx} PRIVATE ada::ada)
target_compile_features(test${idx} PRIVATE cxx_std_17)
endif()
endforeach()
※cmakeのハイライトさせたいのですが、なんかada::ada
の部分で表示が乱れるのでプレーンテキストにしています。
test01.cppファイルも同じディレクトリに配置します。
#include <ada.h>
auto main() -> int {
auto const url = "https://github.com/ada-url/ada";
if (auto const result = ada::parse<ada::url_aggregator>(url)) {
std::cout << "host : " << result->get_host() << '\n';
}
return 0;
}
cmakeでのビルドを実行します。
cmake -B build -DCMAKE_TOOLCHAIN_FILE=conan_toolchain.cmake -DCMAKE_BUILD_TYPE=Release -S .
cmake --build build -j 4
build/test01
が生成されます。
実行するとこんな感じ。
host : github.com
他のメソッドも使ってみます。
#include <ada.h>
auto main() -> int {
auto const url = "https://github.com/ada-url/ada";
if (auto const result = ada::parse<ada::url_aggregator>(url)) {
std::cout << "protocol : " << result->get_protocol() << '\n';
std::cout << "host : " << result->get_host() << '\n';
std::cout << "path : " << result->get_pathname() << '\n';
std::cout << "port : " << result->get_port() << '\n';
std::cout << '\n';
std::cout << "href : " << result->get_href() << '\n';
}
return 0;
}
実行するとこんな感じ。
portについてはデフォルトポート番号が埋まるわけではないですね。
各get_xxx()
の戻り値はstd::string_view
でした。
protocol : https:
host : github.com
path : /ada-url/ada
port :
href : https://github.com/ada-url/ada
公式のREADME.mdには明記ありませんが、ada::parse
のテンプレートパラメータは省略可能みたいです。
#include <ada.h>
auto main() -> int {
auto const url = "https://github.com/ada-url/ada";
if (auto const result = ada::parse(url)) {
std::cout << "host : " << result->get_host() << '\n';
}
return 0;
}
README.mdが省略表記ではないので、これ以降は省略しない方で書きますかね。
「じゃあ、テンプレートパラメータは何のためにあるの?」ってなりますが内部で処理結果をどう持つのかに影響します。
クラス名 | 動作 |
get_xxx() の型 |
---|---|---|
ada::url_aggregator |
parse() 内で解析結果を1つのstd::string で保持して、get_xxx() ではその部分文字列を返す |
std::string_view |
ada::url |
parse() 内で各区分ごとにstd::string を作成・保持する |
std::string だったりstd::string_view だったり色々 |
このため test01.cpp の内容だったら ada::url
に変更するだけで動作します。
#include <ada.h>
auto main() -> int {
auto const url = "https://github.com/ada-url/ada";
if (auto const result = ada::parse<ada::url>(url)) {
std::cout << "host : " << result->get_host() << '\n';
}
return 0;
}
ada::url
使う場面は次のような感じですかね。
- パースした後の一部分だけ長いスコープで持ちたい
- URLの一部を更新する (更新処理は後で出てきます)
普通はada::url_aggregator
で良い気がします。
ada::url_aggregator
はada 2.0で追加された機能で、これによりadaのパース性能は20%ぐらい向上したみたいですね。
Announcing Ada URL parser v2.0
ada::parse()
の戻り値はada::result
となっていますが、実態はadaにとりこまれたexpectedになっています。
なので、次のようなコードも動きます。
「意味があるか?」と言われるとあんまりないですかね。
#include <ada.h>
auto main() -> int {
auto const url = "https://github.com/ada-url/ada";
ada::parse<ada::url_aggregator>(url)
.map([](ada::url_aggregator const& result) {
std::cout << result.get_host() << '\n';
});
return 0;
}
adaではパース結果に対して変更をすることもできます。
#include <ada.h>
auto main() -> int {
auto const url = "https://github.com/ada-url/ada";
std::cout << "original : " << url << '\n';
if (auto result = ada::parse<ada::url_aggregator>(url)) {
result->set_host("bitbucket.org");
std::cout << "modified : " << result->get_href() << '\n';
result->set_port("18880");
std::cout << "modified : " << result->get_href() << '\n';
result->set_protocol("http");
std::cout << "modified : " << result->get_href() << '\n';
}
return 0;
}
実行すると次のように出力されます。
original : https://github.com/ada-url/ada
modified : https://bitbucket.org/ada-url/ada
modified : https://bitbucket.org:18880/ada-url/ada
modified : http://bitbucket.org:18880/ada-url/ada
私の用途では、URLがパースさえできればいいので、あんまり使わない機能な気がします。
日本語ドメインのパースもできました。偉い。
#include <ada.h>
auto main() -> int {
auto const url = "https://日本語.jp/case/accessible/all.html";
std::cout << "original : " << url << '\n';
if (auto const result = ada::parse<ada::url_aggregator>(url)) {
std::cout << "hostname : " << result->get_host() << '\n';
}
return 0;
}
ちゃんとPunycode変換されているのが分かります。
original : https://日本語.jp/case/accessible/all.html
hostname : xn--wgv71a119e.jp
Punycodeの逆変換を勝手にやってくれるわけではありません。(当然か)
これはadaが内部的に利用しているidnaの関数を利用することができます。
#include <ada.h>
auto main() -> int {
auto const url = "https://xn--wgv71a119e.jp/case/accessible/all.html";
std::cout << "original : " << url << '\n';
if (auto const result = ada::parse<ada::url_aggregator>(url)) {
std::cout << "hostname : " << result->get_host() << '\n';
std::cout << " : " << ada::idna::to_unicode(result->get_host()) << '\n';
}
return 0;
}
実行結果です。
original : https://xn--wgv71a119e.jp/case/accessible/all.html
hostname : xn--wgv71a119e.jp
: 日本語.jp
たぶん、これは非推奨のやり方なので将来的に使えなくなる可能性は高いですね。
素直にidnaをインストールして使いましょう。
だいたい興味のある機能は触ってみたので、最後に公式ベンチマークを実行してみて終わりにしようと思います。
公式ベンチマークはCMakeでconfigureする時にADA_BENCHMARKを有効にすることでビルドできるようになります。内部でしれっとrustの開発環境が必要になるので、インストールしていない人は途中でエラーになってしまうかも。
cd /tmp
wget -nd https://github.com/ada-url/ada/archive/refs/tags/v2.3.0.tar.gz -O - | tar zxvf -
cd ada-2.3.0
cmake -B build -S . -DADA_BENCHMARKS=ON
cmake --build build
build/benchmark/bench
軽く Ryzen5 5600X の環境で実行してみた結果がこちら。
parser | Time | CPU | Iterations |
---|---|---|---|
BasicBench_AdaURL | 3140 ns | 3132 ns | 220056 |
BasicBench_AdaURL_aggregator | 2017 ns | 2013 ns | 357261 |
BasicBench_whatwg | 4728 ns | 4716 ns | 143744 |
BasicBench_CURL | 9752 ns | 9733 ns | 66976 |
BasicBench_ServoUrl | 7236 ns | 7218 ns | 92216 |
なるほど、確かに速い。url_aggregatorもちゃんと優位な差が出てますね。
ベンチマークが何種類かあるので、色々ためすと結構数値は変わりましたが、adaが速いことに変わりなしでした。
ひとまずこれで一段落とします。
- かなり直感的に使える
- 高速に動く
- node.jsで採用されているから当分メンテナンスされるだろう安心感
- 日本語ドメインにも対応
という強みがあり、弱点が見あたらないので徐々にadaに移行しようかなと思っています。
Ada 2.4.2がリリースされて、simdjsonやsimdutfのコミッタでもあるDaniel Lemireさん中心に色々チューニングが進んでいるみたいなので、再計測してみました。
parser | Time | CPU | Iterations |
---|---|---|---|
BasicBench_AdaURL | 3002 ns | 2998 ns | 228199 |
BasicBench_AdaURL_aggregator | 1906 ns | 1903 ns | 378945 |
BasicBench_whatwg | 4799 ns | 4792 ns | 141125 |
BasicBench_CURL | 9805 ns | 9792 ns | 70939 |
BasicBench_ServoUrl | 7068 ns | 7059 ns | 84714 |
Ada urlもAda url_aggregatorも4-5%ぐらい高速化してますね。
今後の高速化にも期待できそうです。