¿Cómo funciona el optimizador de solidez?

El compilador de solidity admite la bandera --optimizeque realiza un conjunto de optimizaciones en el código de solidity compilado, lo que generalmente resulta en una reducción de los costos de gasolina.

¿Cómo funciona el optimizador y cuáles son algunos ejemplos de las optimizaciones que realizará?

Esta pregunta puede ser demasiado amplia. Las preguntas más específicas (como ethereum.stackexchange.com/questions/11828/… ) pueden ser más factibles de responder.

Respuestas (1)

El optimizador me parece mágico incluso a mí. chriseth (Él es el principal desarrollador de DEV de solidity ) probablemente estaría en una mejor posición para responder a su pregunta que yo. Sin embargo, aquí hay una descripción general interesante de alto nivel, que espero responda a su pregunta.

Todo a continuación es de los documentos de solidez .

Internos - el Optimizador

El optimizador de Solidity opera en ensamblaje, por lo que puede ser y también es utilizado por otros lenguajes. Divide la secuencia de instrucciones en bloques básicos en JUMP y JUMPDEST. Dentro de estos bloques, las instrucciones se analizan y cada modificación a la pila, a la memoria o al almacenamiento se registra como una expresión que consta de una instrucción y una lista de argumentos que son esencialmente punteros a otras expresiones. La idea principal ahora es encontrar expresiones que sean siempre iguales (en cada entrada) y combinarlas en una clase de expresión. El optimizador primero intenta encontrar cada nueva expresión en una lista de expresiones ya conocidas. Si esto no funciona, la expresión se simplifica de acuerdo con reglas como constante + constante = suma_de_constantes o X * 1 = X. Como esto se hace recursivamente, también podemos aplicar la última regla si el segundo factor es una expresión más compleja donde sabemos que siempre se evaluará como uno. Las modificaciones a las ubicaciones de almacenamiento y memoria tienen que borrar el conocimiento sobre las ubicaciones de almacenamiento y memoria que no se sabe que sean diferentes: si primero escribimos en la ubicación x y luego en la ubicación y y ambas son variables de entrada, la segunda podría sobrescribir la primera, por lo que en realidad no sabemos qué se almacena en x después de que escribimos en y. Por otro lado, si una simplificación de la expresión x - y se evalúa como una constante distinta de cero, sabemos que podemos mantener nuestro conocimiento sobre lo que se almacena en x. Si primero escribimos en la ubicación x y luego en la ubicación y y ambas son variables de entrada, la segunda podría sobrescribir la primera, por lo que en realidad no sabemos qué se almacena en x después de que escribimos en y. Por otro lado, si una simplificación de la expresión x - y se evalúa como una constante distinta de cero, sabemos que podemos mantener nuestro conocimiento sobre lo que se almacena en x. Si primero escribimos en la ubicación x y luego en la ubicación y y ambas son variables de entrada, la segunda podría sobrescribir la primera, por lo que en realidad no sabemos qué se almacena en x después de que escribimos en y. Por otro lado, si una simplificación de la expresión x - y se evalúa como una constante distinta de cero, sabemos que podemos mantener nuestro conocimiento sobre lo que se almacena en x.

Al final de este proceso, sabemos qué expresiones deben estar en la pila al final y tenemos una lista de modificaciones en la memoria y el almacenamiento. Esta información se almacena junto con los bloques básicos y se utiliza para vincularlos. Además, el conocimiento sobre la configuración de la pila, el almacenamiento y la memoria se envía a los siguientes bloques. Si conocemos los objetivos de todas las instrucciones JUMP y JUMPI, podemos construir un gráfico de flujo de control completo del programa. Si solo hay un objetivo que no conocemos (esto puede suceder porque, en principio, los objetivos de salto se pueden calcular a partir de las entradas), tenemos que borrar todo conocimiento sobre el estado de entrada de un bloque, ya que puede ser el objetivo del JUMP desconocido. . Si se encuentra un JUMPI cuya condición se evalúa como una constante, se transforma en un salto incondicional.

Como último paso, el código de cada bloque se vuelve a generar por completo. Se crea un gráfico de dependencia a partir de las expresiones en la pila al final del bloque y todas las operaciones que no forman parte de este gráfico se eliminan esencialmente. Ahora se genera un código que aplica las modificaciones a la memoria y el almacenamiento en el orden en que se realizaron en el código original (eliminando las modificaciones que se consideró que no eran necesarias) y, finalmente, genera todos los valores que se requieren para estar en la pila en el orden correcto. lugar.

Estos pasos se aplican a cada bloque básico y el código recién generado se usa como reemplazo si es más pequeño. Si un bloque básico se divide en un JUMPI y durante el análisis, la condición se evalúa como una constante, el JUMPI se reemplaza según el valor de la constante y, por lo tanto, se codifica como

var x = 7;
data[7] = 9;
if (data[x] != x + 2)
  return 2;
else
  return 1;

se simplifica a código que también se puede compilar a partir de

data[7] = 9;
return 1;

a pesar de que las instrucciones contenían un salto al principio.