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

Avoid OverflowException in Boolean.TryFormat #108572

Merged
merged 3 commits into from
Oct 15, 2024

Conversation

xtqqczze
Copy link
Contributor

@xtqqczze xtqqczze commented Oct 5, 2024

MemoryMarshal.AsBytes would throw unnecessarily when destination.Length exceeds 0x3FFFFFFF.

@dotnet-issue-labeler dotnet-issue-labeler bot added the needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners label Oct 5, 2024
@dotnet-policy-service dotnet-policy-service bot added the community-contribution Indicates that the PR has been added by a community member label Oct 5, 2024
@xtqqczze
Copy link
Contributor Author

xtqqczze commented Oct 5, 2024

@MihuBot

@xtqqczze
Copy link
Contributor Author

xtqqczze commented Oct 5, 2024

@MihuBot

@huoyaoyuan
Copy link
Member

This may get superseded by #108575

@EgorBo
Copy link
Member

EgorBo commented Oct 5, 2024

This may get superseded by #108575

I was just wondering about perf overhead from a more idiomatic code (since we already do the same for UTF8 formatter for bools, see here)

Surprisingly, the idiomatic way seems to be cheaper? 😐

@xtqqczze
Copy link
Contributor Author

xtqqczze commented Oct 5, 2024

@EgorBot -intel -arm64 -profiler

using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);

public class Benchmarks
{
    static bool[] _values = Enumerable.Range(0, 256).Select(i => i % 2 == 0).ToArray();
    static char[] _output = new char[4096];

    [Benchmark]
    public void WriteBools()
    {
        Span<char> output = _output;
        foreach (var value in _values)
        {
            if (value.TryFormat(output, out int written))
                output = output.Slice(written);
            else
                throw new InvalidOperationException();
        }

        Consume(output);
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    void Consume(Span<char> _){}
}

@EgorBo
Copy link
Member

EgorBo commented Oct 5, 2024

@xtqqczze feel free to just incorporate my change in your PR, but we may need to keep your version for Mono since it can't unroll Memmove.

@xtqqczze xtqqczze marked this pull request as draft October 5, 2024 20:39
@jkotas
Copy link
Member

jkotas commented Oct 5, 2024

just incorporate #108575 in your PR, but we may need to keep your version for Mono since it can't unroll Memmove.

bool formatting is very unlikely to be on a hot path in scenarios targeted by Mono. I think it would be acceptable to take regression for Mono to keep the code simple and safe.

@xtqqczze
Copy link
Contributor Author

xtqqczze commented Oct 5, 2024

Related: #77398

@xtqqczze
Copy link
Contributor Author

xtqqczze commented Oct 5, 2024

@EgorBot -mono -intel -arm64

using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);

public class Benchmarks
{
    static bool[] _values = Enumerable.Range(0, 256).Select(i => i % 2 == 0).ToArray();
    static char[] _output = new char[4096];

    [Benchmark]
    public void WriteBools()
    {
        Span<char> output = _output;
        foreach (var value in _values)
        {
            if (value.TryFormat(output, out int written))
                output = output.Slice(written);
            else
                throw new InvalidOperationException();
        }

        Consume(output);
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    void Consume(Span<char> _){}
}

@EgorBo
Copy link
Member

EgorBo commented Oct 5, 2024

bool formatting is very unlikely to be on a hot path in scenarios targeted by Mono. I think it would be acceptable to take regression for Mono to keep the code simple and safe.

Agree. In fact it improves Mono-jit (mini), but slightly regresses Interpreter.

@xtqqczze
Copy link
Contributor Author

xtqqczze commented Oct 5, 2024

@EgorBot -mono -intel -arm64 --envvars MONO_ENV_OPTIONS:--interpreter

using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);

public class Benchmarks
{
    static bool[] _values = Enumerable.Range(0, 256).Select(i => i % 2 == 0).ToArray();
    static char[] _output = new char[4096];

    [Benchmark]
    public void WriteBools()
    {
        Span<char> output = _output;
        foreach (var value in _values)
        {
            if (value.TryFormat(output, out int written))
                output = output.Slice(written);
            else
                throw new InvalidOperationException();
        }

        Consume(output);
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    void Consume(Span<char> _){}
}

@xtqqczze
Copy link
Contributor Author

xtqqczze commented Oct 5, 2024

bool formatting is very unlikely to be on a hot path in scenarios targeted by Mono. I think it would be acceptable to take regression for Mono to keep the code simple and safe.

@jkotas For monointerpreter, the ratio between #108572 and #108575 ratio is 0.60.

Benchmark results on AzureAmpere

BenchmarkDotNet v0.14.0, Ubuntu 22.04.5 LTS (Jammy Jellyfish)
AzureAmpere
  Job-QUXZYN : .NET 10.0.0 (42.42.42.42424) using MonoVM, Arm64 AOT
  Job-CRJMBB : .NET 10.0.0 (42.42.42.42424) using MonoVM, Arm64 AOT
EnvironmentVariables=MONO_ENV_OPTIONS=--interpreter
Method Toolchain Mean Error Ratio
WriteBools Main 22.75 μs 0.266 μs 1.00
WriteBools #108572 16.58 μs 0.329 μs 0.73
WriteBools Main 22.00 μs 0.440 μs 1.00
WriteBools #108575 26.90 μs 0.529 μs 1.22

@EgorBo
Copy link
Member

EgorBo commented Oct 5, 2024

For monointerpreter, the ratio between #108572 and #108575 ratio is 0.60.

I think 20% regression is fine here, even the best case is still a lot slower than the JIT (16μs vs ~200ns) , so it can be handled via an intrinsic in the interpreter if it really becomes a bottle-neck. A safer/easier-to-read code worth it.

@xtqqczze
Copy link
Contributor Author

xtqqczze commented Oct 6, 2024

@EgorBo Do you expect to merge #108576 soon?

@EgorBo
Copy link
Member

EgorBo commented Oct 6, 2024

@EgorBo Do you expect to merge #108576 soon?

You don't have to wait for it, it improves a tiny bit

@xtqqczze xtqqczze marked this pull request as ready for review October 6, 2024 19:23
@xtqqczze xtqqczze force-pushed the TryFormatOverflowException branch from 208d0fb to d1f0cc0 Compare October 6, 2024 19:33
`MemoryMarshal.AsBytes` would throw unnecessarily when `destination.Length` exceeds 0x3FFFFFFF.
@xtqqczze xtqqczze force-pushed the TryFormatOverflowException branch from d1f0cc0 to 917c458 Compare October 8, 2024 13:32
@xtqqczze
Copy link
Contributor Author

xtqqczze commented Oct 8, 2024

@EgorBot -intel -arm64 -profiler

using System.Runtime.CompilerServices;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Running;

BenchmarkSwitcher.FromAssembly(typeof(Program).Assembly).Run(args);

public class Benchmarks
{
    static bool[] _values = Enumerable.Range(0, 256).Select(i => i % 2 == 0).ToArray();
    static char[] _output = new char[4096];

    [Benchmark]
    public void WriteBools()
    {
        Span<char> output = _output;
        foreach (var value in _values)
        {
            if (value.TryFormat(output, out int written))
                output = output.Slice(written);
            else
                throw new InvalidOperationException();
        }

        Consume(output);
    }

    [MethodImpl(MethodImplOptions.NoInlining)]
    void Consume(Span<char> _){}
}

Copy link
Member

@EgorBo EgorBo left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM, I think it's fine to leave constants instead of .Length to avoid making mono even more slower, but I don't have a strong opinion. I think it should be a good idea to fold ldstr+length in Mono too (I once tried to do so in Mono via mono/mono#16898 but didn't finish)

@xtqqczze
Copy link
Contributor Author

LGTM, I think it's fine to leave constants instead of .Length to avoid making mono even more slower, but I don't have a strong opinion. I think it should be a good idea to fold ldstr+length in Mono too (I once tried to do so in Mono via mono/mono#16898 but didn't finish)

Issue opened: #108857

Copy link
Member

@jkotas jkotas left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@jkotas jkotas merged commit a174fd1 into dotnet:main Oct 15, 2024
143 of 146 checks passed
@github-actions github-actions bot locked and limited conversation to collaborators Nov 15, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
community-contribution Indicates that the PR has been added by a community member needs-area-label An area label is needed to ensure this gets routed to the appropriate area owners
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants