Question

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
Was it helpful?

Solution

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.

OTHER TIPS

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.

Simple - rewrite this piece For m1 = m - 1 To m + 1 as a While loop. Then do Exit While. You can handle multiple GoTo's like that, since there is also a Do loop with its own Exit Do. More about Exit Statement on MSDN.

Although, my preferred solution is to refactor it like this:

Sub SomeLambda(m As Integer, newarray As Short(,,))
  For n = 0 To ny
    For o = 0 To nz
      If newarray(m, n, o) = 1 OrElse newarray(m, n, o) = -1 Then Continue For
      DoSomething(m, n, o, newarray)
    Next
  Next
End Sub

Private Sub DoSomething(m, n, o, newarray)
  For m1 = m - 1 To m + 1
    For n1 = n - 1 To n + 1
      For o1 = o - 1 To o + 1
        If Not SomeCondition() = True Then Continue For 'the array is not out of bounds 
        Dim testVal = newarray(m1, n1, o1)
        If testVal <> -1 Then Continue For
        newarray(m, n, o) = -2
        Return
      Next
    Next
  Next
End Sub

Make sure it does not hurt performance though, and always use Option Strict On. The above would obviously not compile with it - just to show the concept.

Notice I removed unnecessary indentation, so the code became more flat and hopefully more readable.

EDIT: As a compromise between performance and maintainability, you could use class-level variables. So yes, you still jump to DoSomething and back, but no stack pushes.

My desire is to avoid a compilation to IL which includes a calli or call opcode inside the innermost loop.

You shouldn't care much about what IL is generated by your code, the only thing that metters for performance is the machine code (usually x86).

So, what you should do is to write your code in a readable way (using another function with Return for the inner loop) and then measure the performance to see whether it made a difference or not.

As an alternative, you could look at the compiled machine code to see whether the inner loop method was inlined or not.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top