Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there a way to write this without a GoTo statement?

EDIT: This is not a question about whether it's OK to use a GoTo statement.

It is a question about how to handle the center of an O(n^3) algorithm in .NET/IL without using a GoTo statement. Adherents and fellow travelers of Dijkstra's philosophies, please take note before failing to read the question.

Consider the following code, in which for most use cases the contents of the For o = 0 to nz loop will be executed between 3 million and 18 million times. The subroutine takes its place in my code as an argument for a Parallel.For() call. The domain of m, ny, and nz are all between 10 and 300.

It is hand-optimized to avoid stack pushes and subroutine calls, in other words, for speed. My desire is to avoid a compilation to IL which includes a calli or call opcode inside the innermost loop.

To abort the innermost three loops once a test is satisfied, I use a GoTo statement to abort unneeded tests.

The question is, is there a way to code this without the GoTo? Is there a way to code this which the .net JIT-Compiler will compile to faster code without call or calli opcodes ending up in the object code?

Sub SomeLambda(m As Integer, newarray As Short(,,))
    For n = 0 To ny
        For o = 0 To nz
            If newarray(m, n, o) <> 1 AndAlso newarray(m, n, o) <> -1 Then
                For m1 = m - 1 To m + 1
                    For n1 = n - 1 To n + 1
                        For o1 = o - 1 To o + 1
                            If SomeCondition = True Then 'the array is not out of bounds '
                                Dim testVal = newarray(m1, n1, o1)
                                If testVal = -1 Then
                                    newarray(m, n, o) = -2
                                    GoTo Exitloopslabel2
                                End If
                            End If
                        Next
                    Next
                Next
   Exitloopslabel2: 
            End If
        Next
    Next
End Sub
like image 253
Rob Perkins Avatar asked Dec 19 '13 00:12

Rob Perkins


2 Answers

Is there any reason not to push it out to a seperate method and then decorate that method with MethodImplOptions.AggressiveInlining ("The method should be inlined if possible").

As long as the method meets certain requirements (see below), the compiler will make a copy of the method at the point where it's called.

This would let you use Return and tidy up your code considerably while also skipping the stack pushes, jumps, etc. usually associated with a method call.

Unfortunately, with the constraints you've imposed, there aren't many alternatives.

As requested, some example usage in VB.Net:

Imports System.Runtime.CompilerServices

<MethodImpl(MethodImplOptions.AggressiveInlining)>
Public Function Blah() As String 
    ...
End Function

and C#

using System.Runtime.CompilerServices;

[MethodImpl(MethodImplOptions.AggressiveInlining)] 
public string Blah() {
    ...
}

I should mention that this is a hint to the compiler, and there are limits. The following don't support inlining;

  • Virtual methods
  • Recursive methods
  • Methods that take a large value type as a parameter
  • Methods on MarshalByRef classes
  • Methods with complicated flowgraphs
  • Methods meeting other, more exotic criteria

There may also be an IL byte count limit (There's a limit of 32 bytes without this flag, which is either increased or removed entirely). I haven't been able to find adequate documentation.

like image 149
Basic Avatar answered Oct 04 '22 21:10

Basic


Although i would suggest you to use separated method to do the loop, if you are really keen to use nested loop, there is an alternative to jump out from the loop you like:

    Dim list1 = Enumerable.Range(1, 10)
    Dim list2 = Enumerable.Range(101, 10)
    Dim list3 = Enumerable.Range(201, 10)

    Console.WriteLine("Loop Start")

    For i As Integer = 0 To list1.Count - 1
        For j As Integer = 0 To list2.Count - 1
            For k As Integer = 0 To list3.Count - 1
                Console.WriteLine(k)
                If list3(k) = 205 Then ' Assume this is the condition to exit
                    k = list3.Count ' -- exit the loop of list3
                    j = list2.Count ' -- exit the loop of list2
                End If
            Next
        Next
    Next

    Console.WriteLine("Finished")

I wrote a simpler sample (other than using your complex one), this would work with nested loops (regardless number of loops). and I believe the overhead would be minimal.

like image 20
Rex Avatar answered Oct 04 '22 22:10

Rex