VB.NET ejecuta suma en bucle anidado dentro de Parallel.for Synclock pierde información
nested floating-accuracy (1)
A continuación se muestra la mejor representación que he podido desarrollar para calcular una suma en ejecución dentro de un bucle anidado dentro de un bucle Parallel.for en VB.NET (Visual Studio 2010, .NET Framework 4). Tenga en cuenta que al mostrar los resultados en ''suma'' en la pantalla, hay una ligera diferencia entre las dos sumas y, por lo tanto, la pérdida de información en la variante paralelizada. Entonces, ¿cómo se pierde la información y qué está sucediendo? ¿Alguien puede ofrecer alguna "microcirugía" sobre la metodología para mantener una suma corriente en este contexto? (Nota para los nuevos usuarios de Parallel.for: normalmente no utilizo métodos basados en cero, por lo que en la declaración Parallel.for, I1 sube a 101, ya que el código usa 101-1 como límite superior. Esto se debe a que MS desarrolló el código paralelo suponiendo contadores basados en cero):
Dim sum As Double = 0
Dim lock As New Object
Dim clock As New Stopwatch
Dim i, j As Integer
clock.Start()
sum = 0
For i = 1 To 100
For j = 1 To 100
sum += Math.Log(0.9999)
Next j
Next i
clock.Stop()
MsgBox(sum & " " & clock.ElapsedMilliseconds)
sum = 0
clock.Reset()
clock.Start()
Parallel.For(1, 101, Sub(i1)
Dim temp As Double = 0
For j1 As Integer = 1 To 100
temp += Math.Log(0.9999)
Next
SyncLock lock
sum += temp
End SyncLock
End Sub)
clock.Stop()
MsgBox(sum & " " & clock.ElapsedMilliseconds)
Estás trabajando con dobles y dobles simplemente no son precisos. En el bucle no paralelo, todos los errores se almacenan directamente en suma. En el bucle paralelo tiene un tmp adicional que luego se agrega a la suma. Use el mismo tmp en su bucle no paralelo (sumando a la suma después de que se haya ejecutado el bucle interno) y, finalmente, los resultados serán iguales en ese momento.
Dim sum As Double = 0
Dim lock As New Object
Dim clock As New Stopwatch
Dim i, j As Integer
clock.Start()
sum = 0
For i = 1 To 100
For j = 1 To 100
sum += Math.Log(0.9999)
Next j
Next i
clock.Stop()
Console.WriteLine(sum & " " & clock.ElapsedMilliseconds)
sum = 0
clock.Reset()
clock.Start()
sum = 0
For i = 1 To 100
Dim tmp As Double = 0
For j = 1 To 100
tmp += Math.Log(0.9999)
Next
sum += tmp
Next i
clock.Stop()
Console.WriteLine(sum & " " & clock.ElapsedMilliseconds)
sum = 0
clock.Reset()
clock.Start()
Parallel.For(1, 101, Sub(i1)
Dim temp As Double = 0
For j1 As Integer = 1 To 100
temp += Math.Log(0.9999)
Next
SyncLock lock
sum += temp
End SyncLock
End Sub)
clock.Stop()
Console.WriteLine(sum & " " & clock.ElapsedMilliseconds)
End Sub
salida:
-1,00005000333357 0
-1,00005000333347 0
-1,00005000333347 26
Conclusión: si trabajas con doble, entonces (a + b) + c NO es (siempre) igual a a + (b + c)
ACTUALIZAR
un ejemplo aún más simple:
Dim sum As Double
For i = 1 To 100
sum += 0.1
Next
Console.WriteLine(sum)
sum = 0
For i = 1 To 2
Dim tmp As Double = 0
For j = 1 To 50
tmp += 0.1
Next
sum += tmp
Next
Console.WriteLine(sum)
ahora la salida es
9,99999999999998
10