Desafio da Semana #7




Desafio da Semana #7


Por: Roberto Alexis Farah


 


CENÁRIO


 


O desafio dessa semana veio de uma situação que me deparei num incidente há uma semana atrás. Aplicação ASP.NET consumindo 100% de CPU. Após coleta de dumps e análise notei que as threads do Garbage Collector estavam consumindo alta CPU. Analisando mais foi possível isolar o fragmento de código fazendo o GC disparar.


Portanto, montei uma rotina simples apenas com o fragmento de código responsável pelo sintoma, com algumas modificações para simplificar, para que vocês possam reproduzir o sintoma.


Eis o código:


 


‘Exemplo de parametros:


‘ABC!DEF#45GH


‘ABC!DEFxxxxAAAGH1


‘4fgfgjfdhg3fdgjfgjh1


Public Function Crypto(ByVal someString As String) As String


              ‘ Inicializa a semente de nuvero aleatorio para sempre ser diferente.


              VBMath.Randomize()


 


              Dim num1 As Long = Strings.Len(someString)


 


              ‘ Cria uma chave usando um numero aleatorio e o tamanho da string.


              ‘ O numero nunca e’ maior que o tamanho da string.


              Dim text1 As String = StringType.FromLong(CType(Math.Round(CType(((num1 * VBMath.Rnd) + 1.0!), Double)), Long))


 


              ‘ Varre os caracteres da string apenas.


              Do While Not Char.IsNumber(CharType.FromString(Strings.Mid(someString, IntegerType.FromString(text1), 1)))


                     ‘ Efetua a mesma conversao.


                     text1 = StringType.FromLong(CType(Math.Round(CType(((num1 * VBMath.Rnd) + 1.0!), Double)), Long))


              Loop


 


              Crypto = text1


End Function


 


 


SINTOMA


 


Sintoma intermitente de hang com CPU a 100%.


 


OBJETIVO


 


Identifique o PROBLEMA e proponha uma SOLUÇÃO.


Para isso você terá que reproduzir o sintoma utilizando o fragmento de código acima e criando uma aplicação VB.NET para utilizá-lo. Aqui usei Visual Studio 2005.


 


Nota: Não há especificação para o código acima. É a especificação que nos permite classificar um problema como um bug ou um comportamento previsto sob determinada situação, “by design”, entretanto, nesse caso o problema era de fato um bug.


Na situação real após isolar o problema não propus uma solução devido a complexidade do algoritmo, as interações com outros componentes e o fato de não ter acesso a especificação, portanto, serei flexível com as soluções propostas.


De fato, na grande maioria das vezes a solução é a parte fácil, enquanto a parte difícil é se isolar o problema.


 


 

Comments (4)

  1. Waldemir says:

    Realmente, é possível observar um número muito grande de conversões no código acima.

    Quando olhamos o IL gerado, temos algo como:

    .method public instance string  Crypto(string someString) cil managed

    {

     // Code size       111 (0x6f)

     .maxstack  3

     .locals init ([0] string Crypto,

              [1] int64 num1,

              [2] string text1)

     IL_0000:  nop

     IL_0001:  call       void [Microsoft.VisualBasic]Microsoft.VisualBasic.VBMath::Randomize()

     IL_0006:  nop

     IL_0007:  ldarg.1

     IL_0008:  call       int32 [Microsoft.VisualBasic]Microsoft.VisualBasic.Strings::Len(string)

     IL_000d:  conv.i8

     IL_000e:  stloc.1

     IL_000f:  ldloc.1

     IL_0010:  conv.r4

     IL_0011:  call       float32 [Microsoft.VisualBasic]Microsoft.VisualBasic.VBMath::Rnd()

     IL_0016:  mul

     IL_0017:  ldc.r4     1.

     IL_001c:  add

     IL_001d:  conv.r8

     IL_001e:  call       float64 [mscorlib]System.Math::Round(float64)

     IL_0023:  call       float64 [mscorlib]System.Math::Round(float64)

     IL_0028:  conv.ovf.i8

     IL_0029:  call       string [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.StringType::FromLong(int64)

     IL_002e:  stloc.2

     IL_002f:  br.s       IL_0052

     IL_0031:  ldloc.1

     IL_0032:  conv.r4

     IL_0033:  call       float32 [Microsoft.VisualBasic]Microsoft.VisualBasic.VBMath::Rnd()

     IL_0038:  mul

     IL_0039:  ldc.r4     1.

     IL_003e:  add

     IL_003f:  conv.r8

     IL_0040:  call       float64 [mscorlib]System.Math::Round(float64)

     IL_0045:  call       float64 [mscorlib]System.Math::Round(float64)

     IL_004a:  conv.ovf.i8

     IL_004b:  call       string [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.StringType::FromLong(int64)

     IL_0050:  stloc.2

     IL_0051:  nop

     IL_0052:  ldarg.1

     IL_0053:  ldloc.2

     IL_0054:  call       int32 [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.IntegerType::FromString(string)

     IL_0059:  ldc.i4.1

     IL_005a:  call       string [Microsoft.VisualBasic]Microsoft.VisualBasic.Strings::Mid(string,

                                                                                           int32,

                                                                                           int32)

     IL_005f:  call       char [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.CharType::FromString(string)

     IL_0064:  call       bool [mscorlib]System.Char::IsNumber(char)

     IL_0069:  brfalse.s  IL_0031

     IL_006b:  ldloc.2

     IL_006c:  stloc.0

     IL_006d:  ldloc.0

     IL_006e:  ret

    } // end of method Form1::Crypto

    Onde observamos várias chamadas para CONV e referências float [mscorlib].

    Fazendo um loop de chamadas para a função CRYPTO, por exemplo, é possível provocar o HANG de 100% de CPU facilmente.

    Esse HANG poderia ser minimizado após uma validação sobre os vários tipos de dados utilizados. Assim, deve-se evitar o excesso de conversões de tipos.

    Outra ação seria a retirada das conversões na expressão de loop while. Teríamos algo como:

           Dim flag As Boolean = Char.IsNumber((Strings.Mid(someString, (text1), 1)))

           ‘ Varre os caracteres da string apenas.

           Do While Not flag

               ‘ Efetua a mesma conversao.

               text1 = (CType(Math.Round(CType(((num1 * VBMath.Rnd) + 1.0!), Double)), Long))

               flag = Char.IsNumber((Strings.Mid(someString, (text1), 1)))

           Loop

    Esse procedimento gera um código com o trecho a seguir a menos:

     IL_0038:  mul

     IL_0039:  ldc.r4     1.

     IL_003e:  add

     IL_003f:  conv.r8

     IL_0040:  call       float64 [mscorlib]System.Math::Round(float64)

     IL_0045:  call       float64 [mscorlib]System.Math::Round(float64)

     IL_004a:  conv.ovf.i8

     IL_004b:  call       string [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.StringType::FromLong(int64)

     IL_0050:  stloc.2

     IL_0051:  nop

     IL_0052:  ldarg.1

     IL_0053:  ldloc.2

     IL_0054:  call       int32 [Microsoft.VisualBasic]Microsoft.VisualBasic.CompilerServices.IntegerType::FromString(string)

     IL_0059:  ldc.i4.1

     IL_005a:  call       string [Microsoft.VisualBasic]Microsoft.VisualBasic.Strings::Mid(string,

    Um abraço!

    Waldemir

  2. Roberto Farah says:

    Ola Waldemir,

     Devo dizer que gostei da sua analise detalhada! De fato, o codigo faz conversoes desnecessarias e eventualmente isso poderia se traduzir num sintoma de elevada CPU. Sua otimizacao reduz drasticamente essa possibilidade, obtendo bem menos conversoes de tipos. E voce provou isso matematicamente com o codigo IL! 🙂

     Entretanto, embora isso seja um problema potencial, o real problema e’ que o codigo assume incorretamente que algumas entradas nunca vao ocorrer, portanto, na ocorrencia delas ha um loop infinito, o que ocasiona hang de altissimo uso de CPU e deixa a aplicacao travada.

     De fato, o sintoma pode ser facilmente reproduzido no codigo do enunciado ou no codigo que voce forneceu que coloco aqui como Crypto1:

    Public Function Crypto1(ByVal someString As String) As String

      Dim num1 As Long = Strings.Len(someString)

      Dim text1 As String = StringType.FromLong(CType(Math.Round(CType(((num1 * VBMath.Rnd) + 1.0!), Double)), Long))

      Dim flag As Boolean = Char.IsNumber((Strings.Mid(someString, (text1), 1)))

      ‘ Varre os caracteres da string apenas.

      Do While Not flag

      ‘ Efetua a mesma conversao.

      text1 = (CType(Math.Round(CType(((num1 * VBMath.Rnd) + 1.0!), Double)), Long))

      flag = Char.IsNumber((Strings.Mid(someString, (text1), 1)))

      Loop

    Crypto1 = text1

    End Function

     Experimente entrar com uma string vazia ou com uma string sem caracteres numericos no parametro someString. 🙂

     Veja o artigo de resposta a ser publicado ainda hoje para mais detalhes e continue participando!

  3. Waldemir says:

    Olá Farah,

    Excelente. Fiz alguns testes e realmente temos essa situação. "Never Assume, always!!" 🙂

    Acredito que os vários desafios que já tivemos aqui no site têm ajudado a todos os leitores na geração de um novo olhar sobre os vários problemas que enfrentamos com codificação e troubleshooting.

    Para cada cenário, existem sintomas interessantes que podem ser observados, assim como técnicas apropriadas para identificação de problemas e orientação de soluções.

    Muito bom mesmo!!!

    Um abraço!

    Waldemir

  4. Roberto Farah says:

    Waldemir, que coincidência! Citei você na resposta que preparo logo depois de criar o desafio, mas não tinha certeza se era você o autor do post!

    Ei, porque você não escreve um artigo para nosso blog? Seria ótimo para todos os leitores se você pudesse usar nosso espaço para compartilhar seu conhecimento!

    Obrigado

Skip to main content