Skip to content

Commit 72146a4

Browse files
committed
Use interpolation binary search when keys are well distributed
1 parent f977752 commit 72146a4

78 files changed

Lines changed: 601 additions & 207 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

Src/FastData.Generator.CPlusPlus/Internal/Generators/BinarySearchCode.cs

Lines changed: 126 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public override string Generate()
1212
bool customValue = !typeof(TValue).IsPrimitive;
1313
StringBuilder sb = new StringBuilder();
1414
ReadOnlySpan<TKey> keys = ctx.Keys.Span;
15+
bool useInterpolation = ctx.UseInterpolation;
1516

1617
if (!ctx.Values.IsEmpty)
1718
{
@@ -34,36 +35,49 @@ public override string Generate()
3435
{{GetMethodModifier(true)}}bool contains(const {{KeyTypeName}} {{InputKeyName}}){{PostMethodModifier}} {
3536
{{GetMethodHeader(MethodType.Contains)}}
3637
37-
int32_t lo = 0;
38-
int32_t hi = {{(keys.Length - 1).ToStringInvariant()}};
39-
while (lo <= hi) {
40-
const int32_t mid = lo + ((hi - lo) >> 1);
41-
const {{KeyTypeName}} mid_key = keys[mid];
42-
const int32_t order = {{GetCompareFunction("mid_key", LookupKeyName)}};
43-
44-
if (order == 0)
45-
return true;
46-
if (order < 0)
47-
lo = mid + 1;
48-
else
49-
hi = mid - 1;
50-
}
51-
52-
return false;
53-
}
5438
""");
5539

56-
if (!ctx.Values.IsEmpty)
40+
if (useInterpolation)
5741
{
58-
string ptr = customValue ? "" : "&";
59-
shared.Add(CodePlacement.Before, GetObjectDeclarations<TValue>());
60-
6142
sb.Append($$"""
43+
int32_t lo = 0;
44+
int32_t hi = {{(keys.Length - 1).ToStringInvariant()}};
45+
while (lo <= hi && {{LookupKeyName}} >= keys[lo] && {{LookupKeyName}} <= keys[hi]) {
46+
const {{KeyTypeName}} lo_key = keys[lo];
47+
const {{KeyTypeName}} hi_key = keys[hi];
6248
63-
{{MethodAttribute}}
64-
{{GetMethodModifier(false)}}bool try_lookup(const {{KeyTypeName}} {{InputKeyName}}, const {{ValueTypeName}}*& value){{PostMethodModifier}} {
65-
{{GetMethodHeader(MethodType.TryLookup)}}
49+
if (lo_key == hi_key) {
50+
if (lo_key == {{LookupKeyName}})
51+
return true;
52+
53+
break;
54+
}
6655
56+
const double range = static_cast<double>(hi_key) - static_cast<double>(lo_key);
57+
const double offset = static_cast<double>({{LookupKeyName}}) - static_cast<double>(lo_key);
58+
int32_t mid = lo + static_cast<int32_t>((offset * (hi - lo)) / range);
59+
60+
if (mid < lo)
61+
mid = lo;
62+
else if (mid > hi)
63+
mid = hi;
64+
65+
const {{KeyTypeName}} mid_key = keys[mid];
66+
if (mid_key == {{LookupKeyName}})
67+
return true;
68+
if (mid_key < {{LookupKeyName}})
69+
lo = mid + 1;
70+
else
71+
hi = mid - 1;
72+
}
73+
74+
return false;
75+
}
76+
""");
77+
}
78+
else
79+
{
80+
sb.Append($$"""
6781
int32_t lo = 0;
6882
int32_t hi = {{(keys.Length - 1).ToStringInvariant()}};
6983
while (lo <= hi) {
@@ -72,23 +86,106 @@ public override string Generate()
7286
const int32_t order = {{GetCompareFunction("mid_key", LookupKeyName)}};
7387
7488
if (order == 0)
75-
{
76-
value = {{ptr}}values[mid];
7789
return true;
78-
}
79-
8090
if (order < 0)
8191
lo = mid + 1;
8292
else
8393
hi = mid - 1;
8494
}
8595
86-
value = nullptr;
8796
return false;
8897
}
8998
""");
9099
}
91100

101+
if (!ctx.Values.IsEmpty)
102+
{
103+
string ptr = customValue ? "" : "&";
104+
shared.Add(CodePlacement.Before, GetObjectDeclarations<TValue>());
105+
106+
sb.Append($$"""
107+
108+
{{MethodAttribute}}
109+
{{GetMethodModifier(false)}}bool try_lookup(const {{KeyTypeName}} {{InputKeyName}}, const {{ValueTypeName}}*& value){{PostMethodModifier}} {
110+
{{GetMethodHeader(MethodType.TryLookup)}}
111+
112+
""");
113+
114+
if (useInterpolation)
115+
{
116+
sb.Append($$"""
117+
int32_t lo = 0;
118+
int32_t hi = {{(keys.Length - 1).ToStringInvariant()}};
119+
while (lo <= hi && {{LookupKeyName}} >= keys[lo] && {{LookupKeyName}} <= keys[hi]) {
120+
const {{KeyTypeName}} lo_key = keys[lo];
121+
const {{KeyTypeName}} hi_key = keys[hi];
122+
123+
if (lo_key == hi_key) {
124+
if (lo_key == {{LookupKeyName}})
125+
{
126+
value = {{ptr}}values[lo];
127+
return true;
128+
}
129+
130+
break;
131+
}
132+
133+
const double range = static_cast<double>(hi_key) - static_cast<double>(lo_key);
134+
const double offset = static_cast<double>({{LookupKeyName}}) - static_cast<double>(lo_key);
135+
int32_t mid = lo + static_cast<int32_t>((offset * (hi - lo)) / range);
136+
137+
if (mid < lo)
138+
mid = lo;
139+
else if (mid > hi)
140+
mid = hi;
141+
142+
const {{KeyTypeName}} mid_key = keys[mid];
143+
if (mid_key == {{LookupKeyName}})
144+
{
145+
value = {{ptr}}values[mid];
146+
return true;
147+
}
148+
149+
if (mid_key < {{LookupKeyName}})
150+
lo = mid + 1;
151+
else
152+
hi = mid - 1;
153+
}
154+
155+
value = nullptr;
156+
return false;
157+
}
158+
""");
159+
}
160+
else
161+
{
162+
sb.Append($$"""
163+
int32_t lo = 0;
164+
int32_t hi = {{(keys.Length - 1).ToStringInvariant()}};
165+
while (lo <= hi) {
166+
const int32_t mid = lo + ((hi - lo) >> 1);
167+
const {{KeyTypeName}} mid_key = keys[mid];
168+
const int32_t order = {{GetCompareFunction("mid_key", LookupKeyName)}};
169+
170+
if (order == 0)
171+
{
172+
value = {{ptr}}values[mid];
173+
return true;
174+
}
175+
176+
if (order < 0)
177+
lo = mid + 1;
178+
else
179+
hi = mid - 1;
180+
}
181+
182+
value = nullptr;
183+
return false;
184+
}
185+
""");
186+
}
187+
}
188+
92189
return sb.ToString();
93190
}
94191
}

Src/FastData.Generator.CSharp.Benchmarks/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -69,7 +69,7 @@ public class Program
6969
""");
7070

7171
TestHelper.TryWriteFile(Path.Combine(rootDir, "Program.cs"), sb.ToString());
72-
BenchmarkHelper.RunBenchmark("dotnet", "run -c release", rootDir, @"--adapter c_sharp_dot_net --file BenchmarkDotNet.Artifacts\results\CSharp.Program-report-brief-compressed.json --testbed CSharp");
72+
// BenchmarkHelper.RunBenchmark("dotnet", "run -c release", rootDir, @"--adapter c_sharp_dot_net --file BenchmarkDotNet.Artifacts\results\CSharp.Program-report-brief-compressed.json --testbed CSharp");
7373
}
7474

7575
private static string PrintQueries(ITestData data, string identifier)

Src/FastData.Generator.CSharp/Internal/Generators/BinarySearchCode.cs

Lines changed: 127 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ public override string Generate()
1111
{
1212
StringBuilder sb = new StringBuilder();
1313
ReadOnlySpan<TKey> keys = ctx.Keys.Span;
14+
bool useInterpolation = ctx.UseInterpolation;
1415

1516
if (!ctx.Values.IsEmpty)
1617
{
@@ -35,34 +36,51 @@ public override string Generate()
3536
{
3637
{{GetMethodHeader(MethodType.Contains)}}
3738
38-
int lo = 0;
39-
int hi = {{(keys.Length - 1).ToStringInvariant()}};
40-
while (lo <= hi)
41-
{
42-
int i = lo + ((hi - lo) >> 1);
43-
int order = {{GetCompareFunction("_keys[i]", LookupKeyName)}};
44-
45-
if (order == 0)
46-
return true;
47-
if (order < 0)
48-
lo = i + 1;
49-
else
50-
hi = i - 1;
51-
}
52-
53-
return ~lo >= 0;
54-
}
5539
""");
5640

57-
if (!ctx.Values.IsEmpty)
41+
if (useInterpolation)
5842
{
5943
sb.Append($$"""
44+
int lo = 0;
45+
int hi = {{(keys.Length - 1).ToStringInvariant()}};
46+
while (lo <= hi && {{LookupKeyName}} >= _keys[lo] && {{LookupKeyName}} <= _keys[hi])
47+
{
48+
{{KeyTypeName}} loKey = _keys[lo];
49+
{{KeyTypeName}} hiKey = _keys[hi];
6050
61-
{{MethodAttribute}}
62-
{{MethodModifier}}bool TryLookup({{KeyTypeName}} {{InputKeyName}}, out {{ValueTypeName}}? value)
63-
{
64-
{{GetMethodHeader(MethodType.TryLookup)}}
51+
if (loKey == hiKey)
52+
{
53+
if (loKey == {{LookupKeyName}})
54+
return true;
55+
56+
break;
57+
}
58+
59+
double range = (double)hiKey - (double)loKey;
60+
double offset = (double){{LookupKeyName}} - (double)loKey;
61+
int i = lo + (int)((offset * (hi - lo)) / range);
6562
63+
if (i < lo)
64+
i = lo;
65+
else if (i > hi)
66+
i = hi;
67+
68+
{{KeyTypeName}} midKey = _keys[i];
69+
if (midKey == {{LookupKeyName}})
70+
return true;
71+
if (midKey < {{LookupKeyName}})
72+
lo = i + 1;
73+
else
74+
hi = i - 1;
75+
}
76+
77+
return false;
78+
}
79+
""");
80+
}
81+
else
82+
{
83+
sb.Append($$"""
6684
int lo = 0;
6785
int hi = {{(keys.Length - 1).ToStringInvariant()}};
6886
while (lo <= hi)
@@ -71,22 +89,104 @@ public override string Generate()
7189
int order = {{GetCompareFunction("_keys[i]", LookupKeyName)}};
7290
7391
if (order == 0)
74-
{
75-
value = _values[i];
7692
return true;
77-
}
7893
if (order < 0)
7994
lo = i + 1;
8095
else
8196
hi = i - 1;
8297
}
8398
84-
value = default;
85-
return false;
99+
return ~lo >= 0;
86100
}
87101
""");
88102
}
89103

104+
if (!ctx.Values.IsEmpty)
105+
{
106+
sb.Append($$"""
107+
108+
{{MethodAttribute}}
109+
{{MethodModifier}}bool TryLookup({{KeyTypeName}} {{InputKeyName}}, out {{ValueTypeName}}? value)
110+
{
111+
{{GetMethodHeader(MethodType.TryLookup)}}
112+
113+
""");
114+
115+
if (useInterpolation)
116+
{
117+
sb.Append($$"""
118+
int lo = 0;
119+
int hi = {{(keys.Length - 1).ToStringInvariant()}};
120+
while (lo <= hi && {{LookupKeyName}} >= _keys[lo] && {{LookupKeyName}} <= _keys[hi])
121+
{
122+
{{KeyTypeName}} loKey = _keys[lo];
123+
{{KeyTypeName}} hiKey = _keys[hi];
124+
125+
if (loKey == hiKey)
126+
{
127+
if (loKey == {{LookupKeyName}})
128+
{
129+
value = _values[lo];
130+
return true;
131+
}
132+
133+
break;
134+
}
135+
136+
double range = (double)hiKey - (double)loKey;
137+
double offset = (double){{LookupKeyName}} - (double)loKey;
138+
int i = lo + (int)((offset * (hi - lo)) / range);
139+
140+
if (i < lo)
141+
i = lo;
142+
else if (i > hi)
143+
i = hi;
144+
145+
{{KeyTypeName}} midKey = _keys[i];
146+
if (midKey == {{LookupKeyName}})
147+
{
148+
value = _values[i];
149+
return true;
150+
}
151+
if (midKey < {{LookupKeyName}})
152+
lo = i + 1;
153+
else
154+
hi = i - 1;
155+
}
156+
157+
value = default;
158+
return false;
159+
}
160+
""");
161+
}
162+
else
163+
{
164+
sb.Append($$"""
165+
int lo = 0;
166+
int hi = {{(keys.Length - 1).ToStringInvariant()}};
167+
while (lo <= hi)
168+
{
169+
int i = lo + ((hi - lo) >> 1);
170+
int order = {{GetCompareFunction("_keys[i]", LookupKeyName)}};
171+
172+
if (order == 0)
173+
{
174+
value = _values[i];
175+
return true;
176+
}
177+
if (order < 0)
178+
lo = i + 1;
179+
else
180+
hi = i - 1;
181+
}
182+
183+
value = default;
184+
return false;
185+
}
186+
""");
187+
}
188+
}
189+
90190
return sb.ToString();
91191
}
92192
}

0 commit comments

Comments
 (0)