mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	
		
			
				
	
	
		
			141 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
			
		
		
	
	
			141 lines
		
	
	
		
			4.0 KiB
		
	
	
	
		
			Python
		
	
	
	
	
	
"""A script to collect gas statistics and print it.
 | 
						|
 | 
						|
Useful to summarize gas differences to semantic tests for a PR / branch.
 | 
						|
 | 
						|
Dependencies: Parsec (https://pypi.org/project/parsec/) and Tabulate
 | 
						|
(https://pypi.org/project/tabulate/)
 | 
						|
 | 
						|
  pip install parsec tabulate
 | 
						|
 | 
						|
Run from root project dir.
 | 
						|
 | 
						|
  python3 scripts/gas_diff_stats.py
 | 
						|
 | 
						|
Note that the changes to semantic tests have to be committed.
 | 
						|
 | 
						|
Assumes that there is a remote named ``origin`` pointing to the Solidity github
 | 
						|
repository. The changes are compared against ``origin/develop``.
 | 
						|
 | 
						|
"""
 | 
						|
import subprocess
 | 
						|
from pathlib import Path
 | 
						|
from enum import Enum
 | 
						|
from parsec import generate, ParseError, regex, string
 | 
						|
from tabulate import tabulate
 | 
						|
 | 
						|
class Kind(Enum):
 | 
						|
    IrOptimized = 1
 | 
						|
    Legacy = 2
 | 
						|
    LegacyOptimized = 3
 | 
						|
 | 
						|
class Diff(Enum):
 | 
						|
    Minus = 1
 | 
						|
    Plus = 2
 | 
						|
 | 
						|
minus = string("-").result(Diff.Minus)
 | 
						|
plus = string("+").result(Diff.Plus)
 | 
						|
 | 
						|
space = string(" ")
 | 
						|
comment = string("//")
 | 
						|
colon = string(":")
 | 
						|
 | 
						|
gas_ir_optimized = string("gas irOptimized").result(Kind.IrOptimized)
 | 
						|
gas_legacy_optimized = string("gas legacyOptimized").result(Kind.LegacyOptimized)
 | 
						|
gas_legacy = string("gas legacy").result(Kind.Legacy)
 | 
						|
 | 
						|
def number() -> int:
 | 
						|
    """Parse number."""
 | 
						|
    return regex(r"([0-9]*)").parsecmap(int)
 | 
						|
 | 
						|
@generate
 | 
						|
def diff_string() -> (Kind, Diff, int):
 | 
						|
    """Usage: diff_string.parse(string)
 | 
						|
 | 
						|
    Example string:
 | 
						|
 | 
						|
    -// gas irOptimized: 138070
 | 
						|
 | 
						|
    """
 | 
						|
    diff_kind = yield minus | plus
 | 
						|
    yield comment
 | 
						|
    yield space
 | 
						|
    codegen_kind = yield gas_ir_optimized ^ gas_legacy_optimized ^ gas_legacy
 | 
						|
    yield colon
 | 
						|
    yield space
 | 
						|
    val = yield number()
 | 
						|
    return (diff_kind, codegen_kind, val)
 | 
						|
 | 
						|
def collect_statistics(lines) -> (int, int, int, int, int, int):
 | 
						|
    """Returns
 | 
						|
 | 
						|
    (old_ir_optimized, old_legacy_optimized, old_legacy, new_ir_optimized,
 | 
						|
    new_legacy_optimized, new_legacy)
 | 
						|
 | 
						|
    All the values in the same file (in the diff) are summed up.
 | 
						|
 | 
						|
    """
 | 
						|
    if not lines:
 | 
						|
        raise Exception("Empty list")
 | 
						|
 | 
						|
    def try_parse(line):
 | 
						|
        try:
 | 
						|
            return diff_string.parse(line)
 | 
						|
        except ParseError:
 | 
						|
            pass
 | 
						|
        return None
 | 
						|
 | 
						|
    out = [parsed for line in lines if (parsed := try_parse(line)) is not None]
 | 
						|
    diff_kinds = [Diff.Minus, Diff.Plus]
 | 
						|
    codegen_kinds = [Kind.IrOptimized, Kind.LegacyOptimized, Kind.Legacy]
 | 
						|
    return tuple(
 | 
						|
        sum(
 | 
						|
            val
 | 
						|
            for (diff_kind, codegen_kind, val) in out
 | 
						|
            if diff_kind == _diff_kind and codegen_kind == _codegen_kind
 | 
						|
        )
 | 
						|
        for _diff_kind in diff_kinds
 | 
						|
        for _codegen_kind in codegen_kinds
 | 
						|
    )
 | 
						|
 | 
						|
def semantictest_statistics():
 | 
						|
    """Prints the tabulated statistics that can be pasted in github."""
 | 
						|
    def try_parse_git_diff(fname):
 | 
						|
        try:
 | 
						|
            diff_output = subprocess.check_output(
 | 
						|
                "git diff --unified=0 origin/develop HEAD " + fname,
 | 
						|
                shell=True,
 | 
						|
                universal_newlines=True
 | 
						|
            ).splitlines()
 | 
						|
            if diff_output:
 | 
						|
                return collect_statistics(diff_output)
 | 
						|
        except subprocess.CalledProcessError as e:
 | 
						|
            print("Error in the git diff:")
 | 
						|
            print(e.output)
 | 
						|
        return None
 | 
						|
    def stat(old, new):
 | 
						|
        return ((new - old) / old) * 100  if old else 0
 | 
						|
 | 
						|
    table = []
 | 
						|
 | 
						|
    for path in Path("test/libsolidity/semanticTests").rglob("*.sol"):
 | 
						|
        fname = path.as_posix()
 | 
						|
        parsed = try_parse_git_diff(fname)
 | 
						|
        if parsed is None:
 | 
						|
            continue
 | 
						|
        ir_optimized = stat(parsed[0], parsed[3])
 | 
						|
        legacy_optimized = stat(parsed[1], parsed[4])
 | 
						|
        legacy = stat(parsed[2], parsed[5])
 | 
						|
        fname = fname.split('/', 3)[-1]
 | 
						|
        table += [map(str, [fname, ir_optimized, legacy_optimized, legacy])]
 | 
						|
 | 
						|
    if table:
 | 
						|
        print("<details><summary>Click for a table of gas differences</summary>\n")
 | 
						|
        table_header = ["File name", "IR-optimized (%)", "Legacy-Optimized (%)", "Legacy (%)"]
 | 
						|
        print(tabulate(table, headers=table_header, tablefmt="github"))
 | 
						|
        print("</details>")
 | 
						|
    else:
 | 
						|
        print("No differences found.")
 | 
						|
 | 
						|
if __name__ == "__main__":
 | 
						|
    semantictest_statistics()
 |