joi, 9 februarie 2012

PLINQ

Parallel LINQ (PLINQ)
Parallel LINQ, also known as PLINQ, is a parallel implementation of LINQ to objects. PLINQ
implements all the LINQ query extension methods and has additional operators for parallel
operations. The degree of concurrency for PLINQ queries is based on the capabilities of the
computer running the query.
In many, but not all, scenarios, PLINQ can provide a significant increase in speed by using
all available CPUs or CPU cores. A PLINQ query can provide performance gains when
you have CPU-intensive operations that can be paralleled, or divided, across each CPU or
CPU core. The more computationally expensive the work is, the greater the opportunity for
performance gain. For example, if the workload takes 100 milliseconds to execute, a sequential
query over 400 elements will take 40 seconds to complete the work, whereas a parallel
query on a computer with eight cores might take only 5 seconds. This yields a speedup of 35
seconds.
One problem with Windows applications is that when you try to update a control
on your form from a thread other than the thread that created the control, an
InvalidOperationException
is thrown with the message, “Cross-thread operation not valid:
Control ‘txtLog’ accessed from a thread other than the thread it was created on.” To work with
threading, update in a thread-safe way the following extension method for TextBox to the
TextBoxHelper class.
Sample of Visual Basic Code
<Extension()> _
Public Sub WriteLine(ByVal txt As TextBox, _
ByVal format As String, _
ByVal ParamArray parms As Object())
Dim line As String = String.Format((format & Environment.NewLine), parms)
If txt.InvokeRequired Then
txt.BeginInvoke(New Action(Of String)(AddressOf txt.AppendText), _
New Object() {line})
Else
txt.AppendText(line)
224 CHAPTER 3 Introducing LINQ
End If
End Sub
Sample of C# Code
public static void WriteLine(this TextBox txt,
string format, params object[] parms)
{
string line = string.Format(format + Environment.NewLine, parms);
if (txt.InvokeRequired)
{
txt.BeginInvoke((Action<string>)txt.AppendText, line);
}
else
{
txt.AppendText(line);
}
}
You use the Invoke or BeginInvoke method on the TextBox class to marshal the callback to
the thread that was used to create the UI control. The BeginInvoke method posts an internal
dedicated Windows message to the UI thread message queue and returns immediately, which
helps avoid thread deadlock situations.
This extension method checks the TextBox object to see whether marshaling is required. If
marshaling is required (i.e., when the calling thread is not the one used to create the TextBox
object), the BeginInvoke method is executed. If marshaling is not required, the AppendText
method is called directly on the TextBox object. The BeginInvoke method takes Delegate as
a parameter, so txt.AppendText is cast to an action of String, a general-purpose delegate
that exists in the framework, which represents a call to a method that takes a string parameter.
Now that there is a thread-safe way to display information into the TextBox class, the
AsParallel
example can be performed without risking threading-related exceptions.
AsParallel Extension Method
The AsParallel extension method divides work onto each processor or processor core. The
following code sample starts a stopwatch in the System.Diagnostics namespace to show you
the elapsed time when completed, and then the Enumerable class produces a sequence of
integers, from 1 to 10. The AsParallel method call is added to the source. This causes the
iterations to be spread across the available processor and processor cores. Then a LINQ
query retrieves all the even numbers, but in the LINQ query, the where clause is calling a
Compute method, which has a one-second delay using the Thread class, which is in the
System.
Threading
namespace. Finally, a foreach loop displays the results.
Sample of Visual Basic Code
Private Sub AsParallelToolStripMenuItem_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles AsParallelToolStripMenuItem.Click
Dim sw As New Stopwatch
sw.Start()

Dim source = Enumerable.Range(1, 10).AsParallel()
Dim evenNums = From num In source
Where Compute(num) Mod 2 = 0
Select num
For Each ev In evenNums
txtLog.WriteLine("{0} on Thread {1}", _
New Object() {ev, Thread.CurrentThread.GetHashCode})
Next
sw.Stop()
txtLog.WriteLine("Done {0}", New Object() {sw.Elapsed})
End Sub
Public Function Compute(ByVal num As Integer) As Integer
txtLog.WriteLine("Computing {0} on Thread {1}", _
New Object() {num, Thread.CurrentThread.GetHashCode})
Thread.Sleep(1000)
Return num
End Function
Sample of C# Code
private void asParallelToolStripMenuItem_Click(
object sender, EventArgs e)
{
Stopwatch sw = new Stopwatch();
sw.Start();
var source = Enumerable.Range(1, 10).AsParallel();
var evenNums = from num in source
where Compute(num) % 2 == 0
select num;
foreach (var ev in evenNums)
{
txtLog.WriteLine("{0} on Thread {1}", ev,
Thread.CurrentThread.GetHashCode());
}
sw.Stop();
txtLog.WriteLine("Done {0}", sw.Elapsed);
}
public int Compute(int num)
{
txtLog.WriteLine("Computing {0} on Thread {1}", num,
Thread.CurrentThread.GetHashCode());
Thread.Sleep(1000);
return num;
}
AsEnumerable results, showing even numbers, total time, and computing method:
6 on Thread 10
2 on Thread 10
4 on Thread 10
8 on Thread 10
10 on Thread 10
Done 00:00:05.0393262
Computing 1 on Thread 12
226 CHAPTER 3 Introducing LINQ
Computing 2 on Thread 11
Computing 3 on Thread 12
Computing 4 on Thread 11
Computing 5 on Thread 11
Computing 6 on Thread 12
Computing 7 on Thread 12
Computing 8 on Thread 11
Computing 9 on Thread 12
Computing 10 on Thread 11
The output from the Compute calls always shows after the foreach (Visual Basic For Each)
loop output because BeginInvoke marshalls calls to the UI thread for execution when the
UI thread is available. The foreach loop is running on the UI thread, so the thread is busy
until the loop completes. The results are not ordered. Your result will vary as well, and, in
some cases, the results might be ordered. In the example, you can see that the foreach loop
displayed the even numbers, using the main thread of the application, which was thread 10
on this computer. The Compute method was executed on a different thread, but the thread
is either 11 or 12 because this is a two-core processor. Although the Compute method has a
one-second delay, it took five seconds to execute because only two threads were allocated,
one for each core.
In an effort to get a clearer picture of PLINQ, the writing to a TextBox has been replaced
in the following code. Instead of using TextBox, Debug.WriteLine is used, which removes the
requirement to marshall calls back to the UI thread.
Sample of Visual Basic Code
Private Sub AsParallel2ToolStripMenuItem_Click( _
ByVal sender As System.Object, ByVal e As System.EventArgs) _
Handles AsParallel2ToolStripMenuItem.Click
Dim sw As New Stopwatch
sw.Start()
Dim source = Enumerable.Range(1, 10).AsParallel()
Dim evenNums = From num In source
Where Compute2(num) Mod 2 = 0
Select num
For Each ev In evenNums
Debug.WriteLine(String.Format("{0} on Thread {1}", _
New Object() {ev, Thread.CurrentThread.GetHashCode}))
Next
sw.Stop()
Debug.WriteLine(String.Format("Done {0}", New Object() {sw.Elapsed}))
End Sub
Sample of C# Code
private void asParallel2ToolStripMenuItem_Click(
object sender, EventArgs e)
{
Stopwatch sw = new Stopwatch();
sw.Start();
var source = Enumerable.Range(1, 10).AsParallel();
var evenNums = from num in source

where Compute2(num) % 2 == 0
select num;
foreach (var ev in evenNums)
{
Debug.WriteLine(string.Format("{0} on Thread {1}", ev,
Thread.CurrentThread.GetHashCode()));
}
sw.Stop();
Debug.WriteLine(string.Format("Done {0}", sw.Elapsed));
}
public int Compute2(int num)
{
Debug.WriteLine(string.Format("Computing {0} on Thread {1}", num,
Thread.CurrentThread.GetHashCode()));
Thread.Sleep(1000);
return num;
}
The result:
Computing 2 on Thread 10
Computing 1 on Thread 6
Computing 3 on Thread 10
Computing 4 on Thread 6
Computing 5 on Thread 10
Computing 6 on Thread 6
Computing 7 on Thread 10
Computing 8 on Thread 6
Computing 9 on Thread 10
Computing 10 on Thread 6
2 on Thread 9
4 on Thread 9
6 on Thread 9
8 on Thread 9
10 on Thread 9
Done 00:00:05.0632071
The result, which is in the Visual Studio .NET Output window, shows that there is no waiting
for the UI thread. Once again, your result will vary based on your hardware configuration.
ForAll Extension Method
When the query is iterated by using a foreach (Visual Basic For Each) loop, each iteration is
synchronized in the same thread, to be treated one after the other in the order of the sequence.
If you just want to perform each iteration in parallel, without any specific order, use
the ForAll method. It has the same effect as performing each iteration in a different thread.
Analyze this technique to verify that you get the performance gain you expect. The following
example shows the use of the ForAll method instead of the For Each (C# foreach) loop.
Sample of Visual Basic Code
Private Sub ForAllToolStripMenuItem_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
228 CHAPTER 3 Introducing LINQ
Handles ForAllToolStripMenuItem.Click
Dim sw As New Stopwatch
sw.Start()
Dim source = Enumerable.Range(1, 10).AsParallel()
Dim evenNums = From num In source
Where Compute2(num) Mod 2 = 0
Select num
evenNums.ForAll(Sub(ev) Debug.WriteLine(string.Format(
"{0} on Thread {1}", ev, _
Thread.CurrentThread.GetHashCode())))
sw.Stop()
Debug.WriteLine((string.Format("Done {0}", New Object() {sw.Elapsed}))
End Sub
Sample of C# Code
private void forAllToolStripMenuItem_Click(object sender, EventArgs e)
{
Stopwatch sw = new Stopwatch();
sw.Start();
var source = Enumerable.Range(1, 10).AsParallel();
var evenNums = from num in source
where Compute(num) % 2 == 0
select num;
evenNums.ForAll(ev => Debug.WriteLine(string.Format(
"{0} on Thread {1}", ev,
Thread.CurrentThread.GetHashCode())));
sw.Stop();
Debug.WriteLine(string.Format("Done {0}", sw.Elapsed));
}
ForAll result, showing even numbers, total time, and computing method:
Computing 1 on Thread 9
Computing 2 on Thread 10
Computing 3 on Thread 9
2 on Thread 10
Computing 4 on Thread 10
Computing 5 on Thread 9
4 on Thread 10
Computing 6 on Thread 10
Computing 7 on Thread 9
6 on Thread 10
Computing 8 on Thread 10
Computing 9 on Thread 9
8 on Thread 10
Computing 10 on Thread 10
10 on Thread 10
Done 00:00:05.0556551
Like the previous example, the results are not guaranteed to be ordered, and there is no
attempt to put the results in a particular order. This technique can give you better performance
as long as this behavior is acceptable.

AsOrdered Extension Method
Sometimes, you must maintain the order in your query, but you still want parallel execution.
Although this will come at a cost, it’s doable by using the AsOrdered extension method. The
following example shows how you can add this method call right after the AsParallel method
to maintain order.
Sample of Visual Basic Code
Private Sub AsOrderedToolStripMenuItem_Click(ByVal sender As System.Object, _
ByVal e As System.EventArgs) _
Handles AsOrderedToolStripMenuItem.Click
Dim sw As New Stopwatch
sw.Start()
Dim source = Enumerable.Range(1, 10).AsParallel().AsOrdered()
Dim evenNums = From num In source
Where Compute2(num) Mod 2 = 0
Select num
evenNums.ForAll(Sub(ev) Debug.WriteLine(string.Format(
"{0} on Thread {1}", ev, _
Thread.CurrentThread.GetHashCode())))
sw.Stop()
Debug.WriteLine(string.Format("Done {0}", New Object() {sw.Elapsed}))
End Sub
Sample of C# Code
private void asOrderedToolStripMenuItem_Click(object sender, EventArgs e)
{
Stopwatch sw = new Stopwatch();
sw.Start();
var source = Enumerable.Range(1, 10).AsParallel().AsOrdered();
var evenNums = from num in source
where Compute2(num) % 2 == 0
select num;
evenNums.ForAll(ev => Debug.WriteLine(string.Format(
"{0} on Thread {1}", ev,
Thread.CurrentThread.GetHashCode())));
sw.Stop();
Debug.WriteLine(string.Format("Done {0}", sw.Elapsed));
}
AsOrdered result, showing even numbers, total time, and computing method:
Computing 2 on Thread 11
Computing 1 on Thread 10
2 on Thread 11
Computing 4 on Thread 11
Computing 3 on Thread 10
4 on Thread 11
Computing 6 on Thread 11
Computing 5 on Thread 10
6 on Thread 11
Computing 8 on Thread 11
Computing 7 on Thread 10
8 on Thread 11
230 CHAPTER 3 Introducing LINQ
Computing 9 on Thread 11
Computing 10 on Thread 10
10 on Thread 10
Done 00:00:05.2374586
The results are ordered, at least for the even numbers, which is what the AsOrdered extension
method is guaranteeing.

Niciun comentariu:

Trimiteți un comentariu