Skip to content

Commit

Permalink
Rewritten benchmarks into PHP and added Python measurement (if availa…
Browse files Browse the repository at this point in the history
…ble).

Also doint the actual code speed measurements inside the benchmarks, to
minimize impact of its surroundings (interpreter boot time, etc.) to
the measurement.
  • Loading branch information
smuuf committed Dec 6, 2022
1 parent c6af30f commit d9dd7a7
Show file tree
Hide file tree
Showing 11 changed files with 252 additions and 101 deletions.
Empty file modified bin/_helpers.sh
100644 → 100755
Empty file.
159 changes: 159 additions & 0 deletions bin/bench
Original file line number Diff line number Diff line change
@@ -0,0 +1,159 @@
#!/usr/bin/env php
<?php

declare(strict_types=1);

use Smuuf\Primi\Cli\Term;
use Smuuf\Primi\Helpers\Colors;

require __DIR__ . '/../vendor/autoload.php';
chdir(__DIR__ . '/..');

/**
* Runs a shell command, prints out the first line of output as it goes, then
* don't show the rest, but return the rest of the output as a string.
*/
function run_shell_cmd(string $cmd): string {

$output = [];
$firstLine = true;
$proc = popen($cmd, 'r');
while (!feof($proc)) {

$buffer = fread($proc, 4096);

if ($firstLine) {

// If were gathering the first line of output and there's a newline
// character in the current buffer, split it, echo the first part
// (which is still the first line) and gather the second part into
// our output buffer (which we will want to return from this
// function).
if (($newLinePos = mb_strpos($buffer, "\n")) !== false) {
//echo "█D";
echo mb_substr($buffer, 0, $newLinePos);
$output[] = mb_substr($buffer, $newLinePos + 1);
$firstLine = false;
} else {
//echo "█B";
echo $buffer;
}

@flush();

} else {
//echo "█O";
$output[] = $buffer;
}

}

pclose($proc);
return trim(implode('', $output));

}


function show_results(array $data, string $standardKey): void {

$standardAvg = array_sum($data[$standardKey]) / count($data[$standardKey]);
foreach ($data as $key => $times) {

$avgTime = round(array_sum($times) / count($times), 6);
$paddedKey = str_pad("$key:", 10);

$slowerInfo = '';
if ($key !== $standardKey) {
$slower = round($avgTime / $standardAvg, 2);
$slowerInfo = "({$slower}x slower)";
}

echo " $paddedKey $avgTime s $slowerInfo\n";

}

}

function color(string $text, string $color, bool $noNewline = false): void {
$out = Colors::get("{{$color}}█{_} $text");

if ($noNewline) {
echo $out;
return;
}

echo Term::line($out);
}

function title(string $text, bool $noNewline = false): void {
color($text, color: 'yellow', noNewline: $noNewline);
}

function info(string $text, bool $noNewline = false): void {
color($text, color: 'blue', noNewline: $noNewline);
}

function shell(string $cmd, bool $print = false): string {

$result = shell_exec($cmd);
if ($print) {
echo $result;
}

return $result ?? '';

}

function run_benchmark(string $name, string $command, int $times = 1): array {

$results = [];
foreach (range(1, $times) as $index) {
$index = $times !== 1 ? "[$index] " : '';
info("{yellow}$name{_} {darkgrey}$index{_}... ", noNewline: true);

// Output of the bench program (except the first line) looks like this:
// 1. line - self-measured time.
// 2. line - self-measured memory peak.
$output = run_shell_cmd($command);
[$time, $mempeak] = explode("\n", $output);

echo "\n";
echo Term::line(" Took $time s");
echo Term::line(" Mempeak $mempeak MB");
$results[] = $time;

}

return $results;

}

$interpreter = $argv[1] ?? 'php';

info("Using interpreter: $interpreter");
shell("$interpreter --version | head -n1", print: true);

const ITERATIONS = 3;

$perfPhpPath = './tests/bench/perf_bench_php.php';
$perfPythonPath = './tests/bench/perf_bench_python.py';
$perfPrimiPath = './tests/bench/perf_bench_primi.primi';

$results = [
'php' => [],
'python' => [],
'primi' => [],
];

$results['php'] = run_benchmark('PHP', "$interpreter $perfPhpPath", times: 3);

if (shell("command -v python")) {
$results['python'] = run_benchmark('Python', "python $perfPythonPath", times: 3);
} else {
info("Python not available.");
}

$results['primi'] = run_benchmark('Primi', "$interpreter ./primi $perfPrimiPath", times: 3);

title("Results:");
show_results($results, 'php');
100 changes: 0 additions & 100 deletions bin/bench.sh

This file was deleted.

Empty file modified bin/buildphar.sh
100644 → 100755
Empty file.
Empty file modified bin/phpstan.sh
100644 → 100755
Empty file.
Empty file modified bin/tests-smoke.sh
100644 → 100755
Empty file.
Empty file modified bin/tests-unit.sh
100644 → 100755
Empty file.
Empty file modified bin/tests.sh
100644 → 100755
Empty file.
7 changes: 7 additions & 0 deletions tests/bench/perf_bench_php.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

declare(strict_types=1);

$t = hrtime(true);

ini_set('display_errors', '1');
ini_set('display_startup_errors', '1');
error_reporting(E_ALL);
Expand Down Expand Up @@ -100,4 +102,9 @@ function bench_dicts() {
measure(decor('bench_function_calls'));
measure(decor('bench_regex_matches'));
measure(decor('bench_dicts'));

$mempeak = memory_get_peak_usage() / 1_000_000;
$time = (hrtime(true) - $t) / 1_000_000_000;

echo "\n";
echo "$time\n$mempeak";
7 changes: 6 additions & 1 deletion tests/bench/perf_bench_primi.primi
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import std.time
import std.runtime.memory

t = time.monotonic()

max_iter = 50000
tenth_iter = max_iter / 10

Expand Down Expand Up @@ -82,4 +85,6 @@ function bench_dicts() {
decor(bench_function_calls)()
decor(bench_regex_matches)()
decor(bench_dicts)()
print(f" Mem peak {memory.get_peak() / 1_000_000} MB")

print()
print(f"{time.monotonic() - t}\n{memory.get_peak() / 1_000_000}")
80 changes: 80 additions & 0 deletions tests/bench/perf_bench_python.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import re
import time
import resource

t = time.monotonic()

max_iter = 50000
tenth_iter = max_iter / 10

def decor(fn):
def _inner():
print("(", end='')
fn()
print(")", end='')

return _inner

def bench_function_calls():
adder = lambda x, y: x + y

result = -1024
c = 0
while c < max_iter:
result = result + adder(c, 1)
c = c + 1
if c % tenth_iter == 0:
print(':', end='')

return c


def bench_regex_matches():

haystack = "Když začínáme myslet, nemáme k dispozici nic jiného než myšlenku v " + \
"její čisté neurčenosti, neboť k určení již patří jedno nebo nějaké " + \
"jiné, ale na začátku ještě nemáme žádné jiné..."

regex = re.compile("^.*(zač).*(,)?.*?(\.)")

result = 0
c = 0
while c < max_iter:
result = result + int(bool(regex.match(haystack)))
c = c + 1
if c % tenth_iter == 0:
print(':', end='')

return c

def bench_dicts():

c = 0
result = []

while c < max_iter:

dict_ = {
'a': True,
'b': False,
'c': None,
'd': 'áhojky, plantážníku!',
'keys': ['a', 'b', 'c'],
}

for name in dict_['keys']:
result.append(dict[name])

c = c + 1
if c % tenth_iter == 0:
print(':', end='')

return result

decor(bench_function_calls)()
decor(bench_regex_matches)()
decor(bench_dicts)()

mempeak = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
print()
print(f"{time.monotonic() - t}\n{mempeak / 1_000}")

0 comments on commit d9dd7a7

Please sign in to comment.