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