I accidentaly found performance reggresion using"mandelbrot algorithm" #80757

Description

@milen-denev

The place from where I copied the code: https://benchmarksgame-team.pages.debian.net/benchmarksgame/program/mandelbrot-csharpcore-1.html

Actual Code:

using BenchmarkDotNet.Attributes;
using BenchmarkDotNet.Jobs;
using BenchmarkDotNet.Running;
using System.Runtime.CompilerServices;
using System.Runtime.Intrinsics.X86;
using System.Runtime.Intrinsics;
namespace Test;
public static class Program
{
public static void Main() {
BenchmarkRunner.Run<Benchy>();
}
[MemoryDiagnoser(true)]
[SimpleJob(RuntimeMoniker.Net70)]
[SimpleJob(RuntimeMoniker.Net60, baseline: true)]
public class Benchy
{
[Benchmark]
public void Benchmark1()
{
MandelBrot.Default();
}
}
public class MandelBrot
{
// x86 version, AVX2
[MethodImpl(MethodImplOptions.AggressiveInlining)]
static byte Process8(double x, double y, double dx)
{
// initial x coords
var x01 = Vector256.Create(x + 0 * dx, x + 1 * dx, x + 2 * dx, x + 3 * dx);
var x02 = Vector256.Create(x + 4 * dx, x + 5 * dx, x + 6 * dx, x + 7 * dx);
// initial y coords
var y0 = Vector256.Create(y);
Vector256<double> x1 = x01, y1 = y0; // current iteration 1
Vector256<double> x2 = x02, y2 = y0; // current iteration 2
Vector256<double> four = Vector256.Create(4.0); // 4 in each slot var pass = 0;
// temp space, C# requires init.
Vector256<double>
x12 = Vector256<double>.Zero,
y12 = Vector256<double>.Zero,
x22 = Vector256<double>.Zero,
y22 = Vector256<double>.Zero;
// bit masks for results
uint res1 = 1, res2 = 1;
while (pass < 49 && (res1 != 0 || res2 != 0))
{
// do several between checks a time like other code
for (var p = 0; p < 7; ++p)
{
// unroll loop 2x to decrease register stalls
// squares x*x and y*y
x12 = Avx2.Multiply(x1, x1);
y12 = Avx2.Multiply(y1, y1);
x22 = Avx2.Multiply(x2, x2);
y22 = Avx2.Multiply(y2, y2);
// mixed products x*y
var xy1 = Avx2.Multiply(x1, y1);
var xy2 = Avx2.Multiply(x2, y2);
// diff of squares x*x - y*y
var ds1 = Avx2.Subtract(x12, y12);
var ds2 = Avx2.Subtract(x22, y22);
// 2*x*y
xy1 = Avx2.Add(xy1, xy1);
xy2 = Avx2.Add(xy2, xy2);
// next iters
y1 = Avx2.Add(xy1, y0);
y2 = Avx2.Add(xy2, y0);
x1 = Avx2.Add(ds1, x01);
x2 = Avx2.Add(ds2, x02);
}
pass += 7;
// numbers overflow, which gives an Infinity or NaN, which, // when compared N < 4, results in false, which is what we want
// sum of squares x*x + y*y, compare to 4 (escape mandelbrot)
var ss1 = Avx2.Add(x12, y12);
var ss2 = Avx2.Add(x22, y22);
// compare - puts all 0 in reg if false, else all 1 (=NaN bitwise)
// when each register is 0, then all points escaped, so exit
var cmp1 = Avx.Compare(ss1, four,
FloatComparisonMode.OrderedLessThanOrEqualNonSignaling);
var cmp2 = Avx.Compare(ss2, four,
FloatComparisonMode.OrderedLessThanOrEqualNonSignaling);
// take top bit from each byte
res1 = (uint)Avx2.MoveMask(Vector256.AsByte(cmp1));
res2 = (uint)Avx2.MoveMask(Vector256.AsByte(cmp2));
}
// can make a mask of bits in any order, which is the +7, +6, .., +1, +0
res1 &=
(1 << (0 + 7)) |
(1 << (8 + 6)) |
(1 << (16 + 5)) |
(1 << (24 + 4));
res2 &=
(1 << (0 + 3)) |
(1 << (8 + 2)) |
(1 << (16 + 1)) |
(1 << (24 + 0));
var res = res1 | res2;
res |= res >> 16;
res |= res >> 8;
return (byte)(res);
}
public static void MainNew()
{
var size = 200;
var lineLength = size >> 3;
var data = new byte[size * lineLength];
// step size
var delta = 2.0 / size; // (0.5 - (-1.5))/size;
Parallel.For(0, size, y =>
{
var yd = y * delta - 1;
for (var x = 0; x < lineLength; x++)
{
var xd = (x * 8) * delta - 1.5;
data[y * lineLength + x] = Process8(xd, yd, delta);
}
});
}
public static void Default()
{
MainNew();
}
}
}

Configuration

BenchmarkDotNet=v0.13.4, OS=Windows 11 (10.0.22621.1105)
Intel Core i5-10400 CPU 2.90GHz, 1 CPU, 12 logical and 6 physical cores
.NET SDK=7.0.102
[Host] : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
.NET 6.0 : .NET 6.0.13 (6.0.1322.58009), X64 RyuJIT AVX2
.NET 7.0 : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2

 <TargetFrameworks>net6.0;net7.0;</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PublishAot>false</PublishAot>
<ServerGarbageCollection>true</ServerGarbageCollection>
<TieredPGO>true</TieredPGO>
<TieredCompilationQuickJitForLoops>true</TieredCompilationQuickJitForLoops>
<Platforms>AnyCPU;x64</Platforms>

Regression?

The Regression is tested from .NET 6 -> .NET 7

Data

MethodJobRuntimeMeanErrorStdDevRatioRatioSDGen0AllocatedAlloc Ratio
Benchmark1.NET 6.0.NET 6.081.72 us1.496 us1.400 us1.000.000.12218.69 KB1.00
Benchmark1.NET 7.0.NET 7.0101.81 us0.699 us0.654 us1.250.02-8.53 KB0.98

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMItenet-performancePerformance related issue

Type

No type

Projects

No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all \u003cpre\u003e\u003ccode\u003e blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks"); } } catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); } })(); (function(){ try { var __m = "github.com"; var __re = new RegExp('^' + "github\\.com" + '
    Skip to content

    I accidentaly found performance reggresion using"mandelbrot algorithm" #80757

    Description

    @milen-denev

    The place from where I copied the code: https://benchmarksgame-team.pages.debian.net/benchmarksgame/program/mandelbrot-csharpcore-1.html

    Actual Code:

    using BenchmarkDotNet.Attributes;
    using BenchmarkDotNet.Jobs;
    using BenchmarkDotNet.Running;
    using System.Runtime.CompilerServices;
    using System.Runtime.Intrinsics.X86;
    using System.Runtime.Intrinsics;
    namespace Test;
    public static class Program
    {
    public static void Main() {
    BenchmarkRunner.Run<Benchy>();
    }
    [MemoryDiagnoser(true)]
    [SimpleJob(RuntimeMoniker.Net70)]
    [SimpleJob(RuntimeMoniker.Net60, baseline: true)]
    public class Benchy
    {
    [Benchmark]
    public void Benchmark1()
    {
    MandelBrot.Default();
    }
    }
    public class MandelBrot
    {
    // x86 version, AVX2
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    static byte Process8(double x, double y, double dx)
    {
    // initial x coords
    var x01 = Vector256.Create(x + 0 * dx, x + 1 * dx, x + 2 * dx, x + 3 * dx);
    var x02 = Vector256.Create(x + 4 * dx, x + 5 * dx, x + 6 * dx, x + 7 * dx);
    // initial y coords
    var y0 = Vector256.Create(y);
    Vector256<double> x1 = x01, y1 = y0; // current iteration 1
    Vector256<double> x2 = x02, y2 = y0; // current iteration 2
    Vector256<double> four = Vector256.Create(4.0); // 4 in each slot var pass = 0;
    // temp space, C# requires init.
    Vector256<double>
    x12 = Vector256<double>.Zero,
    y12 = Vector256<double>.Zero,
    x22 = Vector256<double>.Zero,
    y22 = Vector256<double>.Zero;
    // bit masks for results
    uint res1 = 1, res2 = 1;
    while (pass < 49 && (res1 != 0 || res2 != 0))
    {
    // do several between checks a time like other code
    for (var p = 0; p < 7; ++p)
    {
    // unroll loop 2x to decrease register stalls
    // squares x*x and y*y
    x12 = Avx2.Multiply(x1, x1);
    y12 = Avx2.Multiply(y1, y1);
    x22 = Avx2.Multiply(x2, x2);
    y22 = Avx2.Multiply(y2, y2);
    // mixed products x*y
    var xy1 = Avx2.Multiply(x1, y1);
    var xy2 = Avx2.Multiply(x2, y2);
    // diff of squares x*x - y*y
    var ds1 = Avx2.Subtract(x12, y12);
    var ds2 = Avx2.Subtract(x22, y22);
    // 2*x*y
    xy1 = Avx2.Add(xy1, xy1);
    xy2 = Avx2.Add(xy2, xy2);
    // next iters
    y1 = Avx2.Add(xy1, y0);
    y2 = Avx2.Add(xy2, y0);
    x1 = Avx2.Add(ds1, x01);
    x2 = Avx2.Add(ds2, x02);
    }
    pass += 7;
    // numbers overflow, which gives an Infinity or NaN, which, // when compared N < 4, results in false, which is what we want
    // sum of squares x*x + y*y, compare to 4 (escape mandelbrot)
    var ss1 = Avx2.Add(x12, y12);
    var ss2 = Avx2.Add(x22, y22);
    // compare - puts all 0 in reg if false, else all 1 (=NaN bitwise)
    // when each register is 0, then all points escaped, so exit
    var cmp1 = Avx.Compare(ss1, four,
    FloatComparisonMode.OrderedLessThanOrEqualNonSignaling);
    var cmp2 = Avx.Compare(ss2, four,
    FloatComparisonMode.OrderedLessThanOrEqualNonSignaling);
    // take top bit from each byte
    res1 = (uint)Avx2.MoveMask(Vector256.AsByte(cmp1));
    res2 = (uint)Avx2.MoveMask(Vector256.AsByte(cmp2));
    }
    // can make a mask of bits in any order, which is the +7, +6, .., +1, +0
    res1 &=
    (1 << (0 + 7)) |
    (1 << (8 + 6)) |
    (1 << (16 + 5)) |
    (1 << (24 + 4));
    res2 &=
    (1 << (0 + 3)) |
    (1 << (8 + 2)) |
    (1 << (16 + 1)) |
    (1 << (24 + 0));
    var res = res1 | res2;
    res |= res >> 16;
    res |= res >> 8;
    return (byte)(res);
    }
    public static void MainNew()
    {
    var size = 200;
    var lineLength = size >> 3;
    var data = new byte[size * lineLength];
    // step size
    var delta = 2.0 / size; // (0.5 - (-1.5))/size;
    Parallel.For(0, size, y =>
    {
    var yd = y * delta - 1;
    for (var x = 0; x < lineLength; x++)
    {
    var xd = (x * 8) * delta - 1.5;
    data[y * lineLength + x] = Process8(xd, yd, delta);
    }
    });
    }
    public static void Default()
    {
    MainNew();
    }
    }
    }
    

    Configuration

    BenchmarkDotNet=v0.13.4, OS=Windows 11 (10.0.22621.1105)
    Intel Core i5-10400 CPU 2.90GHz, 1 CPU, 12 logical and 6 physical cores
    .NET SDK=7.0.102
    [Host] : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
    .NET 6.0 : .NET 6.0.13 (6.0.1322.58009), X64 RyuJIT AVX2
    .NET 7.0 : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2

     <TargetFrameworks>net6.0;net7.0;</TargetFrameworks>
    <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
    <PublishAot>false</PublishAot>
    <ServerGarbageCollection>true</ServerGarbageCollection>
    <TieredPGO>true</TieredPGO>
    <TieredCompilationQuickJitForLoops>true</TieredCompilationQuickJitForLoops>
    <Platforms>AnyCPU;x64</Platforms>
    

    Regression?

    The Regression is tested from .NET 6 -> .NET 7

    Data

    MethodJobRuntimeMeanErrorStdDevRatioRatioSDGen0AllocatedAlloc Ratio
    Benchmark1.NET 6.0.NET 6.081.72 us1.496 us1.400 us1.000.000.12218.69 KB1.00
    Benchmark1.NET 7.0.NET 7.0101.81 us0.699 us0.654 us1.250.02-8.53 KB0.98

    Activity

    Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

    Metadata

    Metadata

    Assignees

    Labels

    area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMItenet-performancePerformance related issue

    Type

    No type

    Projects

    No projects

      Milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
      Skip to content

      I accidentaly found performance reggresion using"mandelbrot algorithm" #80757

      Description

      @milen-denev

      The place from where I copied the code: https://benchmarksgame-team.pages.debian.net/benchmarksgame/program/mandelbrot-csharpcore-1.html

      Actual Code:

      using BenchmarkDotNet.Attributes;
      using BenchmarkDotNet.Jobs;
      using BenchmarkDotNet.Running;
      using System.Runtime.CompilerServices;
      using System.Runtime.Intrinsics.X86;
      using System.Runtime.Intrinsics;
      namespace Test;
      public static class Program
      {
      public static void Main() {
      BenchmarkRunner.Run<Benchy>();
      }
      [MemoryDiagnoser(true)]
      [SimpleJob(RuntimeMoniker.Net70)]
      [SimpleJob(RuntimeMoniker.Net60, baseline: true)]
      public class Benchy
      {
      [Benchmark]
      public void Benchmark1()
      {
      MandelBrot.Default();
      }
      }
      public class MandelBrot
      {
      // x86 version, AVX2
      [MethodImpl(MethodImplOptions.AggressiveInlining)]
      static byte Process8(double x, double y, double dx)
      {
      // initial x coords
      var x01 = Vector256.Create(x + 0 * dx, x + 1 * dx, x + 2 * dx, x + 3 * dx);
      var x02 = Vector256.Create(x + 4 * dx, x + 5 * dx, x + 6 * dx, x + 7 * dx);
      // initial y coords
      var y0 = Vector256.Create(y);
      Vector256<double> x1 = x01, y1 = y0; // current iteration 1
      Vector256<double> x2 = x02, y2 = y0; // current iteration 2
      Vector256<double> four = Vector256.Create(4.0); // 4 in each slot var pass = 0;
      // temp space, C# requires init.
      Vector256<double>
      x12 = Vector256<double>.Zero,
      y12 = Vector256<double>.Zero,
      x22 = Vector256<double>.Zero,
      y22 = Vector256<double>.Zero;
      // bit masks for results
      uint res1 = 1, res2 = 1;
      while (pass < 49 && (res1 != 0 || res2 != 0))
      {
      // do several between checks a time like other code
      for (var p = 0; p < 7; ++p)
      {
      // unroll loop 2x to decrease register stalls
      // squares x*x and y*y
      x12 = Avx2.Multiply(x1, x1);
      y12 = Avx2.Multiply(y1, y1);
      x22 = Avx2.Multiply(x2, x2);
      y22 = Avx2.Multiply(y2, y2);
      // mixed products x*y
      var xy1 = Avx2.Multiply(x1, y1);
      var xy2 = Avx2.Multiply(x2, y2);
      // diff of squares x*x - y*y
      var ds1 = Avx2.Subtract(x12, y12);
      var ds2 = Avx2.Subtract(x22, y22);
      // 2*x*y
      xy1 = Avx2.Add(xy1, xy1);
      xy2 = Avx2.Add(xy2, xy2);
      // next iters
      y1 = Avx2.Add(xy1, y0);
      y2 = Avx2.Add(xy2, y0);
      x1 = Avx2.Add(ds1, x01);
      x2 = Avx2.Add(ds2, x02);
      }
      pass += 7;
      // numbers overflow, which gives an Infinity or NaN, which, // when compared N < 4, results in false, which is what we want
      // sum of squares x*x + y*y, compare to 4 (escape mandelbrot)
      var ss1 = Avx2.Add(x12, y12);
      var ss2 = Avx2.Add(x22, y22);
      // compare - puts all 0 in reg if false, else all 1 (=NaN bitwise)
      // when each register is 0, then all points escaped, so exit
      var cmp1 = Avx.Compare(ss1, four,
      FloatComparisonMode.OrderedLessThanOrEqualNonSignaling);
      var cmp2 = Avx.Compare(ss2, four,
      FloatComparisonMode.OrderedLessThanOrEqualNonSignaling);
      // take top bit from each byte
      res1 = (uint)Avx2.MoveMask(Vector256.AsByte(cmp1));
      res2 = (uint)Avx2.MoveMask(Vector256.AsByte(cmp2));
      }
      // can make a mask of bits in any order, which is the +7, +6, .., +1, +0
      res1 &=
      (1 << (0 + 7)) |
      (1 << (8 + 6)) |
      (1 << (16 + 5)) |
      (1 << (24 + 4));
      res2 &=
      (1 << (0 + 3)) |
      (1 << (8 + 2)) |
      (1 << (16 + 1)) |
      (1 << (24 + 0));
      var res = res1 | res2;
      res |= res >> 16;
      res |= res >> 8;
      return (byte)(res);
      }
      public static void MainNew()
      {
      var size = 200;
      var lineLength = size >> 3;
      var data = new byte[size * lineLength];
      // step size
      var delta = 2.0 / size; // (0.5 - (-1.5))/size;
      Parallel.For(0, size, y =>
      {
      var yd = y * delta - 1;
      for (var x = 0; x < lineLength; x++)
      {
      var xd = (x * 8) * delta - 1.5;
      data[y * lineLength + x] = Process8(xd, yd, delta);
      }
      });
      }
      public static void Default()
      {
      MainNew();
      }
      }
      }
      

      Configuration

      BenchmarkDotNet=v0.13.4, OS=Windows 11 (10.0.22621.1105)
      Intel Core i5-10400 CPU 2.90GHz, 1 CPU, 12 logical and 6 physical cores
      .NET SDK=7.0.102
      [Host] : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
      .NET 6.0 : .NET 6.0.13 (6.0.1322.58009), X64 RyuJIT AVX2
      .NET 7.0 : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2

       <TargetFrameworks>net6.0;net7.0;</TargetFrameworks>
      <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
      <PublishAot>false</PublishAot>
      <ServerGarbageCollection>true</ServerGarbageCollection>
      <TieredPGO>true</TieredPGO>
      <TieredCompilationQuickJitForLoops>true</TieredCompilationQuickJitForLoops>
      <Platforms>AnyCPU;x64</Platforms>
      

      Regression?

      The Regression is tested from .NET 6 -> .NET 7

      Data

      MethodJobRuntimeMeanErrorStdDevRatioRatioSDGen0AllocatedAlloc Ratio
      Benchmark1.NET 6.0.NET 6.081.72 us1.496 us1.400 us1.000.000.12218.69 KB1.00
      Benchmark1.NET 7.0.NET 7.0101.81 us0.699 us0.654 us1.250.02-8.53 KB0.98

      Activity

      Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

      Metadata

      Metadata

      Assignees

      Labels

      area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMItenet-performancePerformance related issue

      Type

      No type

      Projects

      No projects

        Milestone

        Relationships

        None yet

        Development

        No branches or pull requests

        Issue actions

        , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length \u003e 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
        Skip to content

        I accidentaly found performance reggresion using"mandelbrot algorithm" #80757

        Description

        @milen-denev

        The place from where I copied the code: https://benchmarksgame-team.pages.debian.net/benchmarksgame/program/mandelbrot-csharpcore-1.html

        Actual Code:

        using BenchmarkDotNet.Attributes;
        using BenchmarkDotNet.Jobs;
        using BenchmarkDotNet.Running;
        using System.Runtime.CompilerServices;
        using System.Runtime.Intrinsics.X86;
        using System.Runtime.Intrinsics;
        namespace Test;
        public static class Program
        {
        public static void Main() {
        BenchmarkRunner.Run<Benchy>();
        }
        [MemoryDiagnoser(true)]
        [SimpleJob(RuntimeMoniker.Net70)]
        [SimpleJob(RuntimeMoniker.Net60, baseline: true)]
        public class Benchy
        {
        [Benchmark]
        public void Benchmark1()
        {
        MandelBrot.Default();
        }
        }
        public class MandelBrot
        {
        // x86 version, AVX2
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        static byte Process8(double x, double y, double dx)
        {
        // initial x coords
        var x01 = Vector256.Create(x + 0 * dx, x + 1 * dx, x + 2 * dx, x + 3 * dx);
        var x02 = Vector256.Create(x + 4 * dx, x + 5 * dx, x + 6 * dx, x + 7 * dx);
        // initial y coords
        var y0 = Vector256.Create(y);
        Vector256<double> x1 = x01, y1 = y0; // current iteration 1
        Vector256<double> x2 = x02, y2 = y0; // current iteration 2
        Vector256<double> four = Vector256.Create(4.0); // 4 in each slot var pass = 0;
        // temp space, C# requires init.
        Vector256<double>
        x12 = Vector256<double>.Zero,
        y12 = Vector256<double>.Zero,
        x22 = Vector256<double>.Zero,
        y22 = Vector256<double>.Zero;
        // bit masks for results
        uint res1 = 1, res2 = 1;
        while (pass < 49 && (res1 != 0 || res2 != 0))
        {
        // do several between checks a time like other code
        for (var p = 0; p < 7; ++p)
        {
        // unroll loop 2x to decrease register stalls
        // squares x*x and y*y
        x12 = Avx2.Multiply(x1, x1);
        y12 = Avx2.Multiply(y1, y1);
        x22 = Avx2.Multiply(x2, x2);
        y22 = Avx2.Multiply(y2, y2);
        // mixed products x*y
        var xy1 = Avx2.Multiply(x1, y1);
        var xy2 = Avx2.Multiply(x2, y2);
        // diff of squares x*x - y*y
        var ds1 = Avx2.Subtract(x12, y12);
        var ds2 = Avx2.Subtract(x22, y22);
        // 2*x*y
        xy1 = Avx2.Add(xy1, xy1);
        xy2 = Avx2.Add(xy2, xy2);
        // next iters
        y1 = Avx2.Add(xy1, y0);
        y2 = Avx2.Add(xy2, y0);
        x1 = Avx2.Add(ds1, x01);
        x2 = Avx2.Add(ds2, x02);
        }
        pass += 7;
        // numbers overflow, which gives an Infinity or NaN, which, // when compared N < 4, results in false, which is what we want
        // sum of squares x*x + y*y, compare to 4 (escape mandelbrot)
        var ss1 = Avx2.Add(x12, y12);
        var ss2 = Avx2.Add(x22, y22);
        // compare - puts all 0 in reg if false, else all 1 (=NaN bitwise)
        // when each register is 0, then all points escaped, so exit
        var cmp1 = Avx.Compare(ss1, four,
        FloatComparisonMode.OrderedLessThanOrEqualNonSignaling);
        var cmp2 = Avx.Compare(ss2, four,
        FloatComparisonMode.OrderedLessThanOrEqualNonSignaling);
        // take top bit from each byte
        res1 = (uint)Avx2.MoveMask(Vector256.AsByte(cmp1));
        res2 = (uint)Avx2.MoveMask(Vector256.AsByte(cmp2));
        }
        // can make a mask of bits in any order, which is the +7, +6, .., +1, +0
        res1 &=
        (1 << (0 + 7)) |
        (1 << (8 + 6)) |
        (1 << (16 + 5)) |
        (1 << (24 + 4));
        res2 &=
        (1 << (0 + 3)) |
        (1 << (8 + 2)) |
        (1 << (16 + 1)) |
        (1 << (24 + 0));
        var res = res1 | res2;
        res |= res >> 16;
        res |= res >> 8;
        return (byte)(res);
        }
        public static void MainNew()
        {
        var size = 200;
        var lineLength = size >> 3;
        var data = new byte[size * lineLength];
        // step size
        var delta = 2.0 / size; // (0.5 - (-1.5))/size;
        Parallel.For(0, size, y =>
        {
        var yd = y * delta - 1;
        for (var x = 0; x < lineLength; x++)
        {
        var xd = (x * 8) * delta - 1.5;
        data[y * lineLength + x] = Process8(xd, yd, delta);
        }
        });
        }
        public static void Default()
        {
        MainNew();
        }
        }
        }
        

        Configuration

        BenchmarkDotNet=v0.13.4, OS=Windows 11 (10.0.22621.1105)
        Intel Core i5-10400 CPU 2.90GHz, 1 CPU, 12 logical and 6 physical cores
        .NET SDK=7.0.102
        [Host] : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
        .NET 6.0 : .NET 6.0.13 (6.0.1322.58009), X64 RyuJIT AVX2
        .NET 7.0 : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2

         <TargetFrameworks>net6.0;net7.0;</TargetFrameworks>
        <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
        <PublishAot>false</PublishAot>
        <ServerGarbageCollection>true</ServerGarbageCollection>
        <TieredPGO>true</TieredPGO>
        <TieredCompilationQuickJitForLoops>true</TieredCompilationQuickJitForLoops>
        <Platforms>AnyCPU;x64</Platforms>
        

        Regression?

        The Regression is tested from .NET 6 -> .NET 7

        Data

        MethodJobRuntimeMeanErrorStdDevRatioRatioSDGen0AllocatedAlloc Ratio
        Benchmark1.NET 6.0.NET 6.081.72 us1.496 us1.400 us1.000.000.12218.69 KB1.00
        Benchmark1.NET 7.0.NET 7.0101.81 us0.699 us0.654 us1.250.02-8.53 KB0.98

        Activity

        Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

        Metadata

        Metadata

        Assignees

        Labels

        area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMItenet-performancePerformance related issue

        Type

        No type

        Projects

        No projects

          Milestone

          Relationships

          None yet

          Development

          No branches or pull requests

          Issue actions

          , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
          Skip to content

          I accidentaly found performance reggresion using"mandelbrot algorithm" #80757

          Description

          @milen-denev

          The place from where I copied the code: https://benchmarksgame-team.pages.debian.net/benchmarksgame/program/mandelbrot-csharpcore-1.html

          Actual Code:

          using BenchmarkDotNet.Attributes;
          using BenchmarkDotNet.Jobs;
          using BenchmarkDotNet.Running;
          using System.Runtime.CompilerServices;
          using System.Runtime.Intrinsics.X86;
          using System.Runtime.Intrinsics;
          namespace Test;
          public static class Program
          {
          public static void Main() {
          BenchmarkRunner.Run<Benchy>();
          }
          [MemoryDiagnoser(true)]
          [SimpleJob(RuntimeMoniker.Net70)]
          [SimpleJob(RuntimeMoniker.Net60, baseline: true)]
          public class Benchy
          {
          [Benchmark]
          public void Benchmark1()
          {
          MandelBrot.Default();
          }
          }
          public class MandelBrot
          {
          // x86 version, AVX2
          [MethodImpl(MethodImplOptions.AggressiveInlining)]
          static byte Process8(double x, double y, double dx)
          {
          // initial x coords
          var x01 = Vector256.Create(x + 0 * dx, x + 1 * dx, x + 2 * dx, x + 3 * dx);
          var x02 = Vector256.Create(x + 4 * dx, x + 5 * dx, x + 6 * dx, x + 7 * dx);
          // initial y coords
          var y0 = Vector256.Create(y);
          Vector256<double> x1 = x01, y1 = y0; // current iteration 1
          Vector256<double> x2 = x02, y2 = y0; // current iteration 2
          Vector256<double> four = Vector256.Create(4.0); // 4 in each slot var pass = 0;
          // temp space, C# requires init.
          Vector256<double>
          x12 = Vector256<double>.Zero,
          y12 = Vector256<double>.Zero,
          x22 = Vector256<double>.Zero,
          y22 = Vector256<double>.Zero;
          // bit masks for results
          uint res1 = 1, res2 = 1;
          while (pass < 49 && (res1 != 0 || res2 != 0))
          {
          // do several between checks a time like other code
          for (var p = 0; p < 7; ++p)
          {
          // unroll loop 2x to decrease register stalls
          // squares x*x and y*y
          x12 = Avx2.Multiply(x1, x1);
          y12 = Avx2.Multiply(y1, y1);
          x22 = Avx2.Multiply(x2, x2);
          y22 = Avx2.Multiply(y2, y2);
          // mixed products x*y
          var xy1 = Avx2.Multiply(x1, y1);
          var xy2 = Avx2.Multiply(x2, y2);
          // diff of squares x*x - y*y
          var ds1 = Avx2.Subtract(x12, y12);
          var ds2 = Avx2.Subtract(x22, y22);
          // 2*x*y
          xy1 = Avx2.Add(xy1, xy1);
          xy2 = Avx2.Add(xy2, xy2);
          // next iters
          y1 = Avx2.Add(xy1, y0);
          y2 = Avx2.Add(xy2, y0);
          x1 = Avx2.Add(ds1, x01);
          x2 = Avx2.Add(ds2, x02);
          }
          pass += 7;
          // numbers overflow, which gives an Infinity or NaN, which, // when compared N < 4, results in false, which is what we want
          // sum of squares x*x + y*y, compare to 4 (escape mandelbrot)
          var ss1 = Avx2.Add(x12, y12);
          var ss2 = Avx2.Add(x22, y22);
          // compare - puts all 0 in reg if false, else all 1 (=NaN bitwise)
          // when each register is 0, then all points escaped, so exit
          var cmp1 = Avx.Compare(ss1, four,
          FloatComparisonMode.OrderedLessThanOrEqualNonSignaling);
          var cmp2 = Avx.Compare(ss2, four,
          FloatComparisonMode.OrderedLessThanOrEqualNonSignaling);
          // take top bit from each byte
          res1 = (uint)Avx2.MoveMask(Vector256.AsByte(cmp1));
          res2 = (uint)Avx2.MoveMask(Vector256.AsByte(cmp2));
          }
          // can make a mask of bits in any order, which is the +7, +6, .., +1, +0
          res1 &=
          (1 << (0 + 7)) |
          (1 << (8 + 6)) |
          (1 << (16 + 5)) |
          (1 << (24 + 4));
          res2 &=
          (1 << (0 + 3)) |
          (1 << (8 + 2)) |
          (1 << (16 + 1)) |
          (1 << (24 + 0));
          var res = res1 | res2;
          res |= res >> 16;
          res |= res >> 8;
          return (byte)(res);
          }
          public static void MainNew()
          {
          var size = 200;
          var lineLength = size >> 3;
          var data = new byte[size * lineLength];
          // step size
          var delta = 2.0 / size; // (0.5 - (-1.5))/size;
          Parallel.For(0, size, y =>
          {
          var yd = y * delta - 1;
          for (var x = 0; x < lineLength; x++)
          {
          var xd = (x * 8) * delta - 1.5;
          data[y * lineLength + x] = Process8(xd, yd, delta);
          }
          });
          }
          public static void Default()
          {
          MainNew();
          }
          }
          }
          

          Configuration

          BenchmarkDotNet=v0.13.4, OS=Windows 11 (10.0.22621.1105)
          Intel Core i5-10400 CPU 2.90GHz, 1 CPU, 12 logical and 6 physical cores
          .NET SDK=7.0.102
          [Host] : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
          .NET 6.0 : .NET 6.0.13 (6.0.1322.58009), X64 RyuJIT AVX2
          .NET 7.0 : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2

           <TargetFrameworks>net6.0;net7.0;</TargetFrameworks>
          <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
          <PublishAot>false</PublishAot>
          <ServerGarbageCollection>true</ServerGarbageCollection>
          <TieredPGO>true</TieredPGO>
          <TieredCompilationQuickJitForLoops>true</TieredCompilationQuickJitForLoops>
          <Platforms>AnyCPU;x64</Platforms>
          

          Regression?

          The Regression is tested from .NET 6 -> .NET 7

          Data

          MethodJobRuntimeMeanErrorStdDevRatioRatioSDGen0AllocatedAlloc Ratio
          Benchmark1.NET 6.0.NET 6.081.72 us1.496 us1.400 us1.000.000.12218.69 KB1.00
          Benchmark1.NET 7.0.NET 7.0101.81 us0.699 us0.654 us1.250.02-8.53 KB0.98

          Activity

          Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

          Metadata

          Metadata

          Assignees

          Labels

          area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMItenet-performancePerformance related issue

          Type

          No type

          Projects

          No projects

            Milestone

            Relationships

            None yet

            Development

            No branches or pull requests

            Issue actions

            , 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); })();
            Skip to content

            I accidentaly found performance reggresion using"mandelbrot algorithm" #80757

            Description

            @milen-denev

            The place from where I copied the code: https://benchmarksgame-team.pages.debian.net/benchmarksgame/program/mandelbrot-csharpcore-1.html

            Actual Code:

            using BenchmarkDotNet.Attributes;
            using BenchmarkDotNet.Jobs;
            using BenchmarkDotNet.Running;
            using System.Runtime.CompilerServices;
            using System.Runtime.Intrinsics.X86;
            using System.Runtime.Intrinsics;
            namespace Test;
            public static class Program
            {
            public static void Main() {
            BenchmarkRunner.Run<Benchy>();
            }
            [MemoryDiagnoser(true)]
            [SimpleJob(RuntimeMoniker.Net70)]
            [SimpleJob(RuntimeMoniker.Net60, baseline: true)]
            public class Benchy
            {
            [Benchmark]
            public void Benchmark1()
            {
            MandelBrot.Default();
            }
            }
            public class MandelBrot
            {
            // x86 version, AVX2
            [MethodImpl(MethodImplOptions.AggressiveInlining)]
            static byte Process8(double x, double y, double dx)
            {
            // initial x coords
            var x01 = Vector256.Create(x + 0 * dx, x + 1 * dx, x + 2 * dx, x + 3 * dx);
            var x02 = Vector256.Create(x + 4 * dx, x + 5 * dx, x + 6 * dx, x + 7 * dx);
            // initial y coords
            var y0 = Vector256.Create(y);
            Vector256<double> x1 = x01, y1 = y0; // current iteration 1
            Vector256<double> x2 = x02, y2 = y0; // current iteration 2
            Vector256<double> four = Vector256.Create(4.0); // 4 in each slot var pass = 0;
            // temp space, C# requires init.
            Vector256<double>
            x12 = Vector256<double>.Zero,
            y12 = Vector256<double>.Zero,
            x22 = Vector256<double>.Zero,
            y22 = Vector256<double>.Zero;
            // bit masks for results
            uint res1 = 1, res2 = 1;
            while (pass < 49 && (res1 != 0 || res2 != 0))
            {
            // do several between checks a time like other code
            for (var p = 0; p < 7; ++p)
            {
            // unroll loop 2x to decrease register stalls
            // squares x*x and y*y
            x12 = Avx2.Multiply(x1, x1);
            y12 = Avx2.Multiply(y1, y1);
            x22 = Avx2.Multiply(x2, x2);
            y22 = Avx2.Multiply(y2, y2);
            // mixed products x*y
            var xy1 = Avx2.Multiply(x1, y1);
            var xy2 = Avx2.Multiply(x2, y2);
            // diff of squares x*x - y*y
            var ds1 = Avx2.Subtract(x12, y12);
            var ds2 = Avx2.Subtract(x22, y22);
            // 2*x*y
            xy1 = Avx2.Add(xy1, xy1);
            xy2 = Avx2.Add(xy2, xy2);
            // next iters
            y1 = Avx2.Add(xy1, y0);
            y2 = Avx2.Add(xy2, y0);
            x1 = Avx2.Add(ds1, x01);
            x2 = Avx2.Add(ds2, x02);
            }
            pass += 7;
            // numbers overflow, which gives an Infinity or NaN, which, // when compared N < 4, results in false, which is what we want
            // sum of squares x*x + y*y, compare to 4 (escape mandelbrot)
            var ss1 = Avx2.Add(x12, y12);
            var ss2 = Avx2.Add(x22, y22);
            // compare - puts all 0 in reg if false, else all 1 (=NaN bitwise)
            // when each register is 0, then all points escaped, so exit
            var cmp1 = Avx.Compare(ss1, four,
            FloatComparisonMode.OrderedLessThanOrEqualNonSignaling);
            var cmp2 = Avx.Compare(ss2, four,
            FloatComparisonMode.OrderedLessThanOrEqualNonSignaling);
            // take top bit from each byte
            res1 = (uint)Avx2.MoveMask(Vector256.AsByte(cmp1));
            res2 = (uint)Avx2.MoveMask(Vector256.AsByte(cmp2));
            }
            // can make a mask of bits in any order, which is the +7, +6, .., +1, +0
            res1 &=
            (1 << (0 + 7)) |
            (1 << (8 + 6)) |
            (1 << (16 + 5)) |
            (1 << (24 + 4));
            res2 &=
            (1 << (0 + 3)) |
            (1 << (8 + 2)) |
            (1 << (16 + 1)) |
            (1 << (24 + 0));
            var res = res1 | res2;
            res |= res >> 16;
            res |= res >> 8;
            return (byte)(res);
            }
            public static void MainNew()
            {
            var size = 200;
            var lineLength = size >> 3;
            var data = new byte[size * lineLength];
            // step size
            var delta = 2.0 / size; // (0.5 - (-1.5))/size;
            Parallel.For(0, size, y =>
            {
            var yd = y * delta - 1;
            for (var x = 0; x < lineLength; x++)
            {
            var xd = (x * 8) * delta - 1.5;
            data[y * lineLength + x] = Process8(xd, yd, delta);
            }
            });
            }
            public static void Default()
            {
            MainNew();
            }
            }
            }
            

            Configuration

            BenchmarkDotNet=v0.13.4, OS=Windows 11 (10.0.22621.1105)
            Intel Core i5-10400 CPU 2.90GHz, 1 CPU, 12 logical and 6 physical cores
            .NET SDK=7.0.102
            [Host] : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2
            .NET 6.0 : .NET 6.0.13 (6.0.1322.58009), X64 RyuJIT AVX2
            .NET 7.0 : .NET 7.0.2 (7.0.222.60605), X64 RyuJIT AVX2

             <TargetFrameworks>net6.0;net7.0;</TargetFrameworks>
            <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
            <PublishAot>false</PublishAot>
            <ServerGarbageCollection>true</ServerGarbageCollection>
            <TieredPGO>true</TieredPGO>
            <TieredCompilationQuickJitForLoops>true</TieredCompilationQuickJitForLoops>
            <Platforms>AnyCPU;x64</Platforms>
            

            Regression?

            The Regression is tested from .NET 6 -> .NET 7

            Data

            MethodJobRuntimeMeanErrorStdDevRatioRatioSDGen0AllocatedAlloc Ratio
            Benchmark1.NET 6.0.NET 6.081.72 us1.496 us1.400 us1.000.000.12218.69 KB1.00
            Benchmark1.NET 7.0.NET 7.0101.81 us0.699 us0.654 us1.250.02-8.53 KB0.98

            Activity

            Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

            Metadata

            Metadata

            Assignees

            Labels

            area-CodeGen-coreclrCLR JIT compiler in src/coreclr/src/jit and related components such as SuperPMItenet-performancePerformance related issue

            Type

            No type

            Projects

            No projects

              Milestone

              Relationships

              None yet

              Development

              No branches or pull requests

              Issue actions