Skip to content

Commit

Permalink
Make ConstructRid 5x faster and AOT-compatible
Browse files Browse the repository at this point in the history
  • Loading branch information
pkdawson committed Sep 8, 2024
1 parent 924bd29 commit 846a012
Show file tree
Hide file tree
Showing 9 changed files with 263 additions and 42 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,3 +41,6 @@ scons_cache
doc/**/addons/
addons/imgui-godot/include/imgui-version.txt
export/
**/bin/Debug/
**/bin/Release/
**/obj/
20 changes: 16 additions & 4 deletions Dear ImGui for Godot Demo.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,18 @@
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<Nullable>enable</Nullable>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
<IsTrimmable>True</IsTrimmable>
<IsAotCompatible>True</IsAotCompatible>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ExportDebug|AnyCPU'">
<IsTrimmable>True</IsTrimmable>
<IsAotCompatible>True</IsAotCompatible>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='ExportRelease|AnyCPU'">
<IsTrimmable>True</IsTrimmable>
<IsAotCompatible>True</IsAotCompatible>
</PropertyGroup>
<ItemGroup>
<Compile Remove="doc\**" />
<Compile Remove="gdext\**" />
Expand All @@ -14,19 +26,19 @@
</ItemGroup>
<ItemGroup>
<PackageReference Include="ImGui.NET" Version="1.91.0.1" />
<PackageReference Include="Roslynator.Analyzers" Version="4.12.2">
<PackageReference Include="Roslynator.Analyzers" Version="4.12.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Roslynator.CodeAnalysis.Analyzers" Version="4.12.2">
<PackageReference Include="Roslynator.CodeAnalysis.Analyzers" Version="4.12.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Roslynator.CodeFixes" Version="4.12.2">
<PackageReference Include="Roslynator.CodeFixes" Version="4.12.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Roslynator.Formatting.Analyzers" Version="4.12.2">
<PackageReference Include="Roslynator.Formatting.Analyzers" Version="4.12.4">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
Expand Down
37 changes: 3 additions & 34 deletions addons/imgui-godot/ImGuiGodot/Internal/Util.cs
Original file line number Diff line number Diff line change
@@ -1,45 +1,14 @@
using Godot;
using System;
#if GODOT_PC
using System.Reflection;
using System.Reflection.Emit;
#else
using System.Diagnostics;
using System.Runtime.InteropServices;
#endif

namespace ImGuiGodot.Internal;

internal static class Util
{
#if GODOT_PC
// this is ~15x faster, so keep it for non-AOT platforms

public static readonly Func<ulong, Rid> ConstructRid;

static Util()
{
ConstructorInfo cinfo = typeof(Rid).GetConstructor(
BindingFlags.NonPublic | BindingFlags.Instance,
[typeof(ulong)]) ??
throw new PlatformNotSupportedException("failed to get Rid constructor");
DynamicMethod dm = new("ConstructRid", typeof(Rid), [typeof(ulong)]);
ILGenerator il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Newobj, cinfo);
il.Emit(OpCodes.Ret);
ConstructRid = dm.CreateDelegate<Func<ulong, Rid>>();
}
#else
public static Rid ConstructRid(ulong id)
public static unsafe Rid ConstructRid(ulong id)
{
Debug.Assert(Marshal.SizeOf<Rid>() == sizeof(ulong));
nint ptr = Marshal.AllocHGlobal(Marshal.SizeOf<Rid>());
byte[] bytes = BitConverter.GetBytes(id);
Marshal.Copy(bytes, 0, ptr, bytes.Length);
Rid rv = Marshal.PtrToStructure<Rid>(ptr);
Marshal.FreeHGlobal(ptr);
Rid rv;
Buffer.MemoryCopy(&id, &rv, sizeof(Rid), sizeof(ulong));
return rv;
}
#endif
}
105 changes: 105 additions & 0 deletions doc/test/csbench/BenchCanvas.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
using ImGuiNET;
using System.Runtime.InteropServices;

namespace csbench;

[MemoryDiagnoser]
public class BenchCanvas
{
private readonly ImDrawVertPtr v;
private readonly Godot.Vector2[] points = new Godot.Vector2[1];
private readonly Godot.Color[] colors = new Godot.Color[1];
private readonly Godot.Vector2[] uvs = new Godot.Vector2[1];

public unsafe BenchCanvas()
{
nint ptr = Marshal.AllocHGlobal(sizeof(ImDrawVert));
v = new(ptr)
{
col = 0x12345678,
uv = new(0.1f, 0.2f),
pos = new(0.3f, 0.4f)
};
}

[Benchmark(Baseline = true)]
public void ConvertVertex1()
{
points[0] = new(v.pos.X, v.pos.Y);
uint rgba = v.col;
float r = (rgba & 0xFFu) / 255f;
rgba >>= 8;
float g = (rgba & 0xFFu) / 255f;
rgba >>= 8;
float b = (rgba & 0xFFu) / 255f;
rgba >>= 8;
float a = (rgba & 0xFFu) / 255f;
colors[0] = new(r, g, b, a);
uvs[0] = new(v.uv.X, v.uv.Y);
}

// same as #1
[Benchmark]
public void ConvertVertex2()
{
ref var out_pos = ref points[0];
ref var out_color = ref colors[0];
ref var out_uv = ref uvs[0];

out_pos.X = v.pos.X;
out_pos.Y = v.pos.Y;
uint rgba = v.col;
out_color.R = (rgba & 0xFFu) / 255f;
rgba >>= 8;
out_color.G = (rgba & 0xFFu) / 255f;
rgba >>= 8;
out_color.B = (rgba & 0xFFu) / 255f;
rgba >>= 8;
out_color.A = (rgba & 0xFFu) / 255f;
out_uv.X = v.uv.X;
out_uv.Y = v.uv.Y;
}

// this is a bit faster
[Benchmark]
public unsafe void ConvertVertex3()
{
ref var out_pos = ref points[0];
ref var out_color = ref colors[0];
ref var out_uv = ref uvs[0];

ImDrawVert* p = v;

out_pos.X = p->pos.X;
out_pos.Y = p->pos.Y;
uint rgba = p->col;
out_color.R = (rgba & 0xFFu) / 255f;
rgba >>= 8;
out_color.G = (rgba & 0xFFu) / 255f;
rgba >>= 8;
out_color.B = (rgba & 0xFFu) / 255f;
rgba >>= 8;
out_color.A = (rgba & 0xFFu) / 255f;
out_uv.X = p->uv.X;
out_uv.Y = p->uv.Y;
}

// same as #3
[Benchmark]
public unsafe void ConvertVertex4()
{
ImDrawVert* p = v;

points[0] = new(p->pos.X, p->pos.Y);
uint rgba = p->col;
float r = (rgba & 0xFFu) / 255f;
rgba >>= 8;
float g = (rgba & 0xFFu) / 255f;
rgba >>= 8;
float b = (rgba & 0xFFu) / 255f;
rgba >>= 8;
float a = (rgba & 0xFFu) / 255f;
colors[0] = new(r, g, b, a);
uvs[0] = new(p->uv.X, p->uv.Y);
}
}
81 changes: 81 additions & 0 deletions doc/test/csbench/BenchRid.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
using Godot;
using System.Reflection.Emit;
using System.Reflection;
using System.Runtime.InteropServices;
using System.Runtime.CompilerServices;

namespace csbench;

[MemoryDiagnoser]
public class BenchRid
{
private readonly Func<ulong, Rid> _constructRid;
private readonly ulong _id = 12345;
private readonly nint _buf;

public BenchRid()
{
ConstructorInfo cinfo = typeof(Rid).GetConstructor(
BindingFlags.NonPublic | BindingFlags.Instance,
[typeof(ulong)]) ??
throw new PlatformNotSupportedException("failed to get Rid constructor");
DynamicMethod dm = new("ConstructRid", typeof(Rid), [typeof(ulong)]);
ILGenerator il = dm.GetILGenerator();
il.Emit(OpCodes.Ldarg_0);
il.Emit(OpCodes.Newobj, cinfo);
il.Emit(OpCodes.Ret);
_constructRid = dm.CreateDelegate<Func<ulong, Rid>>();

_buf = Marshal.AllocHGlobal(Marshal.SizeOf<Rid>());
}

[Benchmark(Baseline = true)]
public Rid ConstructRid_Emitted()
{
return _constructRid(_id);
}

[Benchmark]
public Rid ConstructRid_Marshal()
{
nint ptr = Marshal.AllocHGlobal(Marshal.SizeOf<Rid>());
byte[] bytes = BitConverter.GetBytes(_id);
Marshal.Copy(bytes, 0, ptr, bytes.Length);
Rid rv = Marshal.PtrToStructure<Rid>(ptr);
Marshal.FreeHGlobal(ptr);
return rv;
}

[Benchmark]
public Rid ConstructRid_Marshal_PreAlloc()
{
// not thread-safe
byte[] bytes = BitConverter.GetBytes(_id);
Marshal.Copy(bytes, 0, _buf, bytes.Length);
Rid rv = Marshal.PtrToStructure<Rid>(_buf);
return rv;
}

[Benchmark]
public unsafe Rid ConstructRid_Unsafe()
{
Rid rv;
byte[] bytes = BitConverter.GetBytes(_id);
fixed (byte* pbytes = bytes)
{
Buffer.MemoryCopy(pbytes, &rv, sizeof(Rid), bytes.Length);
}
return rv;
}

[Benchmark]
public unsafe Rid ConstructRid_Unsafe_Direct()
{
Rid rv;
fixed (ulong* p = &_id)
{
Buffer.MemoryCopy(p, &rv, sizeof(Rid), sizeof(ulong));
}
return rv;
}
}
4 changes: 4 additions & 0 deletions doc/test/csbench/Program.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
using csbench;

//BenchmarkRunner.Run<BenchCanvas>();
BenchmarkRunner.Run<BenchRid>();
23 changes: 23 additions & 0 deletions doc/test/csbench/csbench.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
<PublishAot>False</PublishAot>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="BenchmarkDotNet" Version="0.14.0" />
<PackageReference Include="GodotSharp" Version="4.3.0" />
<PackageReference Include="ImGui.NET" Version="1.91.0.1" />
</ItemGroup>

<ItemGroup>
<Using Include="BenchmarkDotNet.Attributes" />
<Using Include="BenchmarkDotNet.Running" />
</ItemGroup>

</Project>
25 changes: 25 additions & 0 deletions doc/test/csbench/csbench.sln
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.11.35222.181
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "csbench", "csbench.csproj", "{D37BA677-C36A-4F7A-9E87-72D5CF034A7B}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{D37BA677-C36A-4F7A-9E87-72D5CF034A7B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D37BA677-C36A-4F7A-9E87-72D5CF034A7B}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D37BA677-C36A-4F7A-9E87-72D5CF034A7B}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D37BA677-C36A-4F7A-9E87-72D5CF034A7B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {21D11FF2-8E04-4538-B85D-36C1B5758C37}
EndGlobalSection
EndGlobal
7 changes: 3 additions & 4 deletions gdext/src/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,9 @@ namespace ImGui::Godot {

inline RID make_rid(int64_t id)
{
// ugly, may break in the future
// HACK: only way to set a RID value
RID rv;
*reinterpret_cast<int64_t*>(rv._native_ptr()) = id;
IM_ASSERT(rv.get_id() == id);
memcpy(rv._native_ptr(), &id, sizeof(int64_t));
return rv;
}

Expand All @@ -25,5 +24,5 @@ inline RID make_rid(ImTextureID id)
template <>
struct std::hash<RID>
{
std::size_t operator()(const RID& rid) const noexcept { return std::hash<int64_t>{}(rid.get_id()); }
std::size_t operator()(const RID& rid) const noexcept { return rid.get_id(); }
};

0 comments on commit 846a012

Please sign in to comment.