Rodrigo Strauss :: Blog
|
|
|
Usando Win32 API para otimizar o I/O, parte 5
Parte Zero Parte 1 Parte 2 Parte 3 Parte 4 Parte 5 Parte 6 Fontes do parser
Um fator muito importante na hora de melhorar o desempenho de um aplicativo é saber exatamente o que está acontecendo e o que ele está fazendo. Não podemos fazer modificações em um software sem saber se essas modificações vão trazer alguma melhora e sem saber o motivo da melhora. Lembre-se do que eu falei sobre mensuração, precisamos saber exatamente qual foi o grau de melhoria.
Hoje não vamos otimizar nada, mas vamos descobrir porque a nossa versão ficou mais rápida do que a que usa a runtime do C++. Afinal, nosso código faz exatamente a mesma coisa: ler o arquivo inteiro. Só que nós lemos da melhor forma possível (o arquivo todo de uma vez só), e acreditamos que essa não seja a forma usada pela runtime do C++.
Para descobrir como a runtime do C++ faz a leitura do arquivo, vamos usar o WinDbg. Com ele vamos saber exatamente quais parâmetros estão sendo passados para as funções da Win32 API. Como a Win32 API é o acesso mais low level que se pode ter em user mode, a runtime do C++ só pode acabar usando a Win32 API no final das contas.
A função que faz a leitura de um arquivo é a ReadFile (a ReadFileEx é usada somente para leituras assíncronas). Sabendo disso, vamos colocar um breakpoint na função ReadFile e fazer um dump dos parâmetros a cada chamada. Carregue o executável no WinDbg (veja os tutoriais de Windbg caso ache necessário), e coloque o breakpoint na função ReadFile. Depois disso, go, go, go:
0:000> bp KERNEL32!ReadFile 0:000> g Breakpoint 0 hit eax=002f0650 ebx=002f26b8 ecx=002f26b8 edx=002f0081 esi=00000018 edi=0042dfc0 eip=79736168 esp=0012f7f0 ebp=0012f824 iopl=0 nv up ei pl zr na po nc cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00000246 KERNEL32!ReadFile: 79736168 55 push ebp
Chegamos ao ReadFile, agora precisamos fazer um dump dos parâmetros. Olhando a documentação da MSDN, vemos que o primeiro parâmetro é o HANDLE do arquivo, e o terceiro é a quantidade de bytes lidos. Fazendo dump desses parâmetros conseguimos saber exatamente como a runtime do C++ está fazendo a leitura. Código primeiro, explicação depois:
0:000> * Informações do handle (primeiro parâmetro) 0:000> !handle poi(@esp+4) 4 Handle 3d4 Name \Device\HarddiskVolume1\temp\test.idl 0:000> * Quantidade de bytes lidos (terceiro parâmetro) 0:000> dd @esp+0xc L1 0012f7fc 00001000
A primeira coisa que fizemos foi pegar informações sobre o HANDLE do arquivo. Usamos a extensão !handle, que com a flag 4 no final faz um dump de todas as informações do HANDLE, inclusive nome do arquivo quando cabível (pode ser um HANDLE de evento ou outra coisa...). Pegamos o primeiro parâmetro usando @esp+4 (já falei sobre posição de parâmetros na pilha x86 anteriormente) e colocamos poi() para tratá-lo como um ponteiro e pegar o apontado. Apesar do tipo ser HANDLE e não um ponteiro, HANDLE nada mais é do que um #define para void*. Depois disso usamos o dd (Dump DWORD) para ler o terceiro parâmetro - a primeira coluna é o endereço de memória lido e o L1 no final é para ler somente um DWORD, o default é ler 32 DWORDs. Note que tudo vem em hexadecimal, então 00001000 é na realidade 4096 em decimal, que na realidade é o tamanho de uma página de memória x86 (na realidade, eu já falei sobre alinhamento e paginação de memória nessa série).
Temos as informações da primeira chamada à ReadFile, mas repetir esses comandos em todas as chamadas é muito trabalhoso. Por isso vamos criar um breakpoint que execute esses comandos para fazer um log de todos os ReadFile chamados. Vamos ao comando:
0:000> bp kernel32!ReadFile "!handle poi(@esp+4) 4; dd esp+0xC L1 ; g" 0:000> g
Com esses comandos faremos o dump das informações que queremos e deixaremos o programa seguir (g). Vamos ao resultado:
0:000> g Handle 3d4 Name \Device\HarddiskVolume1\temp\test.idl 0012f7fc 00001000 Handle 3d4 Name \Device\HarddiskVolume1\temp\test.idl 0012f7fc 00001000 Handle 3d4 Name \Device\HarddiskVolume1\temp\test.idl 0012f7fc 00001000 Handle 3d4 Name \Device\HarddiskVolume1\temp\test.idl 0012f7fc 00001000 Handle 3d8 Name ...\Microsoft SDK\include\OAIdl.Idl 0012f200 00001000 Handle 3d8 Name ...\Microsoft SDK\include\OAIdl.Idl 0012f200 00001000 Handle 3d8 Name ...\Microsoft SDK\include\OAIdl.Idl 0012f200 00000001 Handle 3d8 Name ...\Microsoft SDK\include\OAIdl.Idl 0012f200 00001000 Handle 3d8 Name ...\Microsoft SDK\include\OAIdl.Idl 0012f200 00001000 Handle 3d8 Name ...\Microsoft SDK\include\OAIdl.Idl 0012f200 00001000 Handle 3d8 Name ...\Microsoft SDK\include\OAIdl.Idl 0012f200 00001000 Handle 3d8 Name ...\Microsoft SDK\include\OAIdl.Idl 0012f200 00001000 Handle 3d8 Name ...\Microsoft SDK\include\OAIdl.Idl 0012f200 00001000 Handle 3d8 Name ...\Microsoft SDK\include\OAIdl.Idl 0012f200 00000001 Handle 3d8 Name ...\Microsoft SDK\include\OAIdl.Idl 0012f200 00001000 Handle 3d8 Name ...\Microsoft SDK\include\OAIdl.Idl 0012f200 00001000 Handle 3d8 Name ...\Microsoft SDK\include\OAIdl.Idl 0012f200 00001000 Handle 3d8 Name ...\Microsoft SDK\include\OAIdl.Idl 0012f200 00001000 Handle 3d8 Name ...\Microsoft SDK\include\OAIdl.Idl 0012f200 00001000 Handle 3d8 Name ...\Microsoft SDK\include\OAIdl.Idl 0012f200 00001000 Handle 3d8 Name ...\Microsoft SDK\include\OAIdl.Idl 0012f200 00001000 Handle 3d8 Name ...\Microsoft SDK\include\OAIdl.Idl 0012f200 00001000 Handle 3c8 Name ...\Microsoft SDK\include\ObjIdl.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\ObjIdl.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\ObjIdl.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\ObjIdl.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\ObjIdl.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\ObjIdl.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\ObjIdl.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\ObjIdl.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\ObjIdl.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\ObjIdl.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\ObjIdl.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\ObjIdl.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\ObjIdl.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\ObjIdl.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\ObjIdl.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\ObjIdl.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\ObjIdl.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\ObjIdl.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\ObjIdl.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\ObjIdl.Idl 0012ec04 00001000 Handle 3c4 Name ...\Microsoft SDK\include\Unknwn.Idl 0012e608 00001000 Handle 3c4 Name ...\Microsoft SDK\include\Unknwn.Idl 0012e608 00001000 Handle 3c4 Name ...\Microsoft SDK\include\Unknwn.Idl 0012e608 00001000 Handle 3c0 Name ...\Microsoft SDK\include\WTypes.Idl 0012e00c 00001000 Handle 3c0 Name ...\Microsoft SDK\include\WTypes.Idl 0012e00c 00001000 Handle 3c0 Name ...\Microsoft SDK\include\WTypes.Idl 0012e00c 00001000 Handle 3c0 Name ...\Microsoft SDK\include\WTypes.Idl 0012e00c 00001000 Handle 3c0 Name ...\Microsoft SDK\include\WTypes.Idl 0012e00c 00001000 Handle 3c0 Name ...\Microsoft SDK\include\WTypes.Idl 0012e00c 00001000 Handle 3c0 Name ...\Microsoft SDK\include\WTypes.Idl 0012e00c 00001000 Handle 3c0 Name ...\Microsoft SDK\include\WTypes.Idl 0012e00c 00001000 Handle 3c0 Name ...\Microsoft SDK\include\WTypes.Idl 0012e00c 00001000 Handle 3c0 Name ...\Microsoft SDK\include\WTypes.Idl 0012e00c 00001000 Handle 3c0 Name ...\Microsoft SDK\include\WTypes.Idl 0012e00c 00001000 Handle 3d8 Name ...\Microsoft SDK\include\OCIdl.Idl 0012f200 00001000 Handle 3d8 Name ...\Microsoft SDK\include\OCIdl.Idl 0012f200 00001000 Handle 3d8 Name ...\Microsoft SDK\include\OCIdl.Idl 0012f200 00001000 Handle 3d8 Name ...\Microsoft SDK\include\OCIdl.Idl 0012f200 00000001 Handle 3d8 Name ...\Microsoft SDK\include\OCIdl.Idl 0012f200 00001000 Handle 3d8 Name ...\Microsoft SDK\include\OCIdl.Idl 0012f200 00001000 Handle 3d8 Name ...\Microsoft SDK\include\OCIdl.Idl 0012f200 00001000 Handle 3d8 Name ...\Microsoft SDK\include\OCIdl.Idl 0012f200 00001000 Handle 3d8 Name ...\Microsoft SDK\include\OCIdl.Idl 0012f200 00000001 Handle 3d8 Name ...\Microsoft SDK\include\OCIdl.Idl 0012f200 00001000 Handle 3d8 Name ...\Microsoft SDK\include\OCIdl.Idl 0012f200 00001000 Handle 3d8 Name ...\Microsoft SDK\include\OCIdl.Idl 0012f200 00001000 Handle 3d8 Name ...\Microsoft SDK\include\OCIdl.Idl 0012f200 00001000 Handle 3d8 Name ...\Microsoft SDK\include\OCIdl.Idl 0012f200 00001000 Handle 3c8 Name ...\Microsoft SDK\include\OleIdl.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\OleIdl.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\OleIdl.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\OleIdl.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\OleIdl.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\OleIdl.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\OleIdl.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\OleIdl.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\ServProv.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\ServProv.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00000001 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00000001 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00001000 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012ec04 00001000 Handle 3c4 Name ...\Microsoft SDK\include\MsXml.Idl 0012e608 00001000 Handle 3c4 Name ...\Microsoft SDK\include\MsXml.Idl 0012e608 00001000 Handle 3c4 Name ...\Microsoft SDK\include\MsXml.Idl 0012e608 00001000 Handle 3c4 Name ...\Microsoft SDK\include\MsXml.Idl 0012e608 00001000 Handle 3c4 Name ...\Microsoft SDK\include\MsXml.Idl 0012e608 00001000 eax=00000000 ebx=7ffdf000 ecx=00000000 edx=00000000 esi=7846ee04 edi=00000000 eip=7846ee0f esp=0012fe00 ebp=0012fec8 iopl=0 nv up ei pl zr na po nc cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00000246 ntdll!NtTerminateProcess+0xb: 7846ee0f c20800 ret 0x8
Pronto, descobrimos que a runtime do C++ (usando getline(f, str, f.widen(EOF))) pede 4096 bytes a cada leitura. Nosso parser fica mais rápido usando Win32 API diretamente porque lemos o arquivo todo de uma vez, além de passar a flag que avisa o Windows que leremos o arquivo sequencialmente. Olhe como fica o mesmo dump feito com a versão Win32:
0:000> bp kernel32!ReadFile "!handle poi(@esp+4) 4; dd esp+0xC L1 ; g" 0:000> g Handle 3d4 Name \Device\HarddiskVolume1\temp\test.idl 0012f834 00002cfd Handle 3d8 Name ...\Microsoft SDK\include\OAIdl.Idl 0012f1d8 0000ecf9 Handle 3c8 Name ...\Microsoft SDK\include\ObjIdl.Idl 0012eb7c 00012200 Handle 3c4 Name ...\Microsoft SDK\include\Unknwn.Idl 0012e520 000013fb Handle 3c0 Name ...\Microsoft SDK\include\WTypes.Idl 0012dec4 00009ea7 Handle 3d8 Name ...\Microsoft SDK\include\OCIdl.Idl 0012f1d8 0000a35b Handle 3c8 Name ...\Microsoft SDK\include\OleIdl.Idl 0012eb7c 00006d48 Handle 3c8 Name ...\Microsoft SDK\include\ServProv.Idl 0012eb7c 00000e93 Handle 3c8 Name ...\Microsoft SDK\include\UrlMon.Idl 0012eb7c 0001cd2f Handle 3c4 Name ...\Microsoft SDK\include\MsXml.Idl 0012e520 00003048 eax=00000000 ebx=7ffdf000 ecx=00000000 edx=00000000 esi=7846ee04 edi=00000000 eip=7846ee0f esp=0012fe00 ebp=0012fec8 iopl=0 nv up ei pl zr na po nc cs=001b ss=0023 ds=0023 es=0023 fs=0038 gs=0000 efl=00000246 ntdll!NtTerminateProcess+0xb: 7846ee0f c20800 ret 0x8
Agora temos um log bem menor porque só fazemos uma chamada à ReadFile por arquivo. Agora sabemos exatamente o motivo pelo qual nosso I/O é mais rápido.
Em 08/09/2005 09:27






Pq agora pra optimizar vc nao usa syscall mesmo pura ?
tipo:
NtCreateFile:
mov eax, 0x0000001A
lea edx, [esp+04]
int 0x2E
ret 0x2C
NtReadFile:
....
etc etc etc ?
Claro que teriamos que escrever as funcoes de acordo com o build do OS (os version, patch level, language) mas faz ai pelo menos 1 vez pra vermos a diferenca :-)
Abraços!