Skip to content

Commit 92fd6e6

Browse files
committed
impl TakeSkip optimization
1 parent 7495b6d commit 92fd6e6

File tree

6 files changed

+396
-23
lines changed

6 files changed

+396
-23
lines changed

src/FileGen/DropinGen.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,11 @@ internal static partial class ZLinqDropInExtensions
109109
return null;
110110
}
111111

112+
if (methodInfo.Name is "Skip" && methodInfo.ReturnType.ToString().Contains("TakeSkip"))
113+
{
114+
return null;
115+
}
116+
112117
if (methodInfo.Name is "Contains" && !methodInfo.GetGenericArguments().Any(x => x.Name == "TEnumerator"))
113118
{
114119
return null;

src/ZLinq/Linq/Select.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ public void Dispose()
9999
source.Dispose();
100100
}
101101

102-
public SelectWhere<TEnumerator, TSource, TResult> Where(Func<TResult, bool> predicate)
102+
internal SelectWhere<TEnumerator, TSource, TResult> Where(Func<TResult, bool> predicate)
103103
=> new(source, selector, predicate);
104104
}
105105

src/ZLinq/Linq/Skip.cs

Lines changed: 142 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,4 +125,146 @@ public void Dispose()
125125
source.Dispose();
126126
}
127127
}
128+
129+
[StructLayout(LayoutKind.Auto)]
130+
[EditorBrowsable(EditorBrowsableState.Never)]
131+
#if NET9_0_OR_GREATER
132+
public ref
133+
#else
134+
public
135+
#endif
136+
struct SkipTake<TEnumerator, TSource>(TEnumerator source, Int32 skipCount, Int32 takeCount)
137+
: IValueEnumerator<TSource>
138+
where TEnumerator : struct, IValueEnumerator<TSource>
139+
#if NET9_0_OR_GREATER
140+
, allows ref struct
141+
#endif
142+
{
143+
TEnumerator source = source;
144+
internal readonly int skipCount = Math.Max(0, skipCount); // ensure non-negative
145+
internal readonly int takeCount = Math.Max(0, takeCount); // ensure non-negative
146+
int skipped;
147+
int taken;
148+
149+
public bool TryGetNonEnumeratedCount(out int count)
150+
{
151+
if (source.TryGetNonEnumeratedCount(out count))
152+
{
153+
// Calculate elements after skipping
154+
count = Math.Max(0, count - skipCount);
155+
// Apply take limit
156+
count = Math.Min(count, takeCount);
157+
return true;
158+
}
159+
160+
count = default;
161+
return false;
162+
}
163+
164+
public bool TryCopyTo(Span<TSource> destination, Index offset)
165+
{
166+
if (source.TryGetNonEnumeratedCount(out var sourceCount))
167+
{
168+
// Determine actual number of elements after skipping
169+
var actualSkipCount = Math.Min(sourceCount, skipCount);
170+
var availableAfterSkip = sourceCount - actualSkipCount;
171+
172+
// Apply take limit
173+
var actualCount = Math.Min(availableAfterSkip, takeCount);
174+
175+
if (actualCount <= 0)
176+
{
177+
return false;
178+
}
179+
180+
// Calculate offset within the resulting sequence
181+
var offsetInResult = offset.GetOffset(actualCount);
182+
183+
if (offsetInResult < 0 || offsetInResult >= actualCount)
184+
{
185+
return false;
186+
}
187+
188+
// Calculate offset in source sequence (skip + offset)
189+
var sourceOffset = actualSkipCount + offsetInResult;
190+
191+
// Calculate elements available after offset
192+
var elementsAvailable = actualCount - offsetInResult;
193+
194+
// Calculate elements to copy
195+
var elementsToCopy = Math.Min(elementsAvailable, destination.Length);
196+
197+
if (elementsToCopy <= 0)
198+
{
199+
return false;
200+
}
201+
202+
return source.TryCopyTo(destination.Slice(0, elementsToCopy), sourceOffset);
203+
}
204+
205+
return false;
206+
}
207+
208+
public bool TryGetSpan(out ReadOnlySpan<TSource> span)
209+
{
210+
if (source.TryGetSpan(out span))
211+
{
212+
// Skip elements
213+
if (span.Length <= skipCount)
214+
{
215+
span = default;
216+
return true;
217+
}
218+
219+
span = span.Slice(skipCount);
220+
221+
// Take elements
222+
if (span.Length > takeCount)
223+
{
224+
span = span.Slice(0, takeCount);
225+
}
226+
227+
return true;
228+
}
229+
230+
span = default;
231+
return false;
232+
}
233+
234+
public bool TryGetNext(out TSource current)
235+
{
236+
// Skip elements if not already skipped
237+
while (skipped < skipCount)
238+
{
239+
if (!source.TryGetNext(out var _))
240+
{
241+
Unsafe.SkipInit(out current);
242+
return false;
243+
}
244+
skipped++;
245+
}
246+
247+
// Check if we've reached the take limit
248+
if (taken >= takeCount)
249+
{
250+
Unsafe.SkipInit(out current);
251+
return false;
252+
}
253+
254+
// Return elements after skipping and before take limit
255+
if (source.TryGetNext(out current))
256+
{
257+
taken++;
258+
return true;
259+
}
260+
261+
Unsafe.SkipInit(out current);
262+
return false;
263+
}
264+
265+
public void Dispose()
266+
{
267+
source.Dispose();
268+
}
269+
}
128270
}

0 commit comments

Comments
 (0)