---
title: "Vantagens e Desvantagens de `string` no OpenResty - API7.ai"
description: "Este artigo apresenta as técnicas de otimização a serem seguidas no OpenResty: processar as requisições de forma curta, simples e rápida, evitando a geração de dados intermediários. Também mostra as técnicas de otimização para strings."
url: "https://api7.ai/pt/learning-center/openresty/advantages-and-disadvantages-of-string"
---

# Vantagens e Desvantagens de \`string\` no OpenResty

API7.ai

December 8, 2022

[OpenResty (NGINX + Lua)](https://api7.ai/learning-center/openresty)

No artigo anterior, nos familiarizamos com as funções de bloqueio comuns no OpenResty, que são frequentemente mal utilizadas por iniciantes. A partir deste artigo, entraremos no núcleo da otimização de desempenho, que envolverá muitas técnicas de otimização que podem nos ajudar a melhorar rapidamente o desempenho do código OpenResty, então não leve isso de forma leve.

Neste processo, precisamos escrever mais códigos de teste para experimentar como usar essas técnicas de otimização e verificar sua eficácia, para que possamos utilizá-las de forma adequada.

## Por trás das cenas das dicas de otimização de desempenho

As técnicas de otimização fazem parte da parte "prática", então antes de fazermos isso, vamos falar sobre a "teoria" da otimização.

Os métodos de otimização de desempenho mudarão com as iterações do LuaJIT e do OpenResty. Alguns métodos podem ser diretamente otimizados pela tecnologia subjacente e não precisam mais ser dominados; ao mesmo tempo, haverá algumas novas técnicas de otimização. Portanto, é mais importante dominar o conceito constante por trás dessas técnicas de otimização.

Vamos dar uma olhada em algumas das ideias críticas sobre desempenho na programação OpenResty.

### Teoria 1: O processamento de solicitações deve ser curto, simples e rápido

O OpenResty é um servidor web, então ele frequentemente lida com 1.000+, 10.000+, ou até 100.000+ solicitações de clientes simultaneamente. Portanto, para alcançar o maior desempenho geral, devemos garantir que as solicitações individuais sejam processadas rapidamente e que vários recursos, como memória, sejam recuperados.

- O "curto" mencionado aqui significa que o ciclo de vida da solicitação deve ser curto, para não ocupar recursos por um longo tempo sem liberá-los; mesmo para conexões longas, um limite de tempo ou número de solicitações deve ser definido para liberar recursos regularmente.
- O segundo "simples" refere-se a fazer apenas uma coisa em uma API. Divida a lógica de negócios complexa em várias APIs e mantenha o código simples.
- Finalmente, "rápido" significa não bloquear o thread principal e não executar muitas operações de CPU. Mesmo que você precise fazer isso, não se esqueça de trabalhar com outros métodos que introduzimos no último artigo.

Essa consideração arquitetônica não é apenas adequada para o OpenResty, mas também para outras linguagens e plataformas de desenvolvimento, então espero que você possa entender e pensar sobre isso cuidadosamente.

### Teoria 2: Evite gerar dados intermediários

Evitar dados inúteis no processo intermediário é, sem dúvida, a teoria de otimização mais dominante na programação OpenResty. Vamos ver um pequeno exemplo para explicar dados inúteis no processo intermediário.

```
$ resty -e 'local s= "hello"
s = s .. " world"
s = s .. "!"
print(s)
'
```

Neste trecho de código, fizemos várias operações de concatenação na variável `s` para obter o resultado `hello world!`. Mas apenas o estado final `hello world!` de `s` é útil. O valor inicial de `s` e as atribuições intermediárias são todos dados intermediários que devem ser gerados o mínimo possível.

A razão é que esses dados temporários trarão perdas de desempenho de inicialização e GC. Não subestime essas perdas; se isso aparecer em código quente como loops, o desempenho será obviamente degradado. Também explicarei isso mais tarde com um exemplo de string.

## `string`s são imutáveis

Agora, de volta ao assunto deste artigo, `string`. Aqui, estou destacando o fato de que `string`s são imutáveis em Lua.

Claro, isso não significa que `string`s não possam ser concatenadas, modificadas, etc., mas quando modificamos uma `string`, não alteramos a `string` original, mas criamos um novo objeto `string` e mudamos a referência para a `string`. Então, naturalmente, se a `string` original não tiver nenhuma outra referência, ela será recuperada pelo GC (coleta de lixo) do Lua.

O benefício aparente das `string`s imutáveis é que elas economizam memória. Dessa forma, haverá apenas uma cópia da mesma `string` na memória, e diferentes variáveis apontarão para o mesmo endereço de memória.

A desvantagem desse design é que, quando se trata de adicionar e recuperar `string`s, toda vez que você adiciona uma `string`, o LuaJIT precisa chamar `lj_str_new` para verificar se a `string` já existe; se não, ele precisa criar uma nova `string`. Se você fizer isso com muita frequência, isso terá um grande impacto no desempenho.

Vamos ver um exemplo concreto de uma operação de concatenação de `string` como a deste exemplo, que é encontrada em muitos projetos de código aberto do OpenResty.

```
$ resty -e 'local begin = ngx.now()
local s = ""
-- loop `for`, usando `..` para realizar a concatenação de strings
for i = 1, 100000 do
    s = s .. "a"
end
ngx.update_time()
print(ngx.now() - begin)
'
```

O que este código de exemplo faz é realizar 100.000 concatenações de `string` na variável `s` e imprimir o tempo de execução. Embora o exemplo seja um pouco extremo, ele dá uma boa ideia da diferença entre antes e depois da otimização de desempenho. Sem otimização, este código roda por 0,4 segundos no meu laptop, o que ainda é relativamente lento. Então, como devemos otimizá-lo?

Nos artigos anteriores, a resposta foi dada, que é usar `table` para fazer uma camada de encapsulamento, removendo todas as `string`s intermediárias temporárias e mantendo apenas os dados originais e o resultado final. Vamos ver a implementação do código concreto.

```
$ resty -e 'local begin = ngx.now()
local t = {}
-- loop `for` que usa um array para armazenar a string, contando o comprimento do array cada vez
for i = 1, 100000 do
    t[#t + 1] = "a"
end
-- Concatenando strings usando o método `concat` de arrays
local s =  table.concat(t, "")
ngx.update_time()
print(ngx.now() - begin)
'
```

Podemos ver que este código salva cada string em sequência com `table`, e o índice é determinado por `#t + 1`, ou seja, o comprimento atual de `table` mais `1`. Finalmente, use a função `table.concat` para concatenar cada elemento do array. Isso naturalmente ignora todas as strings temporárias e evita 100.000 chamadas de `lj_str_new` e GC.

Essa foi nossa análise de código, mas como funciona a otimização? O código otimizado leva apenas 0,007 segundos, o que significa uma melhoria de desempenho de mais de 50 vezes. Em um projeto real, a melhoria de desempenho pode ser ainda mais pronunciada, porque, neste exemplo, adicionamos apenas um caractere `a` de cada vez.

Qual seria a diferença de desempenho se a nova `string` tivesse o comprimento de 10x `a`?

Os 0,007 segundos de código são bons o suficiente para o nosso trabalho de otimização? Não, ainda pode ser otimizado. Vamos modificar mais uma linha de código e ver o resultado.

```
$ resty -e 'local begin = ngx.now()
local t = {}
-- loop `for`, usando um array para armazenar a string, mantendo o comprimento do array por si só
for i = 1, 100000 do
    t[i] = "a"
end
local s =  table.concat(t, "")
ngx.update_time()
print(ngx.now() - begin)
'
```

Desta vez, mudamos `t[#t + 1] = "a"` para `t[i] = "a"`, e com apenas uma linha de código, podemos evitar 100.000 chamadas de função para obter o comprimento do array. Lembra da operação para obter o comprimento de um array que mencionamos na seção `table` anteriormente? Ela tem uma complexidade de tempo de `O(n)`, uma operação relativamente cara. Então, aqui simplesmente mantemos nosso índice de array para contornar a operação de obter o comprimento do array. Como diz o ditado, se você não pode lidar com isso, pode evitá-lo.

Claro, esta é uma maneira mais simples de escrever. O código a seguir ilustra mais claramente como manter o índice de um array por nós mesmos.

```
$ resty -e 'local begin = ngx.now()
local t = {}
local index = 1
for i = 1, 100000 do
    t[index] = "a"
    index = index + 1
end
local s = table.concat(t, "")
ngx.update_time()
print(ngx.now() - begin)
'
```

## Reduzir outras `string`s temporárias

Os erros que acabamos de falar, `string`s temporárias causadas por concatenação de `string`s, são aparentes. Com alguns lembretes do código de exemplo acima, acredito que não cometeremos erros semelhantes novamente. No entanto, algumas `string`s temporárias mais ocultas são geradas no OpenResty, que são muito menos facilmente detectadas. Por exemplo, a função de manipulação de `string` que discutiremos abaixo é frequentemente usada. Você pode imaginar que ela também gera `string`s temporárias?

Como sabemos, a função `string.sub` intercepta uma parte especificada de uma `string`. Como mencionamos anteriormente, `string`s em Lua são imutáveis, então interceptar uma nova string envolve `lj_str_new` e operações subsequentes de GC.

```
resty -e 'print(string.sub("abcd", 1, 1))'
```

A função do código acima é pegar o primeiro caractere da `string` e imprimi-lo. Naturalmente, isso inevitavelmente gera uma `string` temporária. Existe uma maneira melhor de alcançar o mesmo efeito?

```
resty -e 'print(string.char(string.byte("abcd")))'
```

Naturalmente sim. Olhando para este código, primeiro usamos `string.byte` para obter o código numérico do primeiro caractere e, em seguida, usamos `string.char` para converter o número no caractere correspondente. Este processo não gera nenhuma `string` temporária. Portanto, é mais eficiente usar `string.byte` para fazer a varredura e análise relacionadas a `string`s.

## Aproveitar o suporte do SDK para o tipo `table`

Depois de aprender como reduzir a `string` temporária, você está ansioso para tentar? Então, podemos pegar o resultado do código de exemplo acima e enviá-lo ao cliente como o conteúdo do corpo da resposta. Neste ponto, você pode pausar e tentar escrever este código você mesmo primeiro.

```
$ resty -e 'local begin = ngx.now()
local t = {}
local index = 1
for i = 1, 100000 do
    t[index] = "a"
    index = index + 1
end
local response = table.concat(t, "")
ngx.say(response)
'
```

Se você conseguiu escrever este código, já está à frente da maioria dos desenvolvedores OpenResty. A API Lua do OpenResty já levou em consideração o uso de `table`s para concatenação de `string`s, então em `ngx.say`, `ngx.print`, `ngx.log`, `cosocket:send` e outras APIs que podem receber muitas `string`s, ela aceita não apenas `string` como parâmetro, mas também `table` como parâmetro.

```
resty -e 'local begin = ngx.now()
local t = {}
local index = 1
for i = 1, 100000 do
    t[index] = "a"
    index = index + 1
end
ngx.say(t)
'
```

Neste último trecho de código, omitimos o `local response = table.concat(t, "")`, o passo de concatenação de `string`s, e passamos o `table` diretamente para `ngx.say`. Isso transfere a tarefa de concatenação de `string`s do nível Lua para o nível C, evitando outra busca, geração e GC de `string`. Para `string`s longas, isso é outro ganho significativo de desempenho.

## Resumo

Depois de ler este artigo, podemos ver que muita otimização de desempenho do OpenResty lida com vários detalhes. Portanto, precisamos conhecer bem o LuaJIT e a API Lua do OpenResty para alcançar o desempenho ideal. Isso também nos lembra que, se esquecermos o conteúdo anterior, devemos revisar e consolidá-lo a tempo.

Finalmente, pense em um problema: escreva as strings `hello`, `world` e `!` no log de erros. Podemos escrever um código de exemplo sem concatenação de `string`s?

Além disso, não se esqueça da outra pergunta no texto. Qual seria a diferença de desempenho no código a seguir se as novas `string`s tivessem o comprimento de 10x `a`?

```
$ resty -e 'local begin = ngx.now()
local t = {}
for i = 1, 100000 do
    t[#t + 1] = "a"
end
local s =  table.concat(t, "")
ngx.update_time()
print(ngx.now() - begin)
'
```

Você também é bem-vindo a compartilhar este artigo com seus amigos para aprender e se comunicar.

Share article link

Share

Related Posts

[![I/O não bloqueante - A chave para melhorar o desempenho do OpenResty](https://static.api7.ai/2022/12/05/638dce5075e0f.png)](https://api7.ai/learning-center/openresty/blocking-io)

## [I/O não bloqueante - A chave para melhorar o desempenho do OpenResty](https://api7.ai/learning-center/openresty/blocking-io)

[![FAQ do OpenResty | Como o OpenResty é usado na prática](https://static.api7.ai/uploads/2023/02/12/jbv9M0zi_Hello%20World%20in%20OpenResty%20%283%29.png)](https://api7.ai/learning-center/openresty/faq-5)

## [FAQ do OpenResty | Como o OpenResty é usado na prática](https://api7.ai/learning-center/openresty/faq-5)

Table of Contents

- [Por trás das cenas das dicas de otimização de desempenho](#por-tr%C3%A1s-das-cenas-das-dicas-de-otimiza%C3%A7%C3%A3o-de-desempenho)

  - [Teoria 1: O processamento de solicitações deve ser curto, simples e rápido](#teoria-1-o-processamento-de-solicita%C3%A7%C3%B5es-deve-ser-curto-simples-e-r%C3%A1pido)
  - [Teoria 2: Evite gerar dados intermediários](#teoria-2-evite-gerar-dados-intermedi%C3%A1rios)

- [`string`s são imutáveis](#strings-s%C3%A3o-imut%C3%A1veis)

- [Reduzir outras `string`s temporárias](#reduzir-outras-strings-tempor%C3%A1rias)

- [Aproveitar o suporte do SDK para o tipo `table`](#aproveitar-o-suporte-do-sdk-para-o-tipo-table)

- [Resumo](#resumo)
