Avoid Split to reduce memory press (on servers) I have a Component that is used for all Active Directory queries.

The function below is used heavily perhaps even milions of times each day.

Performance is a feature!

It's used on servers and I want to reduce memory press on these servers (when the GC is collecting the server websites etc are not responding... well of course ther are perhaps even bigger sinners in that case - but I write this in VB.NET and because of that the code has to be better than ordinary).


I can trust the format so I don't have to check.
dn = distinguishedName
Data: "CN=joedoe0101,OU=User,OU=Cardiff Electric,OU=Company,DC=root,DC=usa,DC=net"
Result: "Cardiff Electric"


Below I have two examples that not quite does what I want:

Friend Shared Function GetOrganization(dn As String) As String
    Return dn.Split(","c)(2).Substring(3)
End Function


Or this

Friend Shared Function GetOrganization(dn As String) As String
    Return dn.Substring(dn.IndexOf(","c) + 1).Split(","c)(1).Substring(3)
End Function


Is there a better solution?

Is there a solution to also get the "Company" ?
You basically have 3 other possibilities  


Using Split() like you did, but different

Private Const CommaOUEqual As String = ",OU="
Friend Shared Function GetOrganization(dn As String) As String
    Return dn.Split({CommaOUEqual}, StringSplitOptions.None)(2)
End Function  

Using IndexOf() similiar like you did, nut without Split()

Private Const CommaOUEqual As String = ",OU="
Friend Shared Function GetOrganization(dn As String) As String
    Dim index As Integer = dn.IndexOf(CommaOUEqual) + 4
    index = dn.IndexOf(CommaOUEqual, index) + 4
    Return dn.Substring(index, dn.IndexOf(CommaOUEqual, index) - index)
End Function  

Using Regex  

Private Shared regex As Regex = New Regex(",OU=(.+?),OU=(.+?),OU=", RegexOptions.Compiled)
Friend Shared Function GetOrganization(dn As String) As String
    Return regex.Match(dn).Groups(2).ToString()
End Function  



Timing these 3 methods together with the 2 methods fromn you results after 500.000 iteration in  


  yours  
  
  
  ~340 ms (Split())
  ~330 ms (IndexOf() + Split())
  
  
  mine
  
  
  ~460 ms (Split())
  ~250 ms (IndexOf())
  ~900 ms (Regex)
  


So in the worst case for assuming 5.000.000 calls in 24 hours this will result for the worst case in 9 seconds and for the best case in 2.5 seconds.  

But this are still only micro gain in performance.

But wait, we can do faster  

Friend Shared Function GetOrganization(dn As String) As String
    Dim firstHit As Boolean = False
    Dim secondHit As Boolean = False
    Dim startIndex As Integer = 0
    For i As Integer = 6 To dn.Length - 3
        If dn.Chars(i) = "="c AndAlso dn.Chars(i - 1) = "U"c AndAlso dn.Chars(i - 2) = "O"c Then
            If Not secondHit AndAlso firstHit Then
                startIndex = i + 1
                secondHit = True
                Continue For
            End If
            If secondHit Then
                Return New String(dn.ToCharArray(startIndex, i - startIndex - 3))
            End If
            firstHit = True
        End If
    Next
    Return String.Empty
End Function  


This will run for 500.000 iterations in 


  ~70 ms


Using  

Return dn.Substring(startIndex, i - startIndex - 3)


instead of  

Return New String(dn.ToCharArray(startIndex, i - startIndex - 3))


will improve the speed to about  


  ~60 ms  


if you need to parse more than one item of the given string we should use this to its own class and remove the Shared.  

Friend Class ParsedLDAP
    Friend Enum LDAPComponent
        OU = 1
        DC = 2
        CN = 4
        ALL = 7
    End Enum
    Public Property CommonNames As IList(Of String)
    Public Property OrganisationalUnits As IList(Of String)
    Public Property DomainComponents As IList(Of String)
    Private Sub New()
        CommonNames = New List(Of String)
        OrganisationalUnits = New List(Of String)
        DomainComponents = New List(Of String)
    End Sub
    Public Sub New(dn As String)
        Me.new(dn, LDAPComponent.ALL)
    End Sub
    Public Sub New(dn As String, parseComponent As LDAPComponent)
        Me.New()
        ParseString(dn, parseComponent)
    End Sub

    Private Const reversedOU As String = "UO"
    Private Const reversedDC As String = "CD"
    Private Const reversedCN As String = "NC"
    Private Const equalSign As Char = "="c
    Private Sub ParseString(dn As String, parseComponent As LDAPComponent)
        Dim isNotOU As Boolean = ((parseComponent And LDAPComponent.OU) &lt;&gt; LDAPComponent.OU)
        Dim isNotDC As Boolean = ((parseComponent And LDAPComponent.DC) &lt;&gt; LDAPComponent.DC)
        Dim isNotCN As Boolean = ((parseComponent And LDAPComponent.CN) &lt;&gt; LDAPComponent.CN)

        If (isNotOU AndAlso isNotDC AndAlso isNotCN) Then Return

        Dim currentComponent As LDAPComponent
        For i As Integer = 2 To dn.Length - 1
            If dn.Chars(i) &lt;&gt; equalSign Then Continue For

            Dim startIndex As Integer = i + 1
            Select Case dn.Chars(i - 1)
                Case reversedOU.Chars(0)
                    If isNotOU Then Continue For
                    If dn.Chars(i - 2) &lt;&gt; reversedOU.Chars(1) Then Continue For
                    currentComponent = LDAPComponent.OU
                Case reversedDC.Chars(0)
                    If isNotDC Then Continue For
                    If dn.Chars(i - 2) &lt;&gt; reversedDC.Chars(1) Then Continue For
                    currentComponent = LDAPComponent.DC
                Case reversedCN.Chars(0)
                    If isNotCN Then Continue For
                    If dn.Chars(i - 2) &lt;&gt; reversedCN.Chars(1) Then Continue For
                    currentComponent = LDAPComponent.CN
                Case Else
                    Continue For
            End Select

            While (i &lt; dn.Length - 1 AndAlso dn.Chars(i + 1) &lt;&gt; equalSign)
                i += 1
            End While

            If i = dn.Length - 1 Then i += 3 ' This is needed, because otherwise the last item can't be found

            Dim currentValue As String = dn.Substring(startIndex, i - startIndex - 2)
            Select Case currentComponent
                Case LDAPComponent.OU
                    OrganisationalUnits.Add(currentValue)
                Case LDAPComponent.DC
                    DomainComponents.Add(currentValue)
                Case LDAPComponent.CN
                    CommonNames.Add(currentValue)
            End Select

        Next
    End Sub
End Class  


Because I assume that you maybe have millions of calls where you need e.g the desired OU but for a much smaller number of distinct dn's I would suggest to store the results in a Dictionary(Of String, ParsedLDAP) to first lookup if the dn has been parsed by calling Dictionary(Of TKey,TValue).TryGetValue().