Skip to content

Commit ed2f737

Browse files
authored
Merge pull request #89 from GerardSmit/fix/upath-combine-allocation
Reduce allocations in UPath.Combine
2 parents 16d85e5 + 7003422 commit ed2f737

File tree

1 file changed

+98
-9
lines changed

1 file changed

+98
-9
lines changed

src/Zio/UPath.cs

Lines changed: 98 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,33 @@ public static explicit operator string(UPath path)
129129
/// </exception>
130130
/// <exception cref="System.ArgumentException">If an error occurs while trying to combine paths.</exception>
131131
public static UPath Combine(UPath path1, UPath path2)
132+
{
133+
if (TryGetAbsolute(path1, path2, out var result))
134+
{
135+
return result;
136+
}
137+
138+
try
139+
{
140+
#if NET7_0_OR_GREATER
141+
return string.Create(path1.FullName.Length + path2.FullName.Length + 1, new KeyValuePair<UPath, UPath>(path1, path2), (span, state) =>
142+
{
143+
var (left, right) = state;
144+
left.FullName.AsSpan().CopyTo(span);
145+
span[left.FullName.Length] = '/';
146+
right.FullName.AsSpan().CopyTo(span.Slice(left.FullName.Length + 1));
147+
});
148+
#else
149+
return new UPath($"{path1.FullName}/{path2.FullName}");
150+
#endif
151+
}
152+
catch (ArgumentException ex)
153+
{
154+
throw new ArgumentException($"Unable to combine path `{path1}` with `{path2}`", ex);
155+
}
156+
}
157+
158+
private static bool TryGetAbsolute(UPath path1, UPath path2, out UPath result)
132159
{
133160
if (path1.FullName is null)
134161
throw new ArgumentNullException(nameof(path1));
@@ -138,26 +165,88 @@ public static UPath Combine(UPath path1, UPath path2)
138165

139166
// If the right path is absolute, it takes priority over path1
140167
if (path1.IsEmpty || path2.IsAbsolute)
141-
return path2;
142-
143-
try
144168
{
145-
return new UPath($"{path1.FullName}/{path2.FullName}");
146-
}
147-
catch (ArgumentException ex)
148-
{
149-
throw new ArgumentException($"Unable to combine path `{path1}` with `{path2}`", ex);
169+
result = path2;
170+
return true;
150171
}
172+
173+
result = default;
174+
return false;
151175
}
152176

153177
public static UPath Combine(UPath path1, UPath path2, UPath path3)
154178
{
179+
if (TryGetAbsolute(path1, path2, out var result))
180+
{
181+
return Combine(result, path3);
182+
}
183+
184+
if (TryGetAbsolute(path2, path3, out result))
185+
{
186+
return Combine(path1, result);
187+
}
188+
189+
#if NET7_0_OR_GREATER
190+
return string.Create(path1.FullName.Length + path2.FullName.Length + path3.FullName.Length + 2, (path1, path2, path3), (span, state) =>
191+
{
192+
var (p1, p2, p3) = state;
193+
var remaining = span;
194+
195+
p1.FullName.AsSpan().CopyTo(remaining);
196+
remaining[p1.FullName.Length] = '/';
197+
remaining = remaining.Slice(p1.FullName.Length + 1);
198+
199+
p2.FullName.AsSpan().CopyTo(remaining);
200+
remaining[p2.FullName.Length] = '/';
201+
202+
remaining = remaining.Slice(p2.FullName.Length + 1);
203+
p3.FullName.AsSpan().CopyTo(remaining);
204+
});
205+
#else
155206
return UPath.Combine(UPath.Combine(path1, path2), path3);
207+
#endif
156208
}
157209

158210
public static UPath Combine(UPath path1, UPath path2, UPath path3, UPath path4)
159211
{
160-
return UPath.Combine(Combine(path1, path2), Combine(path3, path4));
212+
if (TryGetAbsolute(path1, path2, out var result))
213+
{
214+
return Combine(result, path3, path4);
215+
}
216+
217+
if (TryGetAbsolute(path2, path3, out result))
218+
{
219+
return Combine(path1, result, path4);
220+
}
221+
222+
if (TryGetAbsolute(path3, path4, out result))
223+
{
224+
return Combine(path1, path2, result);
225+
}
226+
227+
#if NET7_0_OR_GREATER
228+
return string.Create(path1.FullName.Length + path2.FullName.Length + path3.FullName.Length + path4.FullName.Length + 3, (path1, path2, path3, path4), (span, state) =>
229+
{
230+
var (p1, p2, p3, p4) = state;
231+
var remaining = span;
232+
233+
p1.FullName.AsSpan().CopyTo(remaining);
234+
remaining[p1.FullName.Length] = '/';
235+
remaining = remaining.Slice(p1.FullName.Length + 1);
236+
237+
p2.FullName.AsSpan().CopyTo(remaining);
238+
remaining[p2.FullName.Length] = '/';
239+
remaining = remaining.Slice(p2.FullName.Length + 1);
240+
241+
p3.FullName.AsSpan().CopyTo(remaining);
242+
remaining[p3.FullName.Length] = '/';
243+
remaining = remaining.Slice(p3.FullName.Length + 1);
244+
245+
p4.FullName.AsSpan().CopyTo(remaining);
246+
});
247+
#else
248+
return UPath.Combine(UPath.Combine(path1, path2), UPath.Combine(path3, path4));
249+
#endif
161250
}
162251

163252
public static UPath Combine(params UPath[] paths)

0 commit comments

Comments
 (0)