1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

String.Equals はもう使う必要ない説

Last updated at Posted at 2024-06-04

更新履歴: StringComparison.OrdinalIgnoreCase と ToLowerInvariant/ToUpperInvariant の比較
 

文字列の比較に string.Equals を使うのって昔はテクニックとして有効だったのかもしれないけど、今(.NET Standard 2.1)ではもう使う意味なくね? って感じだったので色々と調べました。

【テスト環境】コンソールアプリ @ BenchmarkDotNet v0.13.12

テストコード

BenchmarkDotNet v0.13.12
[MemoryDiagnoser]
public class Benchmark
{
    string Irohani1st = "いろはにほへと ちりぬるを わかよたれそ  つねならむ うゐのおくやま けふこえて あさきゆめみし ゑひもせす";
    string Irohani2nd = "いろはにほへと ちりぬるを わかよたれそ  つねならむ うゐのおくやま けふこえて あさきゆめみし ゑひもせす";
    string TheQuickBrown = "The quick brown fox jumps over the lazy dog";

    [Benchmark]
    public void StringComparisonOp()
    {
        if (Irohani1st == TheQuickBrown)
            goto EXIT;
        else if (Irohani1st == Irohani2nd)
            goto EXIT;
    EXIT:
        ;
    }

    [Benchmark]
    public void StringComparisonEqualsOrdinal()
    {
        if (Irohani1st.Equals(TheQuickBrown, StringComparison.Ordinal))
            goto EXIT;
        else if (Irohani1st.Equals(Irohani2nd, StringComparison.Ordinal))
            goto EXIT;
    EXIT:
        ;
    }

    [Benchmark]
    public void StringComparisonEquals()
    {
        if (Irohani1st.Equals(TheQuickBrown))
            goto EXIT;
        else if (Irohani1st.Equals(Irohani2nd))
            goto EXIT;
    EXIT:
        ;
    }
}

凡例

  • StringComparisonOp ==
  • StringComparisonEqualsOrdinal string.Equals(..., StringComparison.Ordinal)
  • StringComparisonEquals string.Equals(...)

.NET Core 3.0(Unity 想定)

Core 3.0 は .NET Standard 2.1 なので Unity 想定です。Ordinal 無しの方が速いのは意外。

NET Core 3.1.32 でのベンチマーク結果

Method Mean Error StdDev Allocated
StringComparisonOp 2.644 ns 0.0136 ns 0.0121 ns -
StringComparisonEqualsOrdinal 3.866 ns 0.0305 ns 0.0254 ns -
StringComparisonEquals 2.665 ns 0.0263 ns 0.0233 ns -

[MethodImpl(MethodImplOptions.NoOptimization)] アリ

なんの意味もないですが純粋なスピード勝負? てことで。==Equals にたどり着くまでのインライン化がなくなったとかそういう感じ?

Method Mean Error StdDev Allocated
StringComparisonOp 3.609 ns 0.0384 ns 0.0359 ns -
StringComparisonEqualsOrdinal 4.811 ns 0.0619 ns 0.0483 ns -
StringComparisonEquals 2.304 ns 0.0115 ns 0.0096 ns -

.NET 8.0

全体的に .Net Core 3 よりもかなり速くなってますね。ナノ秒レベルですが。こっちは StringComparison.Ordinal アリの Equals が速い結果に。

.NET 8.0.5 (8.0.524.21615) でのベンチマーク結果

Method Mean Error StdDev Allocated
StringComparisonOp 0.6416 ns 0.0047 ns 0.0040 ns -
StringComparisonEqualsOrdinal 0.7478 ns 0.0057 ns 0.0048 ns -
StringComparisonEquals 1.3397 ns 0.0072 ns 0.0067 ns -

MethodImplOptions.NoOptimization アリ

最適化ナシだと Ordinal アリが遅くなるのは謎。

Method Mean Error StdDev Allocated
StringComparisonOp 2.605 ns 0.0085 ns 0.0079 ns -
StringComparisonEqualsOrdinal 3.099 ns 0.0229 ns 0.0179 ns -
StringComparisonEquals 2.126 ns 0.0152 ns 0.0118 ns -

Span.SequenceEqual

.Net 8 が速いのは SequenceEqual 使ってるからじゃね? と思いましたが、純粋に実行速度が速くなってるみたいです。CoreCLR を使うように変わったとか??

[Benchmark]
public void StringComparisonSequenceEqual()
{
    if (Irohani1st.AsSpan().SequenceEqual(TheQuickBrown))
        goto EXIT;
    else if (Irohani1st.AsSpan().SequenceEqual(Irohani2nd))
        goto EXIT;
EXIT:
    ;
}

.Net Core 3.0 でのベンチマーク結果

Method Mean Error StdDev Allocated
StringComparisonOp 2.594 ns 0.0089 ns 0.0079 ns -
StringComparisonEqualsOrdinal 4.454 ns 0.0033 ns 0.0030 ns -
StringComparisonEquals 3.077 ns 0.0082 ns 0.0068 ns -
StringComparisonSequenceEqual 2.876 ns 0.0035 ns 0.0028 ns -

2回目

Method Mean Error StdDev Allocated
StringComparisonOp 2.470 ns 0.0201 ns 0.0188 ns -
StringComparisonEqualsOrdinal 4.493 ns 0.0379 ns 0.0296 ns -
StringComparisonEquals 2.609 ns 0.0052 ns 0.0041 ns -
StringComparisonSequenceEqual 3.314 ns 0.0204 ns 0.0181 ns -

追記)OrdinalIgnoreCase と ToUpper/ToLower

文字列比較に Equals を使うのは元々は Java 由来の慣例らしく、C# で単純な文字列比較を行う手段としては全くの意味なし(なんならちょっと遅い)ですが、OrdinalIgnoreCase な比較であれば話は別です。

以下は何のテストになっているのか微妙な内容のベンチマークですが、string.Equals(..., StringComparison.OrdinalIgnoreCase は使える場面があったら積極的に使いたいという感じのパフォーマンスが出ています。

ToLower/ToUpper の実行頻度を低く抑えられるのであれば(低く抑えて) == による比較を行った方が良い結果を得られますが、それが無理なこともあるでしょう。

テストコード
string TheQuickBrownUpper = "the quick brown fox jumps over the lazy dog";
string TheQuickBrownLower = "THE QUICK BROWN FOX JUMPS OVER THE LAZY DOG";

[Benchmark]
public void StringComparisonEqualsIgnoreCase()
{
    if (TheQuickBrownLower.Equals(Irohani1st, StringComparison.OrdinalIgnoreCase))
        goto EXIT;
    else if (TheQuickBrownLower.Equals(TheQuickBrownUpper, StringComparison.OrdinalIgnoreCase))
        goto EXIT;
EXIT:
    ;
}

[Benchmark]
public void StringComparisonToLowerInvariant()
{
    if (TheQuickBrownLower == Irohani1st.ToLowerInvariant())
        goto EXIT;
    else if (TheQuickBrownLower == TheQuickBrownUpper.ToLowerInvariant())
        goto EXIT;
EXIT:
    ;
}

[Benchmark]
public void StringComparisonToUpperInvariant()
{
    if (TheQuickBrownUpper == Irohani1st.ToLowerInvariant())
        goto EXIT;
    else if (TheQuickBrownUpper == TheQuickBrownLower.ToUpperInvariant())
        goto EXIT;
EXIT:
    ;
}

凡例

  • StringComparisonEqualsIgnoreCase string.Equals(..., StringComparison.OrdinalIgnoreCase)
  • StringComparisonToLowerInvariant string == other.ToLowerInvariant()
  • StringComparisonToUpperInvariant string == other.ToUpperInvariant()

.NET Core 3.1.32

Method Mean Error StdDev Gen0 Allocated
StringComparisonEqualsIgnoreCase 21.534 ns 0.0266 ns 0.0208 ns - -
StringComparisonToLowerInvariant 154.355 ns 1.1894 ns 1.1126 ns 0.0162 136 B
StringComparisonToUpperInvariant 154.377 ns 1.0553 ns 0.9355 ns 0.0162 136 B
--
StringComparisonOp 2.535 ns 0.0252 ns 0.0236 ns - -
StringComparisonEqualsOrdinal 3.875 ns 0.0347 ns 0.0290 ns - -
StringComparisonEquals 2.943 ns 0.0692 ns 0.0578 ns - -
StringComparisonSequenceEqual 2.902 ns 0.0510 ns 0.0452 ns - -

.NET 8.0.5 (8.0.524.21615)

Method Mean Error StdDev Gen0 Allocated
StringComparisonEqualsIgnoreCase 12.3164 ns 0.1092 ns 0.0912 ns - -
StringComparisonToLowerInvariant 254.3058 ns 2.3199 ns 2.1701 ns 0.0162 136 B
StringComparisonToUpperInvariant 255.7412 ns 1.0547 ns 0.9866 ns 0.0162 136 B
--
StringComparisonOp 1.0586 ns 0.0245 ns 0.0217 ns - -
StringComparisonEqualsOrdinal 0.8159 ns 0.0052 ns 0.0040 ns - -
StringComparisonEquals 1.1643 ns 0.0294 ns 0.0260 ns - -
StringComparisonSequenceEqual 2.9543 ns 0.0271 ns 0.0254 ns - -

参考)string.Equals のソースコード

おわりに

マイクロベンチマークなんて参考程度にとらえるべき、ですが実際のところ .NET Standard 2.1 以降(Unity 2021 以降?)なら string.Equals を単純な文字列比較を行うだけのケースで使う必要はないと思います。

Unity の場合は IL2CPP の結果も見ないとですが、まあ劇的には変わらないと思うので使わなくて良いでしょう。

以上です。お疲れ様でした。

1
2
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
1
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?