Desenvolvimento - C/C++

Usando Win32 API para otimizar o I/O, parte 1

Como eu já disse em um artigo anterior, estou fazendo um parser de arquivos IDL, usando somente C++ ISO (nada de Win32), STL e Boost. Apesar de estar realmente impressionado com o desempenho do meu parser do jeito que está, eu me propus a testar o nível de otimização de I/O que seria possível usando a API Win32 diretamente.

por Rodrigo Strauss



Como eu já disse em um artigo anterior, estou fazendo um parser de arquivos IDL, usando somente C++ ISO (nada de Win32), STL e Boost. Apesar de estar realmente impressionado com o desempenho do meu parser do jeito que está, eu me propus a testar o nível de otimização de I/O que seria possível usando a API Win32 diretamente. Vou publicar uma sequência de posts contando minhas exeriências com isso, explicando os métodos e ferramentas que eu usei para chegar às conclusões.

Primeiro, deixe-me explicar algo sobre desempenho (ou performance). Ultimamente eu tenho trabalhado bastante com isso, minha atribuição na empresa onde trabalho é (entre outras) pegar programas, serviços e componentes que já funcionam e melhorá-los. As melhorias são feitas em vários aspectos, tanto melhorias na estabilidade dos serviços quanto melhorias de desempenho.

Existem vários fatores importantes para se fazer melhoria de desempenho em um software. Conhecimento da linguagem de programação usada (C++ no meu caso) é muito importante, assim como é importante o conhecimento do funcionamento das bibliotecas (como a grande maioria das bibliotecas C++ são pequenas e com o fonte disponível, essas tarefa exige muito estudo de código fonte e pouca adivinhação ou disassembly). Outro fator muito importante é o conhecimento do sistema operacional e quais interações seu programa tem com cada parte dele. Nesse caso específico, o conhecimento do funcionamento do I/O do sistema operacional é muito importante, até para podermos saber quais opções temos para fazer essa melhoria. Um melhoria pode envolver uma modificação - algumas vezes pequena - no método usado para se chegar a um resultado, ou uma mudança completa no enfoque ou no algoritmo.

Todos os fatores que eu citei são importantes, mas o fator mais importante não é um conhecimento técnico. Chama-se mensuração. Só é possível dizer que alguma coisa foi melhorada se você souber exatamente o estado dela no momento atual, e se você conseguir mensurar exatamente qual o nível de melhoria entre uma modificação e outra. O conceito de melhor é algo muito subjetivo, não resolve o problema. Não basta o software ser "melhor", deve-se saber o "quão melhor" esse software é. E, depois disso, saber se o esforço dedicado a essa melhoria realmente valeu a pena, ou se foi muito trabalho para uma melhoria pequena.

A estrutura do parser é simples, e da forma que está feito hoje (sem Win32), o código que faz o I/O é muito parecido com o abaixo:

void MidlParser::ParseMidlFile(const char* fileName)
{
   fstream f;
   string str;
   
   ...
   
   if(m_bParseAsImportFile)
   {
      if(!OpenIncludeFile(fileName, &f, &m_parsedFileName))
         throw ParseException(string("error opening import file "") + fileName + 
                              "\"", *this, m_parsedFileName);
	   ...
   }
   else
   {
      f.open(fileName, ios::in);

      if(f.fail())
         throw ParseException(string("error opening file "") + fileName + 
                              "\"", *this, m_parsedFileName);
	  ...
   }
   
   //
   // carrega o arquivo inteiro em uma string STL
   //
   getline(f, str, f.widen(EOF));
	
   //
   // Inicializa o tokeninzer
   //
   m_Tokenizer.assign(str,char_separator("\t\r " , "\n\"*,;:{}/[]()"));
   
   ...  
   
   //
   // Loop que interpreta o IDL
   //
   for(;;)
   {
	  // ...
   }

Quando um arquivo é interpretado, diversas estruturas - IdlInterface, IdlMethod, IdlParameter, etc - são preenchidas com as informações interpretadas. Isso faz com que o interpretador fique completamente independente do gerador de proxy/stub que consumirá essas estruturas, o que me permite, futuramente, usar outro formato que não o Microsoft IDL.

Para um arquivo IDL comum, aproximadamente 400kb de texto deve ser lidos, em aproximadamente 10 arquivos. A maioria dos arquivos interpretados fazem parte do Windows Platform SDK, que definem os tipos de dados padrão do COM. A saída do parser é essa:

Parsing import file oaidl.idl
Parsing import file objidl.idl
Parsing import file unknwn.idl
Parsing import file wtypes.idl
Parsing import file ocidl.idl
Parsing import file oleidl.idl
Parsing import file servprov.idl
Parsing import file urlmon.idl
Parsing import file msxml.idl
Finish (234ms)

No próximo artigo da série veremos algumas estatísticas sobre o programa da forma que está e começaremos a fazer algumas otimizações.

Rodrigo Strauss

Rodrigo Strauss - MCP em Visual C++ e C#. Começou a programar com 12 anos de idade, e desde lá nunca mais parou. Trabalha atualmente desenvolvendo softwares para área financeira usando Visual C++, focando a parte server side e de otimização de performance. Entre seus objetos de estudo estão as linguagens C++ e Python, otimização de performance, e programação kernel mode para plataforma Windows.
Mantém o site
www.1bit.com.br, onde escreve um blog e artigos sobre C++ e programação em geral.