BenchmarkDotNet
BenchmarkDotNet copied to clipboard
Compilation fails with Literal of type double cannot be implicitly converted.
After updating to the latest .Net 9.0 SDK and updating Visual Studio we started having issues compiling the solutions. Since we had similar issues on our codebase, maybe it is related to the changes in overload resolution.
S:\Src\ravendb-60-git\bench\Micro.Benchmark\bin\Release\net9.0\e60f7001-ab2b-4959-8145-809b29eb272a\e60f7001-ab2b-4959-8145-809b29eb272a.notcs(17761,69): error CS0664: Literal of type double cannot be implicitly converted to type 'float'; use an 'F' suffix to create a literal of this type [S:\Src\ravendb-60-git\bench\Micro.Benchmark\bin\Release\net9.0\e60f7001-ab2b-4959-8145-809b29eb272a\BenchmarkDotNet.Autogenerated.csproj]
S:\Src\ravendb-60-git\bench\Micro.Benchmark\bin\Release\net9.0\e60f7001-ab2b-4959-8145-809b29eb272a\e60f7001-ab2b-4959-8145-809b29eb272a.notcs(16375,67): error CS0664: Literal of type double cannot be implicitly converted to type 'float'; use an 'F' suffix to create a literal of this type [S:\Src\ravendb-60-git\bench\Micro.Benchmark\bin\Release\net9.0\e60f7001-ab2b-4959-8145-809b29eb272a\BenchmarkDotNet.Autogenerated.csproj]
S:\Src\ravendb-60-git\bench\Micro.Benchmark\bin\Release\net9.0\e60f7001-ab2b-4959-8145-809b29eb272a\e60f7001-ab2b-4959-8145-809b29eb272a.notcs(17167,69): error CS0664: Literal of type double cannot be implicitly converted to type 'float'; use an 'F' suffix to create a literal of this type [S:\Src\ravendb-60-git\bench\Micro.Benchmark\bin\Release\net9.0\e60f7001-ab2b-4959-8145-809b29eb272a\BenchmarkDotNet.Autogenerated.csproj]
S:\Src\ravendb-60-git\bench\Micro.Benchmark\bin\Release\net9.0\e60f7001-ab2b-4959-8145-809b29eb272a\e60f7001-ab2b-4959-8145-809b29eb272a.notcs(16573,67): error CS0664: Literal of type double cannot be implicitly converted to type 'float'; use an 'F' suffix to create a literal of this type [S:\Src\ravendb-60-git\bench\Micro.Benchmark\bin\Release\net9.0\e60f7001-ab2b-4959-8145-809b29eb272a\BenchmarkDotNet.Autogenerated.csproj]
You can inspect the attached file. c926cbed-3cb6-4e22-80b1-08aa98602f89.zip
Can you please share your benchmark that caused this failure? Micro.Benchmark.Benchmarks.Parsing.FindEscapePositions
Sure. This is the benchmark
using System;
using System.Linq;
using BenchmarkDotNet.Analysers;
using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Environments;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Validators;
namespace Micro.Benchmark.Benchmarks.Parsing
{
//[HardwareCounters(HardwareCounter.CacheMisses, HardwareCounter.TotalIssues, HardwareCounter.BranchMispredictions, HardwareCounter.InstructionRetired )]
[DisassemblyDiagnoser]
[Config(typeof(FindEscapePositions.Config))]
public unsafe class FindEscapePositions
{
private class Config : ManualConfig
{
public Config()
{
AddJob(new Job(RunMode.Default)
{
Environment =
{
Runtime = CoreRuntime.Core90,
Platform = Platform.X64,
Jit = Jit.RyuJit
}
});
AddExporter(GetExporters().ToArray());
AddValidator(BaselineValidator.FailOnError);
AddValidator(JitOptimizationsValidator.FailOnError);
AddAnalyser(EnvironmentAnalyser.Default);
}
}
private const int MaxSize = 4096 * 64;
[Params(7, 16, 127, 128, 255, 256, 4096, MaxSize)]
public int Length { get; set; }
[Params(0.0, 0.05, 0.1, 0.5, 0.99, 1.0)]
public float NonAsciiProbability { get; set; }
public const int Operations = 100;
private readonly char[] _source = new char[MaxSize];
[GlobalSetup]
public void Setup()
{
var r = new Random();
for (int i = 0; i < MaxSize; i++)
{
if (r.NextSingle() < NonAsciiProbability)
{
_source[i] = (char)(r.Next(char.MaxValue - 256) + 256);
}
else
{
_source[i] = (char)r.Next(255);
}
}
}
[Benchmark(Baseline = true)]
public int Reference()
{
var count = 0;
var controlCount = 0;
for (int i = 0; i < _source.Length; i++)
{
var value = _source[i];
// PERF: We use the values directly because it is 5x faster than iterating over a constant array.
// 8 => '\b' => 0000 1000
// 9 => '\t' => 0000 1001
// 10 => '\n' => 0000 1010
// 12 => '\f' => 0000 1100
// 13 => '\r' => 0000 1101
// 34 => '"' => 0010 0010
// 92 => '\\' => 0101 1100
if (value == 92 || value == 34 || (value >= 8 && value <= 13 && value != 11))
{
count++;
continue;
}
if (value < 32)
{
controlCount++;
}
}
// we take 5 because that is the max number of bytes for variable size int
// plus 1 for the actual number of positions
// NOTE: this is used by FindEscapePositionsIn, change only if you also modify FindEscapePositionsIn
return (count + 1) * 5 + controlCount * 5;
}
private static ReadOnlySpan<int> EscapePositionsCountTable =>
[
0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
];
private static ReadOnlySpan<int> EscapePositionsControlTable =>
[
1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0, 1, 1,
1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,
0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0
];
[Benchmark]
public int TableBased()
{
var count = 0;
var controlCount = 0;
foreach (var value in _source)
{
if (value >= byte.MaxValue)
continue;
count += EscapePositionsCountTable[value];
controlCount += EscapePositionsControlTable[value];
}
// we take 5 because that is the max number of bytes for variable size int
// plus 1 for the actual number of positions
// NOTE: this is used by FindEscapePositionsIn, change only if you also modify FindEscapePositionsIn
return (count + 1) * 5 + controlCount * 5;
}
}
}
Does it work if you change it like this?
- [Params(0.0, 0.05, 0.1, 0.5, 0.99, 1.0)]
+ [Params(0.0f, 0.05f, 0.1f, 0.5f, 0.99f, 1.0f)]
public float NonAsciiProbability { get; set; }
I have been looking on the code before reading this, and yes that works. I was going to workaround it by just converting to double instead.
instance.Length = 262144;instance.NonAsciiProbability = 0.05d;
@timcassell What do you think of this solution? https://github.com/dotnet/BenchmarkDotNet/pull/2791
@timcassell What do you think of this solution? #2791
I don't like it, it subtly hide bugs. We should rather add a validator that ensures the values are assignable to the type.
@timcassell reverted previous solution. Added parameters type validation as requested
Closing as by-design. We report the error the compiler gives. Compiler behavior is too complex for our validators to try to catch this before compiling.