Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

$"" パフォーマンス改善 #42

Closed
Tracked by #41
ufcpp opened this issue Aug 13, 2021 · 15 comments
Closed
Tracked by #41

$"" パフォーマンス改善 #42

ufcpp opened this issue Aug 13, 2021 · 15 comments

Comments

@ufcpp
Copy link
Collaborator

ufcpp commented Aug 13, 2021

@ufcpp ufcpp changed the title Interpolated string improvements $"" パフォーマンス改善 Aug 13, 2021
@ufcpp
Copy link
Collaborator Author

ufcpp commented Aug 13, 2021

https://gist.github.com/ufcpp/233d33b0220215bb5bdf2a10e8a434bf#file-benchmark-cs

手元の結果:

Method Mean Error StdDev Gen 0 Allocated
OldStyle 935.3 us 2.75 us 2.58 us 229.4922 1,875 KB
Improved 565.5 us 1.77 us 1.57 us 46.8750 391 KB
InvariantCulture 562.9 us 1.48 us 1.24 us 46.8750 391 KB
InitialBuffer 416.1 us 0.90 us 0.84 us 47.3633 391 KB
InitialBufferInvariantCulture 418.3 us 0.98 us 0.87 us 47.3633 391 KB
InitialBufferSkipLocalsInitInvariantCulture 406.9 us 1.51 us 1.41 us 47.3633 391 KB
InitialSingleBufferInvariantCulture 404.2 us 1.48 us 1.32 us 47.3633 391 KB
古いの
Method Mean Error StdDev Gen 0 Allocated
OldStyle 960.5 us 1.59 us 1.33 us 228.5156 1,875 KB
Improved 663.6 us 1.63 us 1.53 us 46.8750 391 KB
InvariantCulture 608.4 us 1.72 us 1.61 us 46.8750 391 KB
InitialBuffer 476.0 us 0.69 us 0.61 us 47.3633 391 KB
InitialBufferInvariantCulture 474.0 us 0.28 us 0.26 us 47.3633 391 KB

@ufcpp-live
Copy link
Owner

ufcpp-live commented Aug 14, 2021

// 多分リーク(ArrayPool への Return が掛からない)
var ss = $"{x} {m() ?? throw new Exception()}";

一応、現状の ArrayPool の実装上、 Return が掛からなくても、「毎度配列が new されて Pool の意味がない」以上のペナルティはないらしい。
new された配列の参照がなくなった時点で GC の対象にはなる。

@ufcpp-live
Copy link
Owner

var t = Task.Delay(1).ContinueWith(_ => 1);
string s = $"{x} / {await t} / {z}"; // これは string.Format になっちゃう。
var t = await Task.Delay(1).ContinueWith(_ => 1);
DefaultInterpolatedStringHandler s = $"{x} / {t} / {z}"; // これはそもそもコンパイルエラー。
var t = await Task.Delay(1).ContinueWith(_ => 1);
string s = $"{x} / {t} / {z}"; // これは大丈夫。AppendFormatted 出てる

@ufcpp-live
Copy link
Owner

// $"" はカルチャー依存
var x = 1234;
var y = 1.234;
var z = new DateOnly(2001, 2, 3);

Thread.CurrentThread.CurrentCulture = new CultureInfo("fr-fr");

// カルチャー依存を避けたければこれを使う。
var s = string.Create(
    CultureInfo.InvariantCulture,
    $"{x} / {y} / {z}");

Console.WriteLine(s);

@ufcpp-live
Copy link
Owner

//var s = string.Create(
//    CultureInfo.InvariantCulture,
//    $"{x} / {y} / {z}");
//
// ↓こう展開される

DefaultInterpolatedStringHandler h = new(6, 3, CultureInfo.InvariantCulture);
h.AppendFormatted(x);
h.AppendLiteral(" / ");
h.AppendFormatted(y);
h.AppendLiteral(" / ");
h.AppendFormatted(z);

// 展開、ここまで

var s = h.ToStringAndClear(); // string.Create の中はこれ1行。

Console.WriteLine(s);

@ufcpp-live
Copy link
Owner

//var s = string.Create(
//    CultureInfo.InvariantCulture,
//    stackalloc char[128],
//    $"{x} / {y} / {z}");
//
// ↓こう展開される

DefaultInterpolatedStringHandler h = new(
    6, 3,
    CultureInfo.InvariantCulture,
    stackalloc char[128]);

@ufcpp-live
Copy link
Owner

// ref がつかない struc なら間に await があっても平気
C.M($"{1} / {await Task.Delay(1).ContinueWith(_ => 2)} / {3}");

class C
{
    //public static void M(string _) => Console.WriteLine("string");
    public static void M(DummyHandler _) => Console.WriteLine("DummyHandler");
}

// これが $"" を受け取れる最低ラインのパターン。
[System.Runtime.CompilerServices.InterpolatedStringHandler]
public class DummyHandler

@ufcpp-live
Copy link
Owner

C.M("");

C.M($""); // string
C.M($"{""}"); // string

const string a = "aaa";
C.M($"{""} {a}"); // string

C.M($"{1}"); // DummyHandler

string b = "aaa";
C.M($"{b}"); // DummyHandler

int x = 1;
C.M($"{x}"); // DummyHandler

class C
{
    public static void M(string _) => Console.WriteLine("string");
    public static void M(DummyHandler _) => Console.WriteLine("DummyHandler");
}

@ufcpp-live
Copy link
Owner

using System.Runtime.CompilerServices;

var a = 1;

write<int>(new[] { "X", "Y", "Z" }, $"{1}{2}{a}");

// 素直にこうしろよという説はある。
//new Dictionary<string, object>
//{
//    ["X"] = 1,
//}

static void write<TValue>(
    string[] keys,
    [InterpolatedStringHandlerArgument("keys")]
    ParamsDictionaryHandler<TValue> handler)
{
    var dic = handler.Dictionary;
    foreach (var (key, value) in dic)
    {
        Console.WriteLine($"{key}: {value}");
    }
}

[InterpolatedStringHandler]
public struct ParamsDictionaryHandler<T>
{
    internal readonly Dictionary<string, T> Dictionary;
    private readonly string[] _keys;
    private int _i = 0;
    public ParamsDictionaryHandler(int _, int formattedCount, string[] keys)
    {
        _keys = keys;
        Dictionary = new(formattedCount);
    }

    public void AppendFormatted(T value)
    {
        Dictionary[_keys[_i]] = value;
        _i++;
    }
}

@ufcpp-live
Copy link
Owner

// M(string) をコメントアウトすると…
C.M(""); // ダメ。

// M(string) の有無で呼ばれる先が違う。
C.M($""); // OK。
C.M($"{"abc"}"); // OK。

class C
{
    //public static void M(string _) => Console.WriteLine("string");
    public static void M(DummyHandler _) => Console.WriteLine("DummyHandler");
}

@ufcpp-live
Copy link
Owner

C.M($""); // ダメ。
C.M($"{"abc"}"); // ダメ。

// キャスト挟めばOK。
C.M((DummyHandler)$""); // OK。
C.M((DummyHandler2)$"{"abc"}"); // OK。

// as とかはダメ。
C.M($"" as DummyHandler); // ダメ。
C.M((DummyHandler)""); // ダメ。

// IFormattable も同じようなもの。
var xx1 = (IFormattable)$""; // OK
var xx2 = $"" as IFormattable; // ダメ

class C
{
    // わざとオーバーロード解決できなくする。
    public static void M(DummyHandler _) => Console.WriteLine("DummyHandler");
    public static void M(DummyHandler2 _) => Console.WriteLine("DummyHandler2");
}

@ufcpp-live
Copy link
Owner

using System.Text;

var sb = new StringBuilder();
sb.Append($"{1}"); // AppendInterpolatedStringHandler が呼ばれる。

var tw = new TextWriter(); // TextWriter には同様のオーバーロードはなさげ…

@ufcpp-live
Copy link
Owner

// 完全なアロケーション0フォーマット。
Span<char> buffer = stackalloc char[128];
buffer.TryWrite($"{1}.{2}", out var charWritten);

@ufcpp-live
Copy link
Owner

true invariant culture string interpolation は多少作りたい…
ヤーポン、華氏、MM/dd/yyyy な国が invariant 騙るのやめろ。

@ufcpp-live
Copy link
Owner

Console.WriteLine(DateTime.Parse("平成35/1/1"));
Console.WriteLine(DateTime.Parse("昭和100/1/1"));
Console.WriteLine(DateTime.Parse("昭和2000/1/1"));
Console.WriteLine(DateTime.Parse("9999/1/1"));

// さすがにダメっぽい。
//Console.WriteLine(DateTime.Parse("平成元年1月1日"));

// FormatException
//Console.WriteLine(DateTime.Parse("昭和-1/1/1"));

// 以下の2つは範囲外だけど FormatException
//Console.WriteLine(DateTime.Parse("昭和9999/1/1"));
//Console.WriteLine(DateTime.Parse("10000/1/1"));

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants