|
Usuários |
|
62 Usuários Online
|
|
[Artigos]
Para aqueles que utilizam FieldByName. |
Publicado por fabiofs : Quarta, Outubro 04, 2006 - 09:06 GMT-3 (10520 leituras)
16 Comentários Enviar para um amigo Versão para impressão
|
Isto é para ser mais um ponto de discussão entre nós, desenvolvedores Delphi.
Sempre fui fã do FieldByName(). Sempre achei que o código ficava muito mais claro com expressões do tipo FieldByName('nome_do_campo').asAlgumaCoisa do que Fields[indice].asAlguma coisa...
Há pouco tempo, em um projeto que
estou trabalhando, um amigo do trabalho me pediu que evitasse a utilização de
FieldByName e de imediato questionei o porquê de tal decisão. O mesmo me pediu
para que eu desse uma olhada na implementação do FieldByName nos fontes da VCL
do Delphi. Vou colar aqui função para vocês:
function TDataSet.FieldByName(const FieldName: string):
TField;
begin
Result := FindField(FieldName);
if Result = nil then DatabaseErrorFmt(SFieldNotFound, [FieldName], Self);
end;
Bom, até agora nada. Mas vamos olhar como é implementado o método FindField:
function TDataSet.FindField(const FieldName: string):
TField;
begin
Result := FFields.FindField(FieldName);
if (Result = nil) and ObjectView then
Result := FieldList.Find(FieldName);
if Result = nil then
Result := FAggFields.FindField(FieldName);
end;
Até agora ainda não temos nada de concreto sobre o motivo da não utilização do
FieldByName a mim solicitada. Sendo um pouco mais persistente, vamos ver o
método FindField do objeto FFields que é do tipo TField:
function TFields.FindField(const FieldName: string):
TField;
var
I: Integer;
begin
for I := 0 to FList.Count - 1 do
begin
Result := FList.Items[I];
if AnsiCompareText(Result.FFieldName, FieldName)
= 0 then Exit;
end;
Result := nil;
end;
Agora sim podemos concluir alguma coisa. Observando o código à cima, vamos
pensar na seguinte situação. Imaginem que temos um dataset com 60 campos e temos
na posição 60 um campo valorado com o qual precisamos fazer uma soma do tipo:
valor := 0;
while not DataSet.Eof do
begin
Valor := valor + DataSet.FieldByName('campo_valorado').asCurrency;
DataSet.Next;
end;
Se tivermos neste DataSet 100000 registros, teremos que passar pela linha de
código
...
Valor := valor + DataSet.FieldByName('campo_valorado').asCurrency;
...
100000 vezes. Um processamento rasoável. Mas e o FieldByName? Observem que na
implementação do método FindField da classe TField é utilizado um for de 0 até o
número de campos para se encontrar o campo desejado e assim retornar o valor.
Sendo, o nosso campo desejado, o campo de número 60, cada chamada de FieldByName
- em nosso caso - ocasionaria um processamento de uma repetição 60 vezes até que
o campo seja encontrado. Agora vamos fazer uma conta simples:
100000 registros x 60 vezes (FieldByname) = 6000000 instruções processadas.
Poxa, chegamos a um valor alto né.
Mas qual a solução? Fields[60]?
Vamos ver a implementação da classe TFields para ver como o mesmo processa a
instrução Fields[indice]:
TFields = class(TObject)
private
FList: TList;
...
protected
...
function GetField(Index: Integer): TField;
...
public
...
property Fields[Index: Integer]: TField read GetField write SetField; default;
end;
Já podemos ver que Fields é uma property indexada. Opá, algo já nos mostra que
isto pode ser mais rápido que a pesquisa com o for do método FieldByName mas
vamos mais a fundo. Vamos dar uma olhadinha no método de acesso GetField:
if FSparseFields > 0 then
begin
if Index >= FSparseFields then
DatabaseError(SListIndexError, DataSet);
Result := FList[0];
Result.FOffset := Index;
end else
Result := FList[Index];
Reparem quem em nosso caso, que apenas a linha Result := FList[Index]; será
acionada utilizando um TList onde são armazenados os campos de um DataSet. E
como será a implementação da propriedade que define os itens de um TList?
TList = class(TObject)
private
FList: PPointerList;
...
protected
function Get(Index: Integer): Pointer;
...
public
...
property Items[Index: Integer]: Pointer read Get write Put; default;
...
end;
Por fim chegamos ao método de acesso Get da property items da classe TList:
function TList.Get(Index: Integer): Pointer;
begin
if (Index < 0) or (Index >= FCount) then
Error(@SListIndexError, Index);
Result := FList^[Index];
end;
Observem a diferença. Aqui se trabalha com Ponteiros para a localização do campo
desejado. Sendo assim, o processamento desta instrução terá peso 1, mesmo que
tenhamos 60 campos em nosso DataSet. Vamos voltar a conta que fizemos
anteriormente:
100000 registros x 1 vez (Fields[indice]) = 100000 instruções processadas.
Olha que diferença entre executar 6000000 de instruções e 100000. Por isto digo,
dentro de Loops envolvendo um campo de um DataSet com vários campos, pensem bem
se vale a pena utilizar
valor := 0;
while not DataSet.Eof do
begin
Valor := valor + DataSet.FieldByName('campo_valorado').asCurrency;
DataSet.Next;
end;
ou
valor := 0;
while not DataSet.Eof do
begin
Valor := valor + DataSet.Fields[60].asCurrency; //campo_valorado
DataSet.Next;
end;
Querem algo para arrepiar os cabelos? Pensem em algo do tipo:
FieldByName('A').asInteger :=
((FieldByName('B').asInteger + FieldByName('C').asInteger)/ FieldByName('D').asInteger)
* FieldByName('E')
Isto para 1000 registros, em um DataSet com 5 campos (algo bem pequeno) daria no
pior caso:
1(A) x 2(B) x 3(C) x 4(D) x 5(E) x 100 = 120000 instruções processadas
Agora transportem esta situação para um DataSet com um pouco mais de campos e um
pouco mais de registros. (Sai até um palavrão neste momento do pensamento de
vocês, não sai?)
Observem que um comentário já torna o código mais claro. Não estou
desaconselhando a utilização do FieldByName porém, temos que avaliar muito bem
mesmo quando formos utilizar um simples método como este.
Por último, quando tiverem algum problema de performance em um código fonte e já
tiverem tentado de tudo, procurem dentro da VCL do Delphi o que vocês estão
pedindo para que ele faça pois, talvez lá, esteja a solução do seu problema. Vai
aqui também um agradecimento ao amigo Cristiano pela dica.
Fábio Silva -
fabiofs@hotmail.com
|
|
Comentários | |
| | Comentários pertencem aos seus respectivos autores. Não somos responsáveis pelo seus conteúdos. |
por: cruzeirense : Out 04, 2006 - 12:15 (Informações sobre o membro | Enviar uma mensagem)
|
Interessante, vou utilizar, mas apenas para casos críticos porque ficar buscando campo por índice é @#!@#@, principalmente quando você tem que alterar posteriormente a instrução sql que gera o dataset ou a mesma é criada dinâmicamente, Qualquer mudança na posição dos campos e pode ter certeza que vão ser horas de debug. De qualquer forma é uma ótima dica.
PS. E não é que saiu o palavrão mesmo?
|
por: fabiofs : Out 04, 2006 - 01:08 (Informações sobre o membro | Enviar uma mensagem)
http://
|
Oi grande Cruzeirense. Esse também é meu time de coração, como bom Mineiro.
É claro que existem casos onde a utilização do FieldByName é totalmente necessário. Mas é bom avaliar em que momentos podemos evitá-lo.
|
por: joemil : Out 04, 2006 - 07:34 (Informações sobre o membro | Enviar uma mensagem)
|
caraca, desse jeito, vai demorar mesmo.
o unico problema q vejo eh qdo a query eh dinamica, como ja disseram antes.
mas onde puder retirar, vou retira-lo.
ou criar um array pra colocar os campos dinamicos selecionados e "recriar" o fieldbyname hehehe
|
por: fabiofs : Out 04, 2006 - 10:12 (Informações sobre o membro | Enviar uma mensagem)
http://
|
Em caso de querys dinâmicas, o jeito acho que até o mais lógico é usar o FieldByName mesmo. Mas mesmo em querys dinâmicas, pode-se usar o Fields mesmo.
Uma maneira de se evitar muita manutenção no código quando se alteram os Campos persistentes de uma query ou tabela é usar constantes com as posições dos campos.
E da mesma forma que o FieldByName tem uma performance pior que o Fields, o ParamByName o é também.
|
por: mmpaulo : Out 06, 2006 - 11:37 (Informações sobre o membro | Enviar uma mensagem)
http://
|
Desculpe, mas discordo totalmente de voce.
É claro que Fields é mais rápido que FieldByName, mas voce fez testes para ver se a diferença é perceptivel e se justifica a troca de FieldByName por Fields?
Outra coisa, independente, de usar FieldByName ou Fields, no exemplo que você deu, você podia ter armazenado o TField em uma variável antes de entrar no loop.
|
|
por: Visitante : Out 06, 2006 - 11:52
|
Totalmente equivocada sua opinião e seus exemplos. o FieldByName só criaria um gargalo de desempenho em um sistema muito mal planejado e mal modelado também. percorrer todos os registros de uma tabela só se o programador não tiver conhecimentos de SQL. Tabelas com mais de 20 campos é uma falha na normalização do banco de dados.
reveja seus conceitos.
|
por: acidbytes (acidbytes@acidbytes.com)
: Out 06, 2006 - 03:11 (Informações sobre o membro | Enviar uma mensagem)
http://
|
Fábio, acho que há um tremendo equívoco, ou falta de informação da pessoa que lhe orientou sobre isso. Rode um profiler e você verá que a diferença de performance é insignificativa.
Para o caso que você citou, de fazer uma soma, acho que nem um iniciante faria assim, uma vez que uma instrução SQL resolve de maneira muito mais eficiente, daquela forma que você colocou, teria que trazer os registros para o cliente e efetuar uma soma no cliente, algo impensável!
Além do mais, qualquer compilador gera um código otimizado nesse caso.
O ganho de performance eventual (se existir algum minimamente significativo), é totalmente invalidado pela perda de tempo e clareza do código.
|
por: clelson : Out 09, 2006 - 11:02 (Informações sobre o membro | Enviar uma mensagem)
http://
|
Bom,
Se o problema é performance, creio que isso resolve o seu problema:
var
campo: TField;
begin
valor := 0;
campo := dataset.FieldByName(''campo_valorado'');
valor := 0;
while not DataSet.Eof do
begin
Valor := valor + campo.asCurrency;
DataSet.Next;
end;
pronto. Isso evita a procura pelo campo todas aquelas vezes, não complica o código e mantem a clareza.
Agora se vamos entrar em questões de performance mesmo: - Que tal dar uma olhada na implementação de uma chamada a método virtual?
VMT e etc?
|
por: fabiofs : Out 12, 2006 - 11:13 (Informações sobre o membro | Enviar uma mensagem) http:// | Que legal a discursão que meu artigo está gerando. Isso é que é interessante, debatermos e confrontarmos nossas opiniões.
Bom, fiz os testes em um sistema que estou desenvolvendo aqui na empresa. Quanto ao sistema ser bem projetado, garanto pra vocês que foi muito bem estruturado.
No meu caso eu utilizei o AQTime para verificar as linhas de código que causavem mais impacto em meu sistema. Estava utilizando em algumas procedures o FieldByName para realizar vários cálculos dentro das mesmas e estava demorando cerca de 3 min para estes calculos serem feitos. O AQTime mostra cada linha de código que se torna gargalo dentro da aplicação, quantas vezes a mesma é chamada e quanto tempo a mesma leva para ser processada.
Depois de trocarmos o FieldByName pela propriedade Fields as linhas de código que gastavam 3 minutos para serem executadas passaram a ser executadas em 17 segundos.
Acho que foi um ganho de performance enorme, não acham? Bom, fica a penas a dica para vocês. Pensem bem nisto e avaliem o que deve ou não ser feito. Como dizia o Capitão Planeta: (essa foi péssima, hehehe) "O poder é vocês". | [ Comentários não permitidos para usuários anônimos. Por gentileza, registre-se ou conecte-se ao sistema
por: fabiofs : Out 12, 2006 - 11:20 (Informações sobre o membro | Enviar uma mensagem)
http://
|
Só um mais um comentário sobre os comentários à cima. Sistemas pequenos e mais simples tem tabelas pequenas. Por exemplo, na tabela de produtos na empresa em que trabalho temos cerca de 60 campos e fale com a equipe de DBAS aqui que a tabela esta desnormalizada que os mesmos "dão até tiro". Mas é verdade que a tabela está dentro das 5 regras de normalização que utilizamos aqui e que são as mesmas utilizadas para normalização de BD em qualuer lugar.
Existem casos também em que temos que montar ClientDataSet dinamicos e sendo assim não há SQL que nos auxilie com suas funções como SUM, AVG, etc...
Estou ciente que o meu exemplo foi bem simples. Mas apenas queria mostrar a diferença de linhas de código executadas quando utilizamos FieldByName e Fields.
|
por: mmpaulo : Out 17, 2006 - 10:48 (Informações sobre o membro | Enviar uma mensagem) http:// | Tá, vc pode postar um treco do código do sistema onde vc testou? Primeiro usando fieldbyname e depois fields?
Voce estava fazendo da mesma forma que o exemplo que voce postou aqui, usando fieldbyname DENTRO de um loop? Em caso afirmativo, faça o teste usando fieldbyname FORA do loop e armazenando as referencias aos TFields em variáveis locais. | [ Comentários não permitidos para usuários anônimos. Por gentileza, registre-se ou conecte-se ao sistema
por: danilorsa_betta (danilo@jfkasdjfs.com.br)
: Out 28, 2006 - 03:36 (Informações sobre o membro | Enviar uma mensagem)
http://http://
|
Achei muito interessante, tanto o artigo quanto as soluções nas respostas dos amigos. Sempre me deparei com essas questões, e assim utilizo o FieldByName em muitos casos, visto, que mudanças futuras em tipos de dados, tamanhos, implicam em remover os campos e adicionar novamente no dataset, quando utilizamos TabelaCAMPO.asAlgumaCoisa. E esse tipo de manutenção, é um Deus nos acuda.
|
|
|
Edição 112 |
|
|
50 Programas Fontes |
|
|
Produtos |
|
|