TysonAndre / phan

Phan is a static analyzer for PHP. Phan prefers to avoid false-positives and attempts to prove incorrectness rather than correctness.
Other
0 stars 0 forks source link

Investigate refactoring Visitors to create fewer Visitor objects #129

Open TysonAndre opened 6 years ago

TysonAndre commented 6 years ago

E.g. this may speed up UnionTypeVisitor, ParseVisitor, and BlockAnalysisVisitor.

Also, making PreOrderAnalysisVisitor and PostOrderAnalysisVisitor into traits (and renaming visitAssign to postOrderVisitAssign, etc) may also improve performance.

<?php
// Took 0.001199 (For ObjectVisitor) vs 0.000682 (For SameObjectVisitor) : iterations=100
// (58% of the time)
use ast\Node;

class TestCodebase {
}

class TestContext {
    public $visits = 0;
}

class ObjectVisitor {
    private $code_base;
    private $context;

    public function __construct(TestCodeBase $code_base, TestContext $context) {
        $this->code_base = $code_base;
        $this->context = $context;
    }

    public function visitA(Node $node) : TestContext {
        $context = $this->context;
        $context->visits += 10;
        foreach ($node->children as $child_node) {
            if (!($child_node instanceof Node)) {
                continue;
            }
            (new ObjectVisitor($this->code_base, $context))->{$node->kind % 2 === 1 ? 'visitA' : 'visitB'}($child_node);
        }
        return $context;
    }

    public function visitB(Node $node) : TestContext {
        $context = $this->context;
        $context->visits++;
        foreach ($node->children as $child_node) {
            if (!($child_node instanceof Node)) {
                continue;
            }
            (new ObjectVisitor($this->code_base, $context))->{$node->kind % 2 === 1 ? 'visitA' : 'visitB'}($child_node);
        }
        return $context;
    }
}

class SameObjectVisitor {
    private $code_base;

    public function __construct(TestCodeBase $code_base) {
        $this->code_base = $context;
    }

    public function visitA(TestContext $context, Node $node) : TestContext {
        $context->visits+= 10;
        foreach ($node->children as $child_node) {
            if (!($child_node instanceof Node)) {
                continue;
            }
            $this->{$node->kind % 2 === 1 ? 'visitA' : 'visitB'}($context, $child_node);
        }
        return $context;
    }

    public function visitB(TestContext $context, Node $node) : TestContext {
        $context->visits++;
        foreach ($node->children as $child_node) {
            if (!($child_node instanceof Node)) {
                continue;
            }
            $this->{$node->kind % 2 === 1 ? 'visitA' : 'visitB'}($context, $child_node);
        }
        return $context;
    }
}

function run_object(TestCodeBase $code_base, Node $root) : TestContext{
    $context = new TestContext();
    return (new ObjectVisitor($code_base, $context))->visitA($root);
}

function run_recursive(TestCodeBase $code_base, Node $root) : TestContext{
    $context = new TestContext();
    return (new SameObjectVisitor($code_base))->visitA($context, $root);
}

const ITERATION_COUNT = 100;

function main() {
    $ast = \ast\parse_file(__DIR__ . '/src/Phan/Analysis/PostOrderAnalysisVisitor.php', 50);
    $code_base = new TestCodeBase();
    $start = microtime(true);
    for ($i = 0; $i < ITERATION_COUNT; $i++) {
        run_object($code_base, $ast);
    }
    $end = microtime(true);

    $start2 = microtime(true);
    for ($i = 0; $i < ITERATION_COUNT; $i++) {
        run_recursive($code_base, $ast);
    }
    $end2 = microtime(true);
    printf("Took %.6f vs %.6f : iterations=%d\n", ($end-$start)/ITERATION_COUNT, ($end2-$start2)/ITERATION_COUNT, ITERATION_COUNT);
}
main();