Skip to content

Commit 1a1c319

Browse files
Merge pull request #1107 from SixLabors/js/parallel-allocation-experiments
Remove Closure Allocations when Iterating Rows in Parallel
2 parents 16abb95 + c66dd0c commit 1a1c319

File tree

54 files changed

+2974
-2022
lines changed

Some content is hidden

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

54 files changed

+2974
-2022
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// Copyright (c) Six Labors and contributors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
using System.Runtime.CompilerServices;
6+
using SixLabors.ImageSharp.Memory;
7+
8+
namespace SixLabors.ImageSharp.Advanced
9+
{
10+
/// <summary>
11+
/// Defines the contract for an action that operates on a row interval.
12+
/// </summary>
13+
public interface IRowIntervalOperation
14+
{
15+
/// <summary>
16+
/// Invokes the method passing the row interval.
17+
/// </summary>
18+
/// <param name="rows">The row interval.</param>
19+
void Invoke(in RowInterval rows);
20+
}
21+
}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
// Copyright (c) Six Labors and contributors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
using System.Buffers;
6+
using System.Runtime.CompilerServices;
7+
using SixLabors.ImageSharp.Memory;
8+
9+
namespace SixLabors.ImageSharp.Advanced
10+
{
11+
/// <summary>
12+
/// Defines the contract for an action that operates on a row interval with a temporary buffer.
13+
/// </summary>
14+
/// <typeparam name="TBuffer">The type of buffer elements.</typeparam>
15+
public interface IRowIntervalOperation<TBuffer>
16+
where TBuffer : unmanaged
17+
{
18+
/// <summary>
19+
/// Invokes the method passing the row interval and a buffer.
20+
/// </summary>
21+
/// <param name="rows">The row interval.</param>
22+
/// <param name="span">The contiguous region of memory.</param>
23+
void Invoke(in RowInterval rows, Span<TBuffer> span);
24+
}
25+
}

src/ImageSharp/Advanced/ParallelUtils/ParallelExecutionSettings.cs renamed to src/ImageSharp/Advanced/ParallelExecutionSettings.cs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,15 @@
1-
// Copyright (c) Six Labors and contributors.
1+
// Copyright (c) Six Labors and contributors.
22
// Licensed under the Apache License, Version 2.0.
33

44
using System;
55
using System.Threading.Tasks;
66

77
using SixLabors.ImageSharp.Memory;
88

9-
namespace SixLabors.ImageSharp.Advanced.ParallelUtils
9+
namespace SixLabors.ImageSharp.Advanced
1010
{
1111
/// <summary>
12-
/// Defines execution settings for methods in <see cref="ParallelHelper"/>.
12+
/// Defines execution settings for methods in <see cref="ParallelRowIterator"/>.
1313
/// </summary>
1414
public readonly struct ParallelExecutionSettings
1515
{
@@ -89,7 +89,7 @@ public ParallelExecutionSettings MultiplyMinimumPixelsPerTask(int multiplier)
8989
}
9090

9191
/// <summary>
92-
/// Get the default <see cref="SixLabors.ImageSharp.Advanced.ParallelUtils.ParallelExecutionSettings"/> for a <see cref="SixLabors.ImageSharp.Configuration"/>
92+
/// Get the default <see cref="SixLabors.ImageSharp.Advanced.ParallelExecutionSettings"/> for a <see cref="SixLabors.ImageSharp.Configuration"/>
9393
/// </summary>
9494
/// <param name="configuration">The <see cref="Configuration"/>.</param>
9595
/// <returns>The <see cref="ParallelExecutionSettings"/>.</returns>
Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
// Copyright (c) Six Labors and contributors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
using System.Buffers;
6+
using System.Runtime.CompilerServices;
7+
using System.Threading.Tasks;
8+
using SixLabors.ImageSharp.Memory;
9+
10+
namespace SixLabors.ImageSharp.Advanced
11+
{
12+
/// <content>
13+
/// Utility methods for batched processing of pixel row intervals.
14+
/// Parallel execution is optimized for image processing based on values defined
15+
/// <see cref="ParallelExecutionSettings"/> or <see cref="Configuration"/>.
16+
/// Using this class is preferred over direct usage of <see cref="Parallel"/> utility methods.
17+
/// </content>
18+
public static partial class ParallelRowIterator
19+
{
20+
private readonly struct IterationParameters
21+
{
22+
public readonly int MinY;
23+
public readonly int MaxY;
24+
public readonly int StepY;
25+
public readonly int Width;
26+
27+
public IterationParameters(int minY, int maxY, int stepY)
28+
: this(minY, maxY, stepY, 0)
29+
{
30+
}
31+
32+
public IterationParameters(int minY, int maxY, int stepY, int width)
33+
{
34+
this.MinY = minY;
35+
this.MaxY = maxY;
36+
this.StepY = stepY;
37+
this.Width = width;
38+
}
39+
}
40+
41+
private readonly struct RowIntervalOperationWrapper<T>
42+
where T : struct, IRowIntervalOperation
43+
{
44+
private readonly IterationParameters info;
45+
private readonly T operation;
46+
47+
[MethodImpl(InliningOptions.ShortMethod)]
48+
public RowIntervalOperationWrapper(in IterationParameters info, in T operation)
49+
{
50+
this.info = info;
51+
this.operation = operation;
52+
}
53+
54+
[MethodImpl(InliningOptions.ShortMethod)]
55+
public void Invoke(int i)
56+
{
57+
int yMin = this.info.MinY + (i * this.info.StepY);
58+
59+
if (yMin >= this.info.MaxY)
60+
{
61+
return;
62+
}
63+
64+
int yMax = Math.Min(yMin + this.info.StepY, this.info.MaxY);
65+
var rows = new RowInterval(yMin, yMax);
66+
67+
// Skip the safety copy when invoking a potentially impure method on a readonly field
68+
Unsafe.AsRef(in this.operation).Invoke(in rows);
69+
}
70+
}
71+
72+
private readonly struct RowIntervalOperationWrapper<T, TBuffer>
73+
where T : struct, IRowIntervalOperation<TBuffer>
74+
where TBuffer : unmanaged
75+
{
76+
private readonly IterationParameters info;
77+
private readonly MemoryAllocator allocator;
78+
private readonly T operation;
79+
80+
[MethodImpl(InliningOptions.ShortMethod)]
81+
public RowIntervalOperationWrapper(
82+
in IterationParameters info,
83+
MemoryAllocator allocator,
84+
in T operation)
85+
{
86+
this.info = info;
87+
this.allocator = allocator;
88+
this.operation = operation;
89+
}
90+
91+
[MethodImpl(InliningOptions.ShortMethod)]
92+
public void Invoke(int i)
93+
{
94+
int yMin = this.info.MinY + (i * this.info.StepY);
95+
96+
if (yMin >= this.info.MaxY)
97+
{
98+
return;
99+
}
100+
101+
int yMax = Math.Min(yMin + this.info.StepY, this.info.MaxY);
102+
var rows = new RowInterval(yMin, yMax);
103+
104+
using IMemoryOwner<TBuffer> buffer = this.allocator.Allocate<TBuffer>(this.info.Width);
105+
106+
Unsafe.AsRef(in this.operation).Invoke(in rows, buffer.Memory.Span);
107+
}
108+
}
109+
}
110+
}
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
// Copyright (c) Six Labors and contributors.
2+
// Licensed under the Apache License, Version 2.0.
3+
4+
using System;
5+
using System.Buffers;
6+
using System.Runtime.CompilerServices;
7+
using System.Threading.Tasks;
8+
using SixLabors.ImageSharp.Memory;
9+
10+
namespace SixLabors.ImageSharp.Advanced
11+
{
12+
/// <summary>
13+
/// Utility methods for batched processing of pixel row intervals.
14+
/// Parallel execution is optimized for image processing based on values defined
15+
/// <see cref="ParallelExecutionSettings"/> or <see cref="Configuration"/>.
16+
/// Using this class is preferred over direct usage of <see cref="Parallel"/> utility methods.
17+
/// </summary>
18+
public static partial class ParallelRowIterator
19+
{
20+
/// <summary>
21+
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s.
22+
/// </summary>
23+
/// <typeparam name="T">The type of row operation to perform.</typeparam>
24+
/// <param name="configuration">The <see cref="Configuration"/> to get the parallel settings from.</param>
25+
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
26+
/// <param name="operation">The operation defining the iteration logic on a single <see cref="RowInterval"/>.</param>
27+
[MethodImpl(InliningOptions.ShortMethod)]
28+
public static void IterateRows<T>(Configuration configuration, Rectangle rectangle, in T operation)
29+
where T : struct, IRowIntervalOperation
30+
{
31+
var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
32+
IterateRows(rectangle, in parallelSettings, in operation);
33+
}
34+
35+
/// <summary>
36+
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s.
37+
/// </summary>
38+
/// <typeparam name="T">The type of row operation to perform.</typeparam>
39+
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
40+
/// <param name="parallelSettings">The <see cref="ParallelExecutionSettings"/>.</param>
41+
/// <param name="operation">The operation defining the iteration logic on a single <see cref="RowInterval"/>.</param>
42+
public static void IterateRows<T>(
43+
Rectangle rectangle,
44+
in ParallelExecutionSettings parallelSettings,
45+
in T operation)
46+
where T : struct, IRowIntervalOperation
47+
{
48+
ValidateRectangle(rectangle);
49+
50+
int top = rectangle.Top;
51+
int bottom = rectangle.Bottom;
52+
int width = rectangle.Width;
53+
int height = rectangle.Height;
54+
55+
int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask);
56+
int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
57+
58+
// Avoid TPL overhead in this trivial case:
59+
if (numOfSteps == 1)
60+
{
61+
var rows = new RowInterval(top, bottom);
62+
Unsafe.AsRef(in operation).Invoke(in rows);
63+
return;
64+
}
65+
66+
int verticalStep = DivideCeil(rectangle.Height, numOfSteps);
67+
var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps };
68+
var info = new IterationParameters(top, bottom, verticalStep);
69+
var wrappingOperation = new RowIntervalOperationWrapper<T>(in info, in operation);
70+
71+
Parallel.For(
72+
0,
73+
numOfSteps,
74+
parallelOptions,
75+
wrappingOperation.Invoke);
76+
}
77+
78+
/// <summary>
79+
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s
80+
/// instantiating a temporary buffer for each <paramref name="operation"/> invocation.
81+
/// </summary>
82+
/// <typeparam name="T">The type of row operation to perform.</typeparam>
83+
/// <typeparam name="TBuffer">The type of buffer elements.</typeparam>
84+
/// <param name="configuration">The <see cref="Configuration"/> to get the parallel settings from.</param>
85+
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
86+
/// <param name="operation">The operation defining the iteration logic on a single <see cref="RowInterval"/>.</param>
87+
public static void IterateRows<T, TBuffer>(Configuration configuration, Rectangle rectangle, in T operation)
88+
where T : struct, IRowIntervalOperation<TBuffer>
89+
where TBuffer : unmanaged
90+
{
91+
var parallelSettings = ParallelExecutionSettings.FromConfiguration(configuration);
92+
IterateRows<T, TBuffer>(rectangle, in parallelSettings, in operation);
93+
}
94+
95+
/// <summary>
96+
/// Iterate through the rows of a rectangle in optimized batches defined by <see cref="RowInterval"/>-s
97+
/// instantiating a temporary buffer for each <paramref name="operation"/> invocation.
98+
/// </summary>
99+
/// <typeparam name="T">The type of row operation to perform.</typeparam>
100+
/// <typeparam name="TBuffer">The type of buffer elements.</typeparam>
101+
/// <param name="rectangle">The <see cref="Rectangle"/>.</param>
102+
/// <param name="parallelSettings">The <see cref="ParallelExecutionSettings"/>.</param>
103+
/// <param name="operation">The operation defining the iteration logic on a single <see cref="RowInterval"/>.</param>
104+
public static void IterateRows<T, TBuffer>(
105+
Rectangle rectangle,
106+
in ParallelExecutionSettings parallelSettings,
107+
in T operation)
108+
where T : struct, IRowIntervalOperation<TBuffer>
109+
where TBuffer : unmanaged
110+
{
111+
ValidateRectangle(rectangle);
112+
113+
int top = rectangle.Top;
114+
int bottom = rectangle.Bottom;
115+
int width = rectangle.Width;
116+
int height = rectangle.Height;
117+
118+
int maxSteps = DivideCeil(width * height, parallelSettings.MinimumPixelsProcessedPerTask);
119+
int numOfSteps = Math.Min(parallelSettings.MaxDegreeOfParallelism, maxSteps);
120+
MemoryAllocator allocator = parallelSettings.MemoryAllocator;
121+
122+
// Avoid TPL overhead in this trivial case:
123+
if (numOfSteps == 1)
124+
{
125+
var rows = new RowInterval(top, bottom);
126+
using (IMemoryOwner<TBuffer> buffer = allocator.Allocate<TBuffer>(width))
127+
{
128+
Unsafe.AsRef(operation).Invoke(in rows, buffer.Memory.Span);
129+
}
130+
131+
return;
132+
}
133+
134+
int verticalStep = DivideCeil(height, numOfSteps);
135+
var parallelOptions = new ParallelOptions { MaxDegreeOfParallelism = numOfSteps };
136+
var info = new IterationParameters(top, bottom, verticalStep, width);
137+
var wrappingOperation = new RowIntervalOperationWrapper<T, TBuffer>(in info, allocator, in operation);
138+
139+
Parallel.For(
140+
0,
141+
numOfSteps,
142+
parallelOptions,
143+
wrappingOperation.Invoke);
144+
}
145+
146+
[MethodImpl(InliningOptions.ShortMethod)]
147+
private static int DivideCeil(int dividend, int divisor) => 1 + ((dividend - 1) / divisor);
148+
149+
private static void ValidateRectangle(Rectangle rectangle)
150+
{
151+
Guard.MustBeGreaterThan(
152+
rectangle.Width,
153+
0,
154+
$"{nameof(rectangle)}.{nameof(rectangle.Width)}");
155+
156+
Guard.MustBeGreaterThan(
157+
rectangle.Height,
158+
0,
159+
$"{nameof(rectangle)}.{nameof(rectangle.Height)}");
160+
}
161+
}
162+
}

0 commit comments

Comments
 (0)