ほとんどのプログラマはIPアドレスに馴染みがあります。0から255までの4つの数字をドットで区切った形式、たとえば192.168.0.1です。
ある意味では、32ビット整数を表す複雑な方法です。
IPアドレスの現代版であるIPv6は通常角括弧で囲まれます。私の経験ではあまり一般的ではありません。
凝った手法を使うと、IPアドレスをわずか50命令程度で解析できます。これは少し複雑で、必ずしもポータブルではありません。
あまり手間をかけず、専門のライブラリも使わずに高速化したい場合は、自分で実装してみることもできます。しかし、私は礼儀正しいプログラマなので、お気に入りのAIに書いてもらいました。
// Parse an IPv4 address starting at 'p'.
// p : start pointer, pend: end of the string
std::expected<uint32_t, parse_error> parse_manual(const char *p, const char *pend) {
uint32_t ip = 0;
int octets = 0;
while (p < pend && octets < 4) {
uint32_t val = 0;
const char *start = p;
while (p < pend && *p >= '0' && *p <= '9') {
val = val * 10 + (*p - '0');
if (val > 255) {
return std::unexpected(invalid_format);
}
p++;
}
if (p == start || (p - start > 1 && *start == '0')) {
return std::unexpected(invalid_format);
}
ip = (ip << 8) | val;
octets++;
if (octets < 4) {
if (p == pend || *p != '.') {
return std::unexpected(invalid_format);
}
p++; // Skip dot
}
}
if (octets == 4 && p == pend) {
return ip;
} else {
return std::unexpected(invalid_format);
}
}
この関数は十分に高速ではないことがすぐにわかりました。そこで、各数値が1〜3桁であることを利用してAIに改善を依頼したところ、以下のような妥当な関数が得られました。
std::expected<uint32_t, parse_error> parse_manual_unrolled(const char *p, const char *pend) {
uint32_t ip = 0;
int octets = 0;
while (p < pend && octets < 4) {
uint32_t val = 0;
if (p < pend && *p >= '0' && *p <= '9') {
val = (*p++ - '0');
if (p < pend && *p >= '0' && *p <= '9') {
if (val == 0) {
return std::unexpected(invalid_format);
}
val = val * 10 + (*p++ - '0');
if (p < pend && *p >= '0' && *p <= '9') {
val = val * 10 + (*p++ - '0');
if (val > 255) {
return std::unexpected(invalid_format);
}
}
}
} else {
return std::unexpected(parse_error::invalid_format);
}
ip = (ip << 8) | val;
octets++;
if (octets < 4) {
if (p == pend || *p != '.') {
return std::unexpected(invalid_format);
}
p++; // Skip the dot
}
}
if (octets == 4 && p == pend) {
return ip;
} else {
return std::unexpected(invalid_format);
}
}
AI、よくできました!
C++には数値を解析する標準関数(std::from_chars)があり、コードを大幅に簡素化できます。
std::expected<uint32_t, parse_error> parse_ip(const char *p, const char *pend) {
const char *current = p;
uint32_t ip = 0;
for (int i = 0; i < 4; ++i) {
uint8_t value;
auto r = std::from_chars(current, pend, value);
if (r.ec != std::errc()) {
return std::unexpected(invalid_format);
}
current = r.ptr;
ip = (ip << 8) | value;
if (i < 3) {
if (current == pend || *current++ != '.') {
return std::unexpected(invalid_format);
}
}
}
return ip;
}
fast_floatライブラリをstd::from_charsの代わりに使うこともできます。fast_floatの最新版では、Soni Shikhar氏(Pavel Novikov氏による修正を含む)のおかげで8ビット整数の解析が高速化されています。
この問題のためのベンチマークを作成しました。まず、LLVM 17を搭載したApple M4プロセッサ(4.5 GHz)での結果を見てみましょう。
| function | instructions/ip | ns/ip |
|---|---|---|
| manual | 185 | 6.2 |
| manual (unrolled) | 114 | 3.3 |
| from_chars | 381 | 14 |
| fast_float | 181 | 7.2 |
次に、GCC 12とIntel Ice Lakeプロセッサ(3.2 GHz)を使った結果です。
| function | instructions/ip | ns/ip |
|---|---|---|
| manual | 219 | 30 |
| manual (unrolled) | 154 | 24 |
| from_chars | 220 | 29 |
最後に、LLVM 21を搭載した中国のLongsoon 3A6000プロセッサ(2.5 GHz)での結果です。
| function | instructions/ip | ns/ip |
|---|---|---|
| manual | 187 | 29 |
fast_floatライブラリの最適化作業は効果がありました。特にx64プロセッサでの違いが顕著です。
この小さな実験で興味深いのは、比較的少ない労力でAIに高速なコードを生成させられたことです。私はAIを「ガイド」する必要がありました。それでも引退できるわけではありません。しかし、良いリファレンスとなるベースラインをより迅速に得られるようになったことは嬉しく、重要な部分に集中しやすくなりました。
参考: The fast_float C++ libraryは、高速な数値解析ライブラリで、GCCや主要なWebブラウザに組み込まれています。
Published by
0 Comments
Log in to join the conversation.No comments yet. Be the first to share your thoughts.