-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathSubjects.cs
More file actions
244 lines (202 loc) · 9.65 KB
/
Copy pathSubjects.cs
File metadata and controls
244 lines (202 loc) · 9.65 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
using System.Numerics.Tensors;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Runtime.Intrinsics;
namespace LinqSumProof;
/// <summary>
/// Суммирование массива: свой цикл против LINQ Sum() против своего
/// вектора против TensorPrimitives.
///
/// Ключевое: начиная с .NET 8 Enumerable.Sum() на int/long векторизован,
/// когда источник представим как ReadOnlySpan (массивы и List) —
/// PR dotnet/runtime #84519. А обычный for со сложением — скаляр:
/// RyuJIT не векторизует произвольный цикл (issue #12466, с 2019).
/// То есть LINQ здесь БЫСТРЕЕ цикла.
///
/// Пруфы:
/// Векторизация Sum - PR #84519 (brantburnett, ревью tannergooding)
/// https://github.com/dotnet/runtime/pull/84519
/// Автовекторизация RyuJIT - issue #12466
/// https://github.com/dotnet/runtime/issues/12466
/// Sum остаётся checked: в векторном пути переполнение отслеживается
/// отдельным вектором overflowTracking по знаковому биту - исходник
/// Sum.cs (SumSignedIntegersVectorized)
/// https://github.com/dotnet/runtime/blob/main/src/libraries/System.Linq/src/System/Linq/Sum.cs
/// Порядок сложения изменился, OverflowException может вылетать иначе -
/// официальный breaking change .NET 8 + issue #92230
/// https://learn.microsoft.com/dotnet/core/compatibility/core-libraries/8.0/enumerable-sum
/// https://github.com/dotnet/runtime/issues/92230
///
/// NoInlining на методах - только чтобы каждый печатался в листинге
/// отдельно и под своим именем. На сам цикл инлайн не влияет.
/// </summary>
public static class Subjects
{
// ------------------------------------------------------------------
// Свой цикл: скаляр, один элемент за итерацию.
// Сложение unchecked (дефолт C#) - в отличие от Sum().
// ------------------------------------------------------------------
[MethodImpl(MethodImplOptions.NoInlining)]
public static int SumFor(int[] values)
{
int sum = 0;
for (int i = 0; i < values.Length; i++)
{
sum += values[i];
}
return sum;
}
// ------------------------------------------------------------------
// Тот же цикл, но checked - та же гарантия OverflowException,
// что и у Sum(). Для сравнения на равных условиях.
// ------------------------------------------------------------------
[MethodImpl(MethodImplOptions.NoInlining)]
public static int SumForChecked(int[] values)
{
int sum = 0;
for (int i = 0; i < values.Length; i++)
{
checked
{
sum += values[i];
}
}
return sum;
}
// ------------------------------------------------------------------
// Развёрнутый цикл, четыре аккумулятора. Закрывает возражение
// "разверни цикл - догонишь": это всё равно скаляр, вектора
// в листинге не появится.
// ------------------------------------------------------------------
[MethodImpl(MethodImplOptions.NoInlining)]
public static int SumForUnrolled(int[] values)
{
int sum0 = 0;
int sum1 = 0;
int sum2 = 0;
int sum3 = 0;
int length = values.Length;
int i = 0;
for (; i <= length - 4; i += 4)
{
sum0 += values[i];
sum1 += values[i + 1];
sum2 += values[i + 2];
sum3 += values[i + 3];
}
for (; i < length; i++)
{
sum0 += values[i];
}
return sum0 + sum1 + sum2 + sum3;
}
// ------------------------------------------------------------------
// foreach по массиву - компилируется в тот же индексный проход.
// ------------------------------------------------------------------
[MethodImpl(MethodImplOptions.NoInlining)]
public static int SumForeach(int[] values)
{
int sum = 0;
foreach (int value in values)
{
sum += value;
}
return sum;
}
// ------------------------------------------------------------------
// LINQ на массиве. Внутри TryGetSpan -> SumSignedIntegersVectorized:
// вектор плюс слежка за переполнением.
// ------------------------------------------------------------------
[MethodImpl(MethodImplOptions.NoInlining)]
public static int SumLinq(int[] values) => values.Sum();
// ------------------------------------------------------------------
// Тот же Sum(), но через параметр-интерфейс. Проверка типа у Sum
// рантаймная, а не компайл-таймная: массив под интерфейсом всё
// равно уходит в векторный путь. Контраст с foreach, где всё
// решает тип переменной на этапе компиляции.
// ------------------------------------------------------------------
[MethodImpl(MethodImplOptions.NoInlining)]
public static int SumLinqInterface(IEnumerable<int> values) => values.Sum();
// ------------------------------------------------------------------
// Граница векторизации: ленивый итератор в span не превращается,
// Sum() честно крутит MoveNext/Current. Тут LINQ медленный,
// и мы это показываем сами, не дожидаясь комментов.
// ------------------------------------------------------------------
[MethodImpl(MethodImplOptions.NoInlining)]
public static int SumLinqIterator(int[] values) => Iterate(values).Sum();
private static IEnumerable<int> Iterate(int[] values)
{
for (int i = 0; i < values.Length; i++)
{
yield return values[i];
}
}
// ------------------------------------------------------------------
// List<int>: цикл по индексатору против Sum().
// ------------------------------------------------------------------
[MethodImpl(MethodImplOptions.NoInlining)]
public static int SumForList(List<int> values)
{
int sum = 0;
for (int i = 0; i < values.Count; i++)
{
sum += values[i];
}
return sum;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static int SumLinqList(List<int> values) => values.Sum();
// ------------------------------------------------------------------
// Свой вектор без слежки за переполнением. Сумма по горизонтали
// в конце, хвост скаляром. Может обогнать Sum() - но ценой
// гарантии OverflowException: при переполнении просто заворачивается.
// ------------------------------------------------------------------
[MethodImpl(MethodImplOptions.NoInlining)]
public static int SumVector256(int[] values)
{
int sum = 0;
int i = 0;
int length = values.Length;
if (Vector256.IsHardwareAccelerated && length >= Vector256<int>.Count)
{
Vector256<int> acc = Vector256<int>.Zero;
int step = Vector256<int>.Count;
ref int start = ref MemoryMarshal.GetArrayDataReference(values);
for (; i <= length - step; i += step)
{
Vector256<int> block = Vector256.LoadUnsafe(ref start, (nuint)i);
acc += block;
}
sum = Vector256.Sum(acc);
}
for (; i < length; i++)
{
sum += values[i];
}
return sum;
}
// ------------------------------------------------------------------
// TensorPrimitives.Sum (System.Numerics.Tensors) - векторный и
// unchecked: при переполнении заворачивается, исключения нет.
// Семантика другая, и в статье это оговаривается.
// ------------------------------------------------------------------
[MethodImpl(MethodImplOptions.NoInlining)]
public static int SumTensor(int[] values) => TensorPrimitives.Sum(values);
// ------------------------------------------------------------------
// Контраст: float. Sum() на float НЕ векторизован - сложение
// неассоциативно, порядок менять нельзя (PR #84519 покрывает
// только int/long). Аккумулятор у BCL - double.
// ------------------------------------------------------------------
[MethodImpl(MethodImplOptions.NoInlining)]
public static float SumForFloat(float[] values)
{
float sum = 0f;
for (int i = 0; i < values.Length; i++)
{
sum += values[i];
}
return sum;
}
[MethodImpl(MethodImplOptions.NoInlining)]
public static float SumLinqFloat(float[] values) => values.Sum();
}