Skip to content
Open
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ All results are obtained from the same toaster, with the same load, so compariso
Tested frameworks:
- [Arch](https://github.com/genaray/Arch)
- [DefaultEcs](https://github.com/Doraku/DefaultEcs)
- [Fennecs](https://github.com/thygrrr/fennecs)
- [**fenn**ecs](https://fennecs.tech)
- [Flecs.Net](https://github.com/BeanCheeseBurrito/Flecs.NET)
- [Friflo.Engine.ECS](https://github.com/friflo/Friflo.Json.Fliox/blob/main/Engine/README.md)
- [Leopotam.Ecs](https://github.com/Leopotam/ecs) using what I believe is a nuget package not made by the actual author and compiled in debug...
Expand Down
96 changes: 94 additions & 2 deletions source/Ecs.CSharp.Benchmark/Categories.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
namespace Ecs.CSharp.Benchmark
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Filters;
using BenchmarkDotNet.Running;

namespace Ecs.CSharp.Benchmark
{
/// <summary>
/// Prefixes / ECS package names for benchmarks, used as BenchMarkDotNet categories.
/// </summary>
internal static class Categories
{
public const string Arch = "Arch";
Expand All @@ -14,10 +23,93 @@ internal static class Categories
public const string SveltoECS = "Svelto.ECS";
public const string Morpeh = "Morpeh";
public const string FlecsNet = "FlecsNet";
public const string Fennecs = "Fennecs";
public const string Fennecs = "fennecs";
public const string TinyEcs = "TinyEcs";

public const string CreateEntity = "CreateEntity";
public const string System = "System";
}

/// <summary>
/// Capability Requirements for specific tests.
/// Add your own intrinsics or other system dependencies here.
/// </summary>
/// <remarks>
/// Usage: Add a category to it and apply exclusions in the ApplyExclusions method.
/// (this is an EXCLUSIVE category filter, it turns OFF all categories it matches)
/// Then, set your own BenchmarkCategory to include the CapabilityCategory string.
/// </remarks>
/// <example>
/// <code>
/// [BenchmarkCategory(
/// Categories.Fennecs,
/// Capabilities.Avx2
/// )]
/// public void Raw_AVX2()
/// </code>
/// </example>
internal static class Capabilities
{
// These are common vectorized instruction set categories.
// x86/x64
public const string Avx2 = nameof(System.Runtime.Intrinsics.X86.Avx2);
public const string Avx = nameof(System.Runtime.Intrinsics.X86.Avx);
public const string Sse3 = nameof(System.Runtime.Intrinsics.X86.Sse3);
public const string Sse2 = nameof(System.Runtime.Intrinsics.X86.Sse2);

// Arm
public const string AdvSimd = nameof(System.Runtime.Intrinsics.Arm.AdvSimd);

/// <summary>
/// This applies capability-based exclusions as filters to the config.
/// </summary>
/// <param name="self">a Benchmark Config, e.g. as used in Program.cs</param>
public static IConfig WithCapabilityExclusions(this IConfig self)
{
if (!System.Runtime.Intrinsics.X86.Avx2.IsSupported)
{
self = self.AddFilter(new CategoryExclusion(Avx2));
}

if (!System.Runtime.Intrinsics.X86.Avx.IsSupported)
{
self = self.AddFilter(new CategoryExclusion(Avx));
}

if (!System.Runtime.Intrinsics.X86.Sse3.IsSupported)
{
self = self.AddFilter(new CategoryExclusion(Sse3));
}

if (!System.Runtime.Intrinsics.X86.Sse2.IsSupported)
{
self = self.AddFilter(new CategoryExclusion(Sse2));
}

if (!System.Runtime.Intrinsics.Arm.AdvSimd.IsSupported)
{
self = self.AddFilter(new CategoryExclusion(AdvSimd));
}

return self;
}
}

/// <summary>
/// Excludes a given category from benchmarks.
/// (used by Program.cs)
/// </summary>
/// <remarks>
/// When an exclusion is PRESENT, then all benchmarks that HAVE the category will be EXCLUDED.
/// </remarks>
/// <example>
/// <c>CategoryExclusion("foo")</c> will exclude all benchmarks that have the "foo" category.
/// </example>
public class CategoryExclusion(string category) : IFilter
{
public bool Predicate([NotNull] BenchmarkCase benchmarkCase)
{
return !benchmarkCase.Descriptor.Categories.Contains(category);
}
}
}
30 changes: 22 additions & 8 deletions source/Ecs.CSharp.Benchmark/Contexts/FennecsBaseContext.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System;
using System.Runtime.CompilerServices;
using fennecs;

namespace Ecs.CSharp.Benchmark.Contexts
Expand All @@ -7,32 +8,45 @@ namespace Fennecs_Components
{
internal struct Component1
{
public static implicit operator Component1(int value) => new() { Value = value };
public static implicit operator Component2(Component1 self) => new() { Value = self.Value };
public static implicit operator Component3(Component1 self) => new() { Value = self.Value };
public static implicit operator int (Component1 c) => c.Value;

public int Value;
}

internal struct Component2
{
[MethodImpl(MethodImplOptions.AggressiveInlining)]
public static implicit operator Component1(Component2 self) => new() { Value = self.Value };
public static implicit operator Component2(int value) => new() { Value = value };
public static implicit operator Component3(Component2 self) => new() { Value = self.Value };
public static implicit operator int (Component2 c) => c.Value;

public int Value;
}

internal struct Component3
{
public static implicit operator Component1(Component3 self) => new() { Value = self.Value };
public static implicit operator Component2(Component3 self) => new() { Value = self.Value };
public static implicit operator Component3(int value) => new() { Value = value };
public static implicit operator int (Component3 c) => c.Value;

public int Value;
}
}

internal class FennecsBaseContext : IDisposable
internal class FennecsBaseContext(int entityCount) : IDisposable
{
public World World { get; }
public World World { get; } = new World(entityCount * 2);

public FennecsBaseContext()
{
World = new World();
}

public void Dispose()
public virtual void Dispose()
{
World.Dispose();
}
public FennecsBaseContext() : this(100000)
{ }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Ecs.CSharp.Benchmark
public partial class CreateEntityWithOneComponent
{
[Context] private readonly FennecsBaseContext _fennecs;

[BenchmarkCategory(Categories.Fennecs)]
[Benchmark]
public void Fennecs()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
using Ecs.CSharp.Benchmark.Contexts.Fennecs_Components;
using fennecs;

// ReSharper disable once CheckNamespace
namespace Ecs.CSharp.Benchmark
{
public partial class CreateEntityWithThreeComponents
{
[Context]
private readonly FennecsBaseContext _fennecs;

[Context] private readonly FennecsBaseContext _fennecs;

[BenchmarkCategory(Categories.Fennecs)]
[Benchmark]
public void Fennecs()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ namespace Ecs.CSharp.Benchmark
public partial class CreateEntityWithTwoComponents
{
[Context] private readonly FennecsBaseContext _fennecs;

[BenchmarkCategory(Categories.Fennecs)]
[Benchmark]
public void Fennecs()
Expand Down
2 changes: 1 addition & 1 deletion source/Ecs.CSharp.Benchmark/Ecs.CSharp.Benchmark.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
<PackageReference Include="DefaultEcs" Version="0.17.2" />
<PackageReference Include="DefaultEcs.Analyzer" Version="0.17.0" PrivateAssets="all" />

<PackageReference Include="fennecs" Version="0.1.1-beta" />
<PackageReference Include="fennecs" Version="0.3.5-beta" />

<PackageReference Include="Friflo.Engine.ECS" Version="1.14.0" />

Expand Down
28 changes: 22 additions & 6 deletions source/Ecs.CSharp.Benchmark/Program.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
#pragma warning disable CA1852 // Seal internal types

using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using BenchmarkDotNet.Configs;
using BenchmarkDotNet.Order;
using BenchmarkDotNet.Running;
using Ecs.CSharp.Benchmark;

Expand All @@ -14,21 +17,34 @@

BenchmarkSwitcher benchmark = BenchmarkSwitcher.FromTypes(new[]
{
typeof(CreateEntityWithOneComponent),
typeof(CreateEntityWithTwoComponents),
typeof(CreateEntityWithThreeComponents),

typeof(SystemWithOneComponent),
typeof(SystemWithTwoComponents),
typeof(SystemWithThreeComponents),

typeof(SystemWithTwoComponentsMultipleComposition)
typeof(SystemWithTwoComponentsMultipleComposition),

//Moving lighter tests to the back makes the estimated time display more reliable
typeof(CreateEntityWithOneComponent),
typeof(CreateEntityWithTwoComponents),
typeof(CreateEntityWithThreeComponents),
});

IConfig configuration = DefaultConfig.Instance.WithOptions(ConfigOptions.DisableOptimizationsValidator);

IConfig configuration = DefaultConfig.Instance
.WithOptions(ConfigOptions.DisableOptimizationsValidator)
.WithCapabilityExclusions();

if (args.Length > 0)
{
// There's no orderer commandline arg, so let's pretend there is one.
if (args.Contains("--ranking"))
{
List<string> argList = args.ToList();
argList.Remove("--ranking");
args = argList.ToArray();
configuration = configuration.WithOrderer(new DefaultOrderer(SummaryOrderPolicy.FastestToSlowest));
Copy link
Owner

@Doraku Doraku May 20, 2024

Choose a reason for hiding this comment

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

I see this is an active or not settings in benchmarkdotnet. I'm thinking that maybe it would be better to stick to their philosophy (and what I'm doing for the CHECK_CACHE_MISSES define and HardwareCounters attribute for exemple 🤔). What do you think?

Copy link
Author

Choose a reason for hiding this comment

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

I think we can go with the #define (to make starting conditions more uniform), but I'd still use it at the configuration level, rather than at the Attribute level. (BenchmarkDotNet encourages both).

Working on it right now..

Copy link
Author

Choose a reason for hiding this comment

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

IConfig configuration = DefaultConfig.Instance
    .WithOptions(ConfigOptions.DisableOptimizationsValidator)
    .WithCapabilityExclusions();

#if RANK_RESULTS
    configuration = configuration.WithOrderer(new BenchmarkDotNet.Order.DefaultOrderer(BenchmarkDotNet.Order.SummaryOrderPolicy.FastestToSlowest));
#endif

if (args.Length > 0)
{
    benchmark.Run(args, configuration);
}
else
{
    benchmark.RunAll(configuration);
}

Copy link
Author

Choose a reason for hiding this comment

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

Let me know what you think, otherwise I can just remove the change entirely. For my own workflow, the define is certainly less convenient than a command line parameter, but it's your repo and you also run the benchmark in its full scale, so this is what counts.

}

benchmark.Run(args, configuration);
}
else
Expand Down
Loading