ufcpp-live / UfcppLiveAgenda

@ufcpp live streaming agenda
MIT License
24 stars 2 forks source link

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

Closed ufcpp closed 2 years ago

ufcpp commented 2 years ago

配信URL: https://youtu.be/P7-kykSvCmk

41 残作業その1。

https://twitter.com/ufcpp/status/1425773277834711046 https://gist.github.com/ufcpp/233d33b0220215bb5bdf2a10e8a434bf https://ufcpp.net/blog/2021/8/net6p7/#interpolated-string https://github.com/dotnet/csharplang/blob/main/proposals/csharp-10.0/improved-interpolated-strings.md

たぶん、これ単品で1配信になる。

ufcpp commented 2 years ago

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 commented 2 years ago
// 多分リーク(ArrayPool への Return が掛からない)
var ss = $"{x} {m() ?? throw new Exception()}";

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

ufcpp-live commented 2 years ago
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 commented 2 years ago
// $"" はカルチャー依存
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 commented 2 years ago
//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 commented 2 years ago
//var s = string.Create(
//    CultureInfo.InvariantCulture,
//    stackalloc char[128],
//    $"{x} / {y} / {z}");
//
// ↓こう展開される

DefaultInterpolatedStringHandler h = new(
    6, 3,
    CultureInfo.InvariantCulture,
    stackalloc char[128]);
ufcpp-live commented 2 years ago
// 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 commented 2 years ago
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 commented 2 years ago
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 commented 2 years ago
// 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 commented 2 years ago
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 commented 2 years ago
using System.Text;

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

var tw = new TextWriter(); // TextWriter には同様のオーバーロードはなさげ…
ufcpp-live commented 2 years ago
// 完全なアロケーション0フォーマット。
Span<char> buffer = stackalloc char[128];
buffer.TryWrite($"{1}.{2}", out var charWritten);
ufcpp-live commented 2 years ago

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

ufcpp-live commented 2 years ago
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"));