Search code examples
phpmagic-methodsmicrobenchmark

Why is an empty __set() method slower than one that does work?


I was toying around with PHP magic methods (specifically Property overloading), and, while micro-benchmarking, encountered a quirk I'm having trouble explaining:

It seems that a __set method with an empty body takes more time to run than one which does work. The below code snippet demonstrates this:

class EmptySetter {
    public function __set($name, $value) {}
}

class NonEmptySetter {
    public function __set($name, $value) {
        $this->{$name} = $value;
    }
}

function benchmark($obj) {
    $start_time = microtime(TRUE);
    for ($i = 0; $i < 10000000; $i++) {
        $obj->foo = 42;
    }
    return microtime(TRUE) - $start_time;
}

printf("EmptySetter: %.2f seconds\n", benchmark(new EmptySetter));
printf("NonEmptySetter: %.2f seconds\n", benchmark(new NonEmptySetter));

// output (on my Core 2 Duo laptop):
// EmptySetter: 4.39 seconds
// NonEmptySetter: 1.28 seconds

Does anyone have an explanation as to why this is happening?


Solution

  • Oh, i see it's wrong testing case.

    After first loop NonEmptySetter would has new public property foo. Next loops do not call __set method at all, they use public property.

    class NonEmptySetter {
        public function __set($name, $value) {
            echo 'called only once'; // would be echoed only once.
            $this->{$name} = $value;
        }
    }
    

    Valid test

    class EmptySetter {
        public function __set($name, $value) {}
    }
    
    class NonEmptySetter {
        public function __set($name, $value) {
            $this->{$name} = $value;
        }
    }
    
    function benchmark($class_name) {
        $start_time = microtime(TRUE);
        for ($i = 0; $i < 1000000; $i++) {
            $obj = new $class_name();
            $obj->foo = 42;
        }
        return microtime(TRUE) - $start_time;
    }
    
    printf("NonEmptySetter: %.2f seconds\n", benchmark('NonEmptySetter'));
    printf("EmptySetter: %.2f seconds\n", benchmark('EmptySetter'));
    

    http://3v4l.org/gVtSq

    Empty setter is faster.