Промежуточная сумма VB.NET во вложенном цикле внутри Parallel.for Synclock теряет информацию

Ниже представлено лучшее представление, которое я смог разработать для расчета промежуточной суммы внутри цикла, который вложен в цикл Parallel.for в VB.NET (Visual Studio 2010, .NET Framework 4). Обратите внимание, что при отображении результатов в виде "суммы" на экране есть небольшая разница между двумя суммами и, следовательно, потеря информации в распараллеленном варианте. Так как же теряется информация и что происходит? Может ли кто-нибудь предложить какую-нибудь "микрохирургическую" методологию для поддержания текущей суммы в этом контексте? (Примечание для новых пользователей Parallel.for: я обычно не использую нулевые методы, поэтому в операторе Parallel.for I1 повторяется до 101, поскольку код использует 101-1 в качестве верхней границы. Это потому, что MS разработала параллельный код, предполагающий счетчики с нуля):

    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)    

1 ответ

Решение

Вы работаете с двойными, а двойные просто не точны. В непараллельном цикле все ошибки хранятся непосредственно в сумме. В параллельном цикле у вас есть дополнительный tmp, который позже добавляется к сумме. Используйте ту же самую tmp в вашем непараллельном цикле (добавляя к сумме после выполнения внутреннего цикла), и в итоге результаты будут равны.

 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

выход:

-1,00005000333357  0
-1,00005000333347  0
-1,00005000333347  26

Вывод: если вы работаете с double, то (a + b) + c НЕ (всегда) равно a + (b + c)

ОБНОВИТЬ

еще более простой пример:

    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)

теперь вывод

9,99999999999998
10
Другие вопросы по тегам