Skip to content

Commit 72771a7

Browse files
authored
Merge pull request #16 from xledger/faster-ctor
Provide slightly faster, lower memory use, safe constructors
2 parents f4d70ab + 4f54f4e commit 72771a7

20 files changed

Lines changed: 738 additions & 11 deletions
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
namespace Xledger.Collections.Bench;
2+
3+
[MemoryDiagnoser(displayGenColumns: false)]
4+
[SimpleJob(RuntimeMoniker.Net48)]
5+
[SimpleJob(RuntimeMoniker.Net80)]
6+
[SimpleJob(RuntimeMoniker.Net10_0)]
7+
public class ImmArrayConstruction {
8+
[Params(300)]
9+
public int Length;
10+
11+
[Benchmark(Baseline = true)]
12+
public string[] NewArray() {
13+
var arr = new string[this.Length];
14+
for (int i = 0; i < arr.Length; ++i) {
15+
arr[i] = Guid.NewGuid().ToString();
16+
}
17+
return arr;
18+
}
19+
20+
[Benchmark]
21+
public ImmArray<string> ToImmArray() {
22+
var arr = new string[this.Length];
23+
for (int i = 0; i < arr.Length; ++i) {
24+
arr[i] = Guid.NewGuid().ToString();
25+
}
26+
return arr.ToImmArray();
27+
}
28+
29+
[Benchmark]
30+
public ImmArray<string> ImmArray_Build() {
31+
return ImmArray.Build<string>(arr => {
32+
for (int i = 0; i < arr.Length; ++i) {
33+
arr[i] = Guid.NewGuid().ToString();
34+
}
35+
}, length: this.Length);
36+
}
37+
}
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
using System.Linq;
2+
using System.Linq.Expressions;
3+
using System.Reflection;
4+
5+
namespace Xledger.Collections.Bench;
6+
7+
[MemoryDiagnoser(displayGenColumns: false)]
8+
[SimpleJob(RuntimeMoniker.Net48)]
9+
[SimpleJob(RuntimeMoniker.Net80)]
10+
[SimpleJob(RuntimeMoniker.Net10_0)]
11+
public class ImmArrayReflection {
12+
[Params(300)]
13+
public int Length;
14+
15+
public static string[] NewArray(int length) {
16+
var arr = new string[length];
17+
for (int i = 0; i < arr.Length; ++i) {
18+
arr[i] = Guid.NewGuid().ToString();
19+
}
20+
return arr;
21+
}
22+
23+
[Benchmark(Baseline = true)]
24+
public ImmArray<string> ImmArray_Build() {
25+
return ImmArray.Build<string>(FillSpan, length: this.Length);
26+
}
27+
28+
static readonly ConstructorInfo CI_NoCopy = typeof(ImmArray<string>).GetConstructor(
29+
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.ExactBinding,
30+
binder: null,
31+
[typeof(string[])],
32+
modifiers: null);
33+
static readonly MethodInfo MI_Build = typeof(ImmArray).GetMethods()
34+
.Where(mi =>
35+
mi.Name == nameof(ImmArray.Build)
36+
#if NET10_0_OR_GREATER
37+
&& mi.CustomAttributes.Where(
38+
a => a.AttributeType == typeof(System.Runtime.CompilerServices.OverloadResolutionPriorityAttribute)
39+
).Any()
40+
#endif
41+
)
42+
.Single()
43+
.MakeGenericMethod(typeof(string));
44+
45+
static readonly MethodInfo MI_NewArray = typeof(ImmArrayReflection).GetMethod(nameof(NewArray));
46+
static readonly MethodInfo MI_FillSpan = typeof(ImmArrayReflection).GetMethod(nameof(FillSpan));
47+
48+
static readonly Func<int, ImmArray<string>> newNoCopy = Compile_NewNoCopy();
49+
static readonly Func<int, ImmArray<string>> build = Compile_Build();
50+
51+
public static void FillSpan(Span<string> arr) {
52+
for (int i = 0; i < arr.Length; ++i) {
53+
arr[i] = Guid.NewGuid().ToString();
54+
}
55+
}
56+
57+
[Benchmark]
58+
public ImmArray<string> DynInvoke_NewNoCopy() {
59+
return (ImmArray<string>)CI_NoCopy.Invoke([NewArray(this.Length)]);
60+
}
61+
62+
[Benchmark]
63+
public ImmArray<string> NewNoCopy() {
64+
return new ImmArray<string>(NewArray(this.Length));
65+
}
66+
67+
static Func<int, ImmArray<string>> Compile_NewNoCopy() {
68+
var pi_length = Expression.Parameter(typeof(int), "length");
69+
var newImmArray =
70+
Expression.New(CI_NoCopy,
71+
Expression.Call(MI_NewArray, pi_length));
72+
var fn = Expression.Lambda<Func<int, ImmArray<string>>>(newImmArray, tailCall: false, pi_length).Compile();
73+
return fn;
74+
}
75+
76+
static Func<int, ImmArray<string>> Compile_Build() {
77+
var pi_length = Expression.Parameter(typeof(int), "length");
78+
#if NET10_0_OR_GREATER
79+
var fillSpanDelegate = MI_FillSpan.CreateDelegate<Action<Span<string>>>();
80+
#else
81+
var fillSpanDelegate = MI_FillSpan.CreateDelegate(typeof(ImmArray.FillSpan<string>));
82+
#endif
83+
var newImmArray = Expression.Call(MI_Build, Expression.Constant(fillSpanDelegate), pi_length);
84+
var fn = Expression.Lambda<Func<int, ImmArray<string>>>(newImmArray, tailCall: false, pi_length).Compile();
85+
return fn;
86+
}
87+
88+
[Benchmark]
89+
public ImmArray<string> Expr_NewNoCopy() {
90+
return newNoCopy(this.Length);
91+
}
92+
93+
[Benchmark]
94+
public ImmArray<string> Expr_Build() {
95+
return build(this.Length);
96+
}
97+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
namespace Xledger.Collections.Bench;
2+
3+
[MemoryDiagnoser(displayGenColumns: false)]
4+
[SimpleJob(RuntimeMoniker.Net48)]
5+
[SimpleJob(RuntimeMoniker.Net80)]
6+
[SimpleJob(RuntimeMoniker.Net10_0)]
7+
public class ImmDictConstruction {
8+
[Params(0, 300)]
9+
public int Capacity;
10+
11+
[Params(300, 500)]
12+
public int Count;
13+
14+
[Benchmark(Baseline = true)]
15+
public Dictionary<string, int> NewDictionary() {
16+
var dict = new Dictionary<string, int>(this.Capacity);
17+
for (int i = 0; i < this.Count; ++i) {
18+
dict.Add(Guid.NewGuid().ToString(), i);
19+
}
20+
return dict;
21+
}
22+
23+
[Benchmark]
24+
public ImmDict<string, int> ToImmDict() {
25+
var dict = new Dictionary<string, int>(this.Capacity);
26+
for (int i = 0; i < this.Count; ++i) {
27+
dict.Add(Guid.NewGuid().ToString(), i);
28+
}
29+
return dict.ToImmDict();
30+
}
31+
32+
[Benchmark]
33+
public ImmDict<string, int> ImmDict_Build() {
34+
return ImmDict.Build<string, int>(dict => {
35+
for (int i = 0; i < this.Count; ++i) {
36+
dict.Add(Guid.NewGuid().ToString(), i);
37+
}
38+
}, capacity: this.Capacity);
39+
}
40+
}
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
using System.Linq;
2+
using System.Linq.Expressions;
3+
using System.Reflection;
4+
5+
namespace Xledger.Collections.Bench;
6+
7+
[MemoryDiagnoser(displayGenColumns: false)]
8+
[SimpleJob(RuntimeMoniker.Net48)]
9+
[SimpleJob(RuntimeMoniker.Net80)]
10+
[SimpleJob(RuntimeMoniker.Net10_0)]
11+
public class ImmDictReflection {
12+
[Params(0, 300)]
13+
public int Capacity;
14+
15+
[Params(300, 500)]
16+
public int Count;
17+
18+
public static Dictionary<string, int> NewDictionary(int capacity, int count) {
19+
var dict = new Dictionary<string, int>(capacity);
20+
for (int i = 0; i < count; ++i) {
21+
dict.Add(Guid.NewGuid().ToString(), i);
22+
}
23+
return dict;
24+
}
25+
26+
public static ImmDict.BuildDict<string, int> MakeFillDict(int count) {
27+
return (ImmDict.DictBuilder<string, int> dict) => {
28+
for (int i = 0; i < count; ++i) {
29+
dict.Add(Guid.NewGuid().ToString(), i);
30+
}
31+
};
32+
}
33+
34+
[Benchmark(Baseline = true)]
35+
public ImmDict<string, int> ImmDict_Build() {
36+
return ImmDict.Build<string, int>(MakeFillDict(this.Count), capacity: this.Capacity);
37+
}
38+
39+
static readonly ConstructorInfo CI_NoCopy = typeof(ImmDict<string, int>).GetConstructor(
40+
BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.ExactBinding,
41+
binder: null,
42+
[typeof(Dictionary<string, int>)],
43+
modifiers: null);
44+
static readonly MethodInfo MI_Build = typeof(ImmDict).GetMethods()
45+
.Where(mi => mi.Name == nameof(ImmDict.Build))
46+
.Last()
47+
.MakeGenericMethod(typeof(string), typeof(int));
48+
49+
static readonly MethodInfo MI_NewDictionary = typeof(ImmDictReflection).GetMethod(nameof(NewDictionary));
50+
static readonly MethodInfo MI_MakeFillDict = typeof(ImmDictReflection).GetMethod(nameof(MakeFillDict));
51+
52+
static readonly Func<int, int, ImmDict<string, int>> newNoCopy = Compile_NewNoCopy();
53+
static readonly Func<int, int, ImmDict<string, int>> build = Compile_Build();
54+
55+
[Benchmark]
56+
public ImmDict<string, int> DynInvoke_NewNoCopy() {
57+
return (ImmDict<string, int>)CI_NoCopy.Invoke([NewDictionary(this.Capacity, this.Count)]);
58+
}
59+
60+
[Benchmark]
61+
public ImmDict<string, int> NewNoCopy() {
62+
return new ImmDict<string, int>(NewDictionary(this.Capacity, this.Count));
63+
}
64+
65+
static Func<int, int, ImmDict<string, int>> Compile_NewNoCopy() {
66+
var pi_capacity = Expression.Parameter(typeof(int), "capacity");
67+
var pi_count = Expression.Parameter(typeof(int), "count");
68+
var newImmSet =
69+
Expression.New(CI_NoCopy,
70+
Expression.Call(MI_NewDictionary, pi_capacity, pi_count));
71+
var fn = Expression.Lambda<Func<int, int, ImmDict<string, int>>>(
72+
newImmSet,
73+
tailCall: false,
74+
pi_capacity,
75+
pi_count).Compile();
76+
return fn;
77+
}
78+
79+
static Func<int, int, ImmDict<string, int>> Compile_Build() {
80+
var pi_capacity = Expression.Parameter(typeof(int), "capacity");
81+
var pi_count = Expression.Parameter(typeof(int), "count");
82+
var fillSetDelegate = Expression.Call(MI_MakeFillDict, pi_count);
83+
var newImmSet = Expression.Call(MI_Build, fillSetDelegate, pi_capacity, Expression.Constant(false));
84+
var fn = Expression.Lambda<Func<int, int, ImmDict<string, int>>>(
85+
newImmSet,
86+
tailCall: false,
87+
pi_capacity,
88+
pi_count).Compile();
89+
return fn;
90+
}
91+
92+
[Benchmark]
93+
public ImmDict<string, int> Expr_NewNoCopy() {
94+
return newNoCopy(this.Capacity, this.Count);
95+
}
96+
97+
[Benchmark]
98+
public ImmDict<string, int> Expr_Build() {
99+
return build(this.Capacity, this.Count);
100+
}
101+
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
namespace Xledger.Collections.Bench;
2+
3+
[MemoryDiagnoser(displayGenColumns: false)]
4+
[SimpleJob(RuntimeMoniker.Net48)]
5+
[SimpleJob(RuntimeMoniker.Net80)]
6+
[SimpleJob(RuntimeMoniker.Net10_0)]
7+
public class ImmSetConstruction {
8+
[Params(0, 300)]
9+
public int Capacity;
10+
11+
[Params(300, 500)]
12+
public int Count;
13+
14+
[Benchmark(Baseline = true)]
15+
public HashSet<string> NewHashSet() {
16+
var set = new HashSet<string>(this.Capacity);
17+
for (int i = 0; i < this.Count; ++i) {
18+
set.Add(Guid.NewGuid().ToString());
19+
}
20+
return set;
21+
}
22+
23+
[Benchmark]
24+
public ImmSet<string> ToImmSet() {
25+
var set = new HashSet<string>(this.Capacity);
26+
for (int i = 0; i < this.Count; ++i) {
27+
set.Add(Guid.NewGuid().ToString());
28+
}
29+
return set.ToImmSet();
30+
}
31+
32+
[Benchmark]
33+
public ImmSet<string> ImmSet_Build() {
34+
return ImmSet.Build<string>(set => {
35+
for (int i = 0; i < this.Count; ++i) {
36+
set.Add(Guid.NewGuid().ToString());
37+
}
38+
}, capacity: this.Capacity);
39+
}
40+
}

0 commit comments

Comments
 (0)