问题
I have a function that looks something like the code below. Its purpose is to take triangle facets one at a time from an array of points where each three points is one facet, and tessellate them, replacing the facet with a list of smaller facets whose side lengths don't exceed nodeSize.
Naturally, this function is time consuming for any realistic facet mesh. I'd like to refactor it to use some coarse parallelization. However, Parallel.For doesn't appear to have a way to step through indices in an array at intervals while preserving the index number.
Bearing in mind that the SplitTriangle
function inside the loop is computationally not conducive itself to parallelization, how could I refactor this function?
Protected Shared Function SplitTriangles(Points As IEnumerable(Of Point3D), nodeSize As Single) As List(Of Point3D)
Dim resultList As New List(Of Point3D)
For i As Integer = 0 To Points.Count - 1 Step 3
resultList.AddRange(SplitTriangle(Points(i), Points(i + 1), Points(i + 2), nodeSize * 4))
Next
Return resultList
End Function
回答1:
You could use the Enumerable.Range()
function to generate the indices for you. I'm not overly familiar with VB.NET, so I'm going to write this in C#.
Enumerable.Range(0, Points.Count / 3).AsParallel().ForAll( loopVar => {
var i = loopVar * 3;
resultList.AddRange(SplitTriangle(Points(i), Points(i + 1), Points(i + 2), nodeSize * 4))
});
UPDATE I think this version would be thread safe, but you should check to ensure that it is and that the results are in the correct order.
resultList = Enumerable.Range(0, Points.Count / 3).AsParallel().SelectMany( loopVar => {
var i = loopVar * 3;
return SplitTriangle(Points(i), Points(i + 1), Points(i + 2), nodeSize * 4);
});
回答2:
I think the easiest solution here is to first go over the points and split them into an array of 3-point groups. Then you can use Parallel.For
on that array.
EDIT: Since you have millions of points and do this all the time, you should do something else.
First, make sure your Points
container allows easy random access (use an array or a List
). Then do this:
- Allocate the
resultList
with the proper size. - Divide
Points
into several parts ('several' can be hard to estimate, you should play with this a little bit). Say your list has 12,000,000 points, soresultList
is 4,000,000 element long. And say you decide 4 parts is the optimal split.- Each part has to be consecutive (0-3M, 3M-6M, 6M-9M, 9M-12M).
- Finding the optimal split is not easy, but trivial splits might prove good enough, so don't worry about it for now.
- Have 4 threads, each processing one part (you can use the Task API, it would make the code clearer, in my opinion, than a
Parallel.For
in this case.
NOTE about thread safety:
I'm not 100% convinced a List<Point>
is thread safe when you use it as a fixed-size array. It should be, but if you want to be 100% sure use an array.
回答3:
Your code will be much simpler if instead of passing an IEnumerable of Points that, by convention, you treat as triangles, you pass the triangles directly.
If you convert the points to triangles, you can write the following with PLINQ:
Function SplitTriangles(triangles As IEnumerable(Of Triangle3D), nodeSize As Single) As List(Of Triangle3D)
Dim resultList As New List(Of Point3D)
Dim results = (From triangle In triangles.AsParallel()
From newTriangle In SplitTriangle(triangle.A, triangle.B, triangle.C)
Select newTriangle).ToList()
Return results
End Function
AsParallel doesn't preserver ordering but you can enforce this by adding .AsOrdered after AsParallel
Using Parallel.For requires the use of the overload that accepts local initializers and finalizers, to collect the results of each computation. The final code is much more complex for no benefit in this case.
The problem with IEnumerable(Of Point3) is that there is a very strong relation between the points of each triplet. Parallel.For/Foreach are suited for lists where each item is independent of the others.
You can convert the initial list of points to a list of triangles using an adaptation of Marc Gravell's Partition method here, to group points in triplets and return a triangle instead of an IEnumerable.
A simpler but less generic solution would be to create an iterator that returns Triangles from Points:
Iterator Function Triangulize(points As IEnumerable(Of Point3D)) As IEnumerable(Of Triangle3D)
Dim count As Integer = 0
Dim buffer(3) As Point3D
For Each point As Point3D In points
buffer(count) = point
count += 1
If count = 3 Then
Yield New Triangle3D(buffer(0), buffer(1), buffer(2))
count = 0
End If
Next
End Function
来源:https://stackoverflow.com/questions/13593342/parallelizing-a-for-loop-with-stepping-in-net-4-5