Parallelizing a for loop with stepping in .net 4.5

試著忘記壹切 提交于 2020-01-04 04:12:21

问题


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, so resultList 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

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!