├── .gitignore ├── src └── Markbench │ ├── Exception │ └── TooMuchPackageFoundException.php │ ├── Renderer.php │ ├── ProfileInterface.php │ ├── Driver │ ├── PHPMarkdownExtraDriver.php │ ├── CiconiaGfmDriver.php │ ├── ParsedownDriver.php │ ├── CiconiaDriver.php │ ├── CebeMarkdownDriver.php │ ├── PHPMarkdownDriver.php │ └── CebeMarkdownGfmDriver.php │ ├── DriverInterface.php │ ├── Profile │ ├── BlankInputProfile.php │ ├── DefaultProfile.php │ └── GithubSampleProfile.php │ ├── Runner.php │ ├── Task.php │ ├── Result.php │ └── Console │ └── Command │ └── BenchmarkCommand.php ├── .travis.yml ├── bin └── markbench ├── composer.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /vendor/ 2 | /composer.lock 3 | composer.phar 4 | -------------------------------------------------------------------------------- /src/Markbench/Exception/TooMuchPackageFoundException.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | class TooMuchPackageFoundException extends \ErrorException 9 | { 10 | 11 | } -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: php 2 | 3 | php: 4 | - 5.5 5 | - 5.4 6 | 7 | before_script: 8 | - curl -s http://getcomposer.org/installer | php 9 | - php composer.phar update --dev --no-interaction 10 | 11 | script: 12 | - php bin/markbench benchmark --profile=blank 13 | - php bin/markbench benchmark --profile=default 14 | - php bin/markbench benchmark --profile=github-sample 15 | -------------------------------------------------------------------------------- /bin/markbench: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | add(new \Markbench\Console\Command\BenchmarkCommand()); 22 | $app->run(); -------------------------------------------------------------------------------- /src/Markbench/Renderer.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class Renderer 11 | { 12 | 13 | /** 14 | * @var Result[] 15 | */ 16 | private $results; 17 | 18 | /** 19 | * @param array Result[] 20 | */ 21 | public function __construct($results = []) 22 | { 23 | $this->results = $results; 24 | } 25 | 26 | public function render(OutputInterface $output) 27 | { 28 | 29 | } 30 | 31 | } -------------------------------------------------------------------------------- /src/Markbench/ProfileInterface.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | interface ProfileInterface 9 | { 10 | 11 | /** 12 | * Returns the name of this profile 13 | * 14 | * @return mixed 15 | */ 16 | public function getName(); 17 | 18 | /** 19 | * Describe this profile 20 | * 21 | * @return string 22 | */ 23 | public function getDescription(); 24 | 25 | /** 26 | * Returns markdown content to test 27 | * 28 | * @return string 29 | */ 30 | public function getContent(); 31 | 32 | /** 33 | * @return mixed 34 | */ 35 | public function getLoopCount(); 36 | 37 | } -------------------------------------------------------------------------------- /src/Markbench/Driver/PHPMarkdownExtraDriver.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class PHPMarkdownExtraDriver extends PHPMarkdownDriver 11 | { 12 | 13 | /** 14 | * {@inheritdoc} 15 | */ 16 | public function initialize() 17 | { 18 | $this->phpMarkdown = new MarkdownExtra(); 19 | } 20 | 21 | /** 22 | * {@inheritdoc} 23 | */ 24 | public function getDialect() 25 | { 26 | return 'extra'; 27 | } 28 | 29 | 30 | /** 31 | * {@inheritdoc} 32 | */ 33 | public function checkRequirements() 34 | { 35 | return (parent::checkRequirements() && class_exists('Michelf\\MarkdownExtra')); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/Markbench/Driver/CiconiaGfmDriver.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class CiconiaGfmDriver extends CiconiaDriver 12 | { 13 | 14 | /** 15 | * {@inheritdoc} 16 | */ 17 | public function initialize() 18 | { 19 | $this->ciconia = new Ciconia(); 20 | $this->ciconia->addExtensions([ 21 | new Gfm\FencedCodeBlockExtension(), 22 | new Gfm\InlineStyleExtension(), 23 | new Gfm\TableExtension(), 24 | new Gfm\TaskListExtension(), 25 | new Gfm\WhiteSpaceExtension() 26 | ]); 27 | } 28 | 29 | /** 30 | * {@inheritdoc} 31 | */ 32 | public function getDialect() 33 | { 34 | return 'gfm'; 35 | } 36 | 37 | } -------------------------------------------------------------------------------- /src/Markbench/DriverInterface.php: -------------------------------------------------------------------------------- 1 | 7 | */ 8 | interface DriverInterface 9 | { 10 | 11 | /** 12 | * Initialize a parser 13 | * 14 | * @return void 15 | */ 16 | public function initialize(); 17 | 18 | /** 19 | * Transform markdown into HTML 20 | * 21 | * @param string $markdown 22 | * 23 | * @return string 24 | */ 25 | public function run($markdown = ''); 26 | 27 | /** 28 | * Returns the package name of composer 29 | * 30 | * @return string 31 | */ 32 | public function getName(); 33 | 34 | /** 35 | * Returns the name of dialect 36 | * 37 | * @return mixed 38 | */ 39 | public function getDialect(); 40 | 41 | /** 42 | * @return boolean 43 | */ 44 | public function checkRequirements(); 45 | 46 | } 47 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "kzykhys/markbench", 3 | "description": "The markdown benchmark framework", 4 | "keywords": ["markdown", "benchmark"], 5 | "license": "MIT", 6 | "minimum-stability": "stable", 7 | "authors": [ 8 | { 9 | "name": "Kazuyuki Hayashi", 10 | "email": "hayashi@valnur.net" 11 | } 12 | ], 13 | "autoload": { 14 | "psr-0": { 15 | "": "src/" 16 | } 17 | }, 18 | "require": { 19 | "composer/composer": "*@dev", 20 | "symfony/console": "~2.3", 21 | "symfony/finder": "~2.3", 22 | "kzykhys/parallel": ">=0.1", 23 | "symfony/stopwatch": "~2.3", 24 | "kzykhys/git": "*" 25 | }, 26 | "require-dev": { 27 | "kzykhys/ciconia": "*", 28 | "michelf/php-markdown": "*", 29 | "erusev/parsedown": "*", 30 | "cebe/markdown": "*" 31 | }, 32 | "bin": ["bin/markbench"] 33 | } 34 | -------------------------------------------------------------------------------- /src/Markbench/Profile/BlankInputProfile.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class BlankInputProfile implements ProfileInterface 11 | { 12 | 13 | /** 14 | * Returns the name of this profile 15 | * 16 | * @return mixed 17 | */ 18 | public function getName() 19 | { 20 | return 'blank'; 21 | } 22 | 23 | /** 24 | * Describe this profile 25 | * 26 | * @return string 27 | */ 28 | public function getDescription() 29 | { 30 | return 'Blank input "" / 1000 times'; 31 | } 32 | 33 | /** 34 | * Returns markdown content to test 35 | * 36 | * @return string 37 | */ 38 | public function getContent() 39 | { 40 | return ''; 41 | } 42 | 43 | /** 44 | * @return mixed 45 | */ 46 | public function getLoopCount() 47 | { 48 | return 1000; 49 | } 50 | 51 | } -------------------------------------------------------------------------------- /src/Markbench/Driver/ParsedownDriver.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class ParsedownDriver implements DriverInterface 11 | { 12 | 13 | /** 14 | * @var \Parsedown 15 | */ 16 | private $parsedown; 17 | 18 | /** 19 | * {@inheritdoc} 20 | */ 21 | public function initialize() 22 | { 23 | $this->parsedown = \Parsedown::instance(); 24 | } 25 | 26 | /** 27 | * {@inheritdoc} 28 | */ 29 | public function run($markdown = '') 30 | { 31 | return $this->parsedown->parse($markdown); 32 | } 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public function getName() 38 | { 39 | return 'erusev/parsedown'; 40 | } 41 | 42 | /** 43 | * {@inheritdoc} 44 | */ 45 | public function getDialect() 46 | { 47 | return null; 48 | } 49 | 50 | /** 51 | * {@inheritdoc} 52 | */ 53 | public function checkRequirements() 54 | { 55 | return class_exists('Parsedown'); 56 | } 57 | 58 | } 59 | -------------------------------------------------------------------------------- /src/Markbench/Driver/CiconiaDriver.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class CiconiaDriver implements DriverInterface 12 | { 13 | 14 | /** 15 | * @var Ciconia 16 | */ 17 | protected $ciconia; 18 | 19 | /** 20 | * {@inheritdoc} 21 | */ 22 | public function initialize() 23 | { 24 | $this->ciconia = new Ciconia(); 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public function run($markdown = '') 31 | { 32 | return $this->ciconia->render($markdown); 33 | } 34 | 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | public function getName() 39 | { 40 | return 'kzykhys/ciconia'; 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function getDialect() 47 | { 48 | return null; 49 | } 50 | 51 | /** 52 | * {@inheritdoc} 53 | */ 54 | public function checkRequirements() 55 | { 56 | return class_exists('Ciconia\\Ciconia'); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/Markbench/Driver/CebeMarkdownDriver.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class CebeMarkdownDriver implements DriverInterface 12 | { 13 | 14 | /** 15 | * @var Markdown 16 | */ 17 | private $markdown; 18 | 19 | /** 20 | * {@inheritdoc} 21 | */ 22 | public function initialize() 23 | { 24 | $this->markdown = new Markdown(); 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public function run($markdown = '') 31 | { 32 | return $this->markdown->parse($markdown); 33 | } 34 | 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | public function getName() 39 | { 40 | return 'cebe/markdown'; 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function getDialect() 47 | { 48 | return null; 49 | } 50 | 51 | /** 52 | * {@inheritdoc} 53 | */ 54 | public function checkRequirements() 55 | { 56 | return class_exists('cebe\markdown\Markdown'); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/Markbench/Driver/PHPMarkdownDriver.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class PHPMarkdownDriver implements DriverInterface 12 | { 13 | 14 | /** 15 | * @var Markdown 16 | */ 17 | protected $phpMarkdown; 18 | 19 | /** 20 | * {@inheritdoc} 21 | */ 22 | public function initialize() 23 | { 24 | $this->phpMarkdown = new Markdown(); 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public function run($markdown = '') 31 | { 32 | return $this->phpMarkdown->defaultTransform($markdown); 33 | } 34 | 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | public function getName() 39 | { 40 | return 'michelf/php-markdown'; 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function getDialect() 47 | { 48 | return null; 49 | } 50 | 51 | /** 52 | * {@inheritdoc} 53 | */ 54 | public function checkRequirements() 55 | { 56 | return class_exists('Michelf\\Markdown'); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/Markbench/Driver/CebeMarkdownGfmDriver.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | class CebeMarkdownGfmDriver implements DriverInterface 12 | { 13 | 14 | /** 15 | * @var GithubMarkdown 16 | */ 17 | private $markdown; 18 | 19 | /** 20 | * {@inheritdoc} 21 | */ 22 | public function initialize() 23 | { 24 | $this->markdown = new GithubMarkdown(); 25 | } 26 | 27 | /** 28 | * {@inheritdoc} 29 | */ 30 | public function run($markdown = '') 31 | { 32 | return $this->markdown->parse($markdown); 33 | } 34 | 35 | /** 36 | * {@inheritdoc} 37 | */ 38 | public function getName() 39 | { 40 | return 'cebe/markdown'; 41 | } 42 | 43 | /** 44 | * {@inheritdoc} 45 | */ 46 | public function getDialect() 47 | { 48 | return 'gfm'; 49 | } 50 | 51 | /** 52 | * {@inheritdoc} 53 | */ 54 | public function checkRequirements() 55 | { 56 | return class_exists('cebe\markdown\GithubMarkdown'); 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /src/Markbench/Runner.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class Runner 11 | { 12 | 13 | /** 14 | * @var DriverInterface[] 15 | */ 16 | private $drivers = []; 17 | 18 | /** 19 | * @param DriverInterface $driver 20 | */ 21 | public function addDriver(DriverInterface $driver) 22 | { 23 | $this->drivers[$driver->getName().$driver->getDialect()] = $driver; 24 | } 25 | 26 | /** 27 | * @return DriverInterface[] 28 | */ 29 | public function getDrivers() 30 | { 31 | return $this->drivers; 32 | } 33 | 34 | /** 35 | * @param string $markdown 36 | * @param int $loopCount 37 | * 38 | * @return Result[] 39 | */ 40 | public function run($markdown = '', $loopCount = 1000) 41 | { 42 | $tasks = array_map(function (DriverInterface $driver) use ($markdown, $loopCount) { 43 | $task = new Task($driver); 44 | $task->setContent($markdown)->setLoopCount($loopCount); 45 | 46 | return array($task, 'run'); 47 | }, $this->drivers); 48 | 49 | $parallel = new Parallel(); 50 | 51 | return $parallel->values($tasks); 52 | } 53 | 54 | } -------------------------------------------------------------------------------- /src/Markbench/Task.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class Task 11 | { 12 | 13 | /** 14 | * @var DriverInterface 15 | */ 16 | private $driver; 17 | 18 | /** 19 | * @var string 20 | */ 21 | private $content = ''; 22 | 23 | /** 24 | * @var int 25 | */ 26 | private $loopCount = 1000; 27 | 28 | /** 29 | * @param DriverInterface $driver 30 | */ 31 | public function __construct(DriverInterface $driver) 32 | { 33 | $this->driver = $driver; 34 | } 35 | 36 | /** 37 | * @param string $content 38 | * 39 | * @return Task 40 | */ 41 | public function setContent($content) 42 | { 43 | $this->content = $content; 44 | 45 | return $this; 46 | } 47 | 48 | /** 49 | * @param int $loopCount 50 | * 51 | * @return $this 52 | */ 53 | public function setLoopCount($loopCount) 54 | { 55 | $this->loopCount = $loopCount; 56 | 57 | return $this; 58 | } 59 | 60 | /** 61 | * @return Result 62 | */ 63 | public function run() 64 | { 65 | $stopwatch = new Stopwatch(); 66 | $result = ''; 67 | 68 | $stopwatch->start('parsing'); 69 | $this->driver->initialize(); 70 | $stopwatch->lap('parsing'); 71 | 72 | for ($i = 0; $i < $this->loopCount; $i++) { 73 | $result = $this->driver->run($this->content); 74 | } 75 | 76 | $event = $stopwatch->stop('parsing'); 77 | 78 | return new Result($this->driver->getName(), $this->driver->getDialect(), $event, $result, memory_get_peak_usage(true)); 79 | } 80 | 81 | } -------------------------------------------------------------------------------- /src/Markbench/Result.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class Result implements \Serializable 11 | { 12 | 13 | /** 14 | * @var string 15 | */ 16 | private $name; 17 | 18 | /** 19 | * @var 20 | */ 21 | private $output; 22 | 23 | /** 24 | * @var int 25 | */ 26 | private $duration; 27 | 28 | /** 29 | * @var int 30 | */ 31 | private $memory; 32 | 33 | /** 34 | * @var int 35 | */ 36 | private $peakMemory; 37 | /** 38 | * @var string 39 | */ 40 | private $dialect; 41 | 42 | /** 43 | * @param $name 44 | * @param $dialect 45 | * @param StopwatchEvent $event 46 | * @param $output 47 | * @param $peakMemory 48 | */ 49 | public function __construct($name, $dialect, StopwatchEvent $event, $output, $peakMemory) 50 | { 51 | $this->name = $name; 52 | $this->output = $output; 53 | $this->duration = $event->getDuration(); 54 | $this->memory = $event->getMemory(); 55 | $this->peakMemory = $peakMemory; 56 | $this->dialect = $dialect; 57 | } 58 | 59 | /** 60 | * @return string 61 | */ 62 | public function getName() 63 | { 64 | return $this->name; 65 | } 66 | 67 | /** 68 | * @return string 69 | */ 70 | public function getDialect() 71 | { 72 | return $this->dialect; 73 | } 74 | 75 | /** 76 | * @return int 77 | */ 78 | public function getDuration() 79 | { 80 | return $this->duration; 81 | } 82 | 83 | /** 84 | * @return int 85 | */ 86 | public function getMemory() 87 | { 88 | return $this->memory; 89 | } 90 | 91 | /** 92 | * @return int 93 | */ 94 | public function getPeakMemory() 95 | { 96 | return $this->peakMemory; 97 | } 98 | 99 | /** 100 | * @return mixed 101 | */ 102 | public function getOutput() 103 | { 104 | return $this->output; 105 | } 106 | 107 | /** 108 | * String representation of object 109 | * 110 | * @return string the string representation of the object or null 111 | */ 112 | public function serialize() 113 | { 114 | return serialize([ 115 | $this->name, 116 | $this->dialect, 117 | $this->output, 118 | $this->duration, 119 | $this->memory, 120 | $this->peakMemory 121 | ]); 122 | } 123 | 124 | /** 125 | * Constructs the object 126 | * 127 | * @param string $serialized The string representation of the object. 128 | * 129 | * @return void 130 | */ 131 | public function unserialize($serialized) 132 | { 133 | list( 134 | $this->name, 135 | $this->dialect, 136 | $this->output, 137 | $this->duration, 138 | $this->memory, 139 | $this->peakMemory 140 | ) = unserialize($serialized); 141 | } 142 | 143 | } 144 | -------------------------------------------------------------------------------- /src/Markbench/Profile/DefaultProfile.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class DefaultProfile implements ProfileInterface 11 | { 12 | 13 | /** 14 | * Returns the name of this profile 15 | * 16 | * @return mixed 17 | */ 18 | public function getName() 19 | { 20 | return 'default'; 21 | } 22 | 23 | /** 24 | * Describe this profile 25 | * 26 | * @return string 27 | */ 28 | public function getDescription() 29 | { 30 | return 'Basic markdown content with all official syntax / 1000 times'; 31 | } 32 | 33 | /** 34 | * Returns markdown content to test 35 | * 36 | * @return string 37 | */ 38 | public function getContent() 39 | { 40 | return << This is a blockquote with two paragraphs. Lorem ipsum dolor sit amet, 60 | > consectetuer adipiscing elit. Aliquam hendrerit mi posuere lectus. 61 | > Vestibulum enim wisi, viverra nec, fringilla in, laoreet vitae, risus. 62 | > 63 | > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse 64 | > id sem consectetuer libero luctus adipiscing. 65 | 66 | > Donec sit amet nisl. Aliquam semper ipsum sit amet velit. Suspendisse 67 | id sem consectetuer libero luctus adipiscing. 68 | 69 | > This is the first level of quoting. 70 | > 71 | > > This is nested blockquote. 72 | > 73 | > Back to the first level. 74 | 75 | > ## This is a header. 76 | > 77 | > 1. This is the first list item. 78 | > 2. This is the second list item. 79 | > 80 | > Here's some example code: 81 | > 82 | > return shell_exec('echo 1'); 83 | 84 | * Red 85 | * Green 86 | * Blue 87 | 88 | 1. Bird 89 | 2. McHale 90 | 3. Parish 91 | 92 | * Lorem ipsum dolor sit amet, consectetuer adipiscing elit. 93 | Aliquam hendrerit mi posuere lectus. Vestibulum enim wisi, 94 | viverra nec, fringilla in, laoreet vitae, risus. 95 | * Donec sit amet nisl. Aliquam semper ipsum sit amet velit. 96 | Suspendisse id sem consectetuer libero luctus adipiscing. 97 | 98 | * A list item with a blockquote: 99 | 100 | > This is a blockquote 101 | > inside a list item. 102 | 103 | Here is an example of AppleScript: 104 | 105 | tell application "Foo" 106 | beep 107 | end tell 108 | 109 | * * * 110 | 111 | *** 112 | 113 | ***** 114 | 115 | - - - 116 | 117 | --------------------------------------- 118 | 119 | This is [an example](http://example.com/ "Title") inline link. 120 | 121 | [This link](http://example.net/) has no title attribute. 122 | 123 | See my [About](/about/) page for details. 124 | 125 | This is [an example][id] reference-style link. 126 | Visit [Daring Fireball][] for more information. 127 | 128 | I get 10 times more traffic from [Google] [1] than from 129 | [Yahoo] [2] or [MSN] [3]. 130 | 131 | *single asterisks* 132 | 133 | _single underscores_ 134 | 135 | **double asterisks** 136 | 137 | __double underscores__ 138 | 139 | un*frigging*believable 140 | 141 | Use the `printf()` function. 142 | 143 | A single backtick in a code span: `` ` `` 144 | 145 | A backtick-delimited string in a code span: `` `foo` `` 146 | 147 | ![Alt text](/path/to/img.jpg) 148 | 149 | ![Alt text](/path/to/img.jpg "Optional title") 150 | 151 | 152 | 153 | 154 | 155 | [1]: http://google.com/ "Google" 156 | [2]: http://search.yahoo.com/ "Yahoo Search" 157 | [3]: http://search.msn.com/ "MSN Search" 158 | 159 | [id]: http://example.com/ "Optional Title Here" 160 | [Daring Fireball]: http://daringfireball.net/ 161 | EOF; 162 | 163 | } 164 | 165 | /** 166 | * @return mixed 167 | */ 168 | public function getLoopCount() 169 | { 170 | return 1000; 171 | } 172 | 173 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Markdown Benchmarks (PHP) 2 | ========================= 3 | 4 | [![Build Status](https://travis-ci.org/kzykhys/Markbench.png?branch=master)](https://travis-ci.org/kzykhys/Markbench) 5 | [![Latest Stable Version](https://poser.pugx.org/kzykhys/markbench/v/stable.png)](https://packagist.org/packages/kzykhys/markbench) 6 | [![SensioLabsInsight](https://insight.sensiolabs.com/projects/f85a4034-6089-4b14-acb5-990e202a5a55/mini.png)](https://insight.sensiolabs.com/projects/f85a4034-6089-4b14-acb5-990e202a5a55) 7 | 8 | All parsers are managed by composer (minimum-stability=stable). 9 | Tested with latest stable version. 10 | 11 | [**See the latest benchmark on Travis-CI**](https://travis-ci.org/kzykhys/Markbench) 12 | 13 | ``` 14 | $ php bin/markbench benchmark --profile=github-sample 15 | Runtime: PHP5.5.9 16 | Host: Linux testing-worker-linux-9-1-25355-linux-14-19501860 2.6.32-042stab079.5 #1 SMP Fri Aug 2 17:16:15 MSK 2013 x86_64 17 | Profile: Sample content from Github (http://github.github.com/github-flavored-markdown/sample_content.html) / 1000 times 18 | Class: Markbench\Profile\GithubSampleProfile 19 | 20 | +----------------------+---------+---------+---------------+----------+--------------+ 21 | | package | version | dialect | duration (MS) | MEM (B) | PEAK MEM (B) | 22 | +----------------------+---------+---------+---------------+----------+--------------+ 23 | | erusev/parsedown | 0.9.4 | | 4183 | 9437184 | 9437184 | 24 | | cebe/markdown | 0.9.2 | | 5081 | 9437184 | 9699328 | 25 | | cebe/markdown | 0.9.2 | gfm | 6293 | 9437184 | 9699328 | 26 | | michelf/php-markdown | 1.4.0 | | 15568 | 9699328 | 9699328 | 27 | | michelf/php-markdown | 1.4.0 | extra | 23401 | 9699328 | 9961472 | 28 | | kzykhys/ciconia | v1.0.3 | | 32115 | 10747904 | 11010048 | 29 | | kzykhys/ciconia | v1.0.3 | gfm | 39654 | 10747904 | 11010048 | 30 | +----------------------+---------+---------+---------------+----------+--------------+ 31 | ``` 32 | 33 | Tested parsers 34 | -------------- 35 | 36 | * [michelf/php-markdown](https://github.com/michelf/php-markdown) [![Latest Stable Version](https://poser.pugx.org/michelf/php-markdown/v/stable.png)](https://packagist.org/packages/michelf/php-markdown) 37 | * [kzykhys/ciconia](https://github.com/kzykhys/Ciconia) [![Latest Stable Version](https://poser.pugx.org/kzykhys/ciconia/v/stable.png)](https://packagist.org/packages/kzykhys/ciconia) 38 | * [erusev/parsedown](https://github.com/erusev/parsedown) [![Latest Stable Version](https://poser.pugx.org/erusev/parsedown/v/stable.png)](https://packagist.org/packages/erusev/parsedown) 39 | * [cebe/markdown](https://github.com/cebe/markdown) [![Latest Stable Version](https://poser.pugx.org/cebe/markdown/v/stable.png)](https://packagist.org/packages/cebe/markdown) 40 | 41 | Internals 42 | --------- 43 | 44 | Each parser is executed asynchronously using [kzykhys/Parallel.php](https://github.com/kzykhys/Parallel.php) 45 | 46 | ``` 47 | Runner 48 | +-->(kzykhys/Parallel.php) 49 | +-- child process #1 --+ 50 | +-- child process #2 --+--> output 51 | +-- child process #3 --+ 52 | |-- duration/mem usage --| 53 | ``` 54 | 55 | ### Requirements 56 | 57 | * PHP5.4+ 58 | * Compiled with --enable-pcntl 59 | 60 | Add a parser 61 | ------------ 62 | 63 | * Put your class that implements `Markbench\DriverInterface` into `Driver` directory. 64 | * Run command again 65 | 66 | **Feel free to fork and send a pull request!** 67 | 68 | Run a benchmark 69 | --------------- 70 | 71 | ``` 72 | composer install 73 | bin/markbench benchmark 74 | ``` 75 | 76 | ``` 77 | $ bin/markbench help benchmark 78 | Usage: 79 | benchmark [--parser="..."] [-p|--profile[="..."]] 80 | 81 | Options: 82 | --parser Name of a parser. Available: cebe/markdown, cebe/markdown:gfm, kzykhys/ciconia, kzykhys/ciconia:gfm, erusev/parsedown, michelf/php-markdown, michelf/php-markdown:extra 83 | --profile (-p) Name of a profile. (default: "default") 84 | --help (-h) Display this help message. 85 | --quiet (-q) Do not output any message. 86 | --verbose (-v|vv|vvv) Increase the verbosity of messages: 1 for normal output, 2 for more verbose output and 3 for debug 87 | --version (-V) Display this application version. 88 | --ansi Force ANSI output. 89 | --no-ansi Disable ANSI output. 90 | --no-interaction (-n) Do not ask any interactive question. 91 | ``` 92 | 93 | ### Profiles 94 | 95 | * default 96 | * blank 97 | * github-sample 98 | 99 | ### Add a profile 100 | 101 | * Put your class that implements `Markbench\ProfileInterface` into `Profile` directory. 102 | * Run `php bin/markbench benchmark --profile=your_profile_name` 103 | 104 | **Feel free to fork and send a pull request!** 105 | 106 | License 107 | ------- 108 | 109 | The MIT License 110 | 111 | Author 112 | ------ 113 | 114 | Kazuyuki Hayashi (@kzykhys) -------------------------------------------------------------------------------- /src/Markbench/Profile/GithubSampleProfile.php: -------------------------------------------------------------------------------- 1 | 9 | */ 10 | class GithubSampleProfile implements ProfileInterface 11 | { 12 | 13 | /** 14 | * Returns the name of this profile 15 | * 16 | * @return mixed 17 | */ 18 | public function getName() 19 | { 20 | return 'github-sample'; 21 | } 22 | 23 | /** 24 | * Describe this profile 25 | * 26 | * @return string 27 | */ 28 | public function getDescription() 29 | { 30 | return 'Sample content from Github (http://github.github.com/github-flavored-markdown/sample_content.html) / 1000 times'; 31 | } 32 | 33 | /** 34 | * Returns markdown content to test 35 | * 36 | * @return string 37 | */ 38 | public function getContent() 39 | { 40 | return << 3 and 2 < 7. Maybe some arrows. 1 -> 2 -> 3. 9 <- 8 <- 7. 78 | 79 | Triangles man! a^2 + b^2 = c^2 80 | 81 | We all like making lists 82 | ------------------------ 83 | 84 | The above header should be an H2 tag. Now, for a list of fruits: 85 | 86 | * Red Apples 87 | * Purple Grapes 88 | * Green Kiwifruits 89 | 90 | Let's get crazy: 91 | 92 | 1. This is a list item with two paragraphs. Lorem ipsum dolor 93 | sit amet, consectetuer adipiscing elit. Aliquam hendrerit 94 | mi posuere lectus. 95 | 96 | Vestibulum enim wisi, viverra nec, fringilla in, laoreet 97 | vitae, risus. Donec sit amet nisl. Aliquam semper ipsum 98 | sit amet velit. 99 | 100 | 2. Suspendisse id sem consectetuer libero luctus adipiscing. 101 | 102 | What about some code **in** a list? That's insane, right? 103 | 104 | 1. In Ruby you can map like this: 105 | 106 | ['a', 'b'].map { |x| x.uppercase } 107 | 108 | 2. In Rails, you can do a shortcut: 109 | 110 | ['a', 'b'].map(&:uppercase) 111 | 112 | Some people seem to like definition lists 113 | 114 |
115 |
Lower cost
116 |
The new version of this product costs significantly less than the previous one!
117 |
Easier to use
118 |
We've changed the product so that it's much easier to use!
119 |
120 | 121 | I am a robot 122 | ------------ 123 | 124 | Maybe you want to print `robot` to the console 1000 times. Why not? 125 | 126 | def robot_invasion 127 | puts("robot " * 1000) 128 | end 129 | 130 | You see, that was formatted as code because it's been indented by four spaces. 131 | 132 | How about we throw some angle braces and ampersands in there? 133 | 134 | 137 | 138 | Set in stone 139 | ------------ 140 | 141 | Preformatted blocks are useful for ASCII art: 142 | 143 |
144 |              ,-.
145 |     ,     ,-.   ,-.
146 |    / \   (   )-(   )
147 |    \ |  ,.>-(   )-<
148 |     \|,' (   )-(   )
149 |      Y ___`-'   `-'
150 |      |/__/   `-'
151 |      |
152 |      |
153 |      |    -hrr-
154 |   ___|_____________
155 | 
156 | 157 | Playing the blame game 158 | ---------------------- 159 | 160 | If you need to blame someone, the best way to do so is by quoting them: 161 | 162 | > I, at any rate, am convinced that He does not throw dice. 163 | 164 | Or perhaps someone a little less eloquent: 165 | 166 | > I wish you'd have given me this written question ahead of time so I 167 | > could plan for it... I'm sure something will pop into my head here in 168 | > the midst of this press conference, with all the pressure of trying to 169 | > come up with answer, but it hadn't yet... 170 | > 171 | > I don't want to sound like 172 | > I have made no mistakes. I'm confident I have. I just haven't - you 173 | > just put me under the spot here, and maybe I'm not as quick on my feet 174 | > as I should be in coming up with one. 175 | 176 | Table for two 177 | ------------- 178 | 179 | 180 | 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 |
IDNameRank
1Tom Preston-WernerAwesome
2Albert EinsteinNearly as awesome
190 | 191 | Crazy linking action 192 | -------------------- 193 | 194 | I get 10 times more traffic from [Google] [1] than from 195 | [Yahoo] [2] or [MSN] [3]. 196 | 197 | [1]: http://google.com/ "Google" 198 | [2]: http://search.yahoo.com/ "Yahoo Search" 199 | [3]: http://search.msn.com/ "MSN Search" 200 | EOF; 201 | 202 | } 203 | 204 | /** 205 | * @return mixed 206 | */ 207 | public function getLoopCount() 208 | { 209 | return 1000; 210 | } 211 | 212 | } -------------------------------------------------------------------------------- /src/Markbench/Console/Command/BenchmarkCommand.php: -------------------------------------------------------------------------------- 1 | 20 | */ 21 | class BenchmarkCommand extends Command 22 | { 23 | 24 | /** 25 | * @var array 26 | */ 27 | private $versions = []; 28 | 29 | /** 30 | * @var \Markbench\DriverInterface[] 31 | */ 32 | private $drivers = []; 33 | 34 | /** 35 | * {@inheritdoc} 36 | */ 37 | public function __construct($name = null) 38 | { 39 | $finder = Finder::create() 40 | ->in(__DIR__ . '/../../Driver') 41 | ->files() 42 | ->name('*Driver.php'); 43 | 44 | foreach ($finder as $file) { 45 | /* @var \Symfony\Component\Finder\SplFileInfo $file */ 46 | $className = str_replace('.php', '', $file->getRelativePathname()); 47 | $fqcn = 'Markbench\\Driver\\' . $className; 48 | $driver = new $fqcn(); 49 | $name = $driver->getName(); 50 | 51 | if ($dialect = $driver->getDialect()) { 52 | $name .= ':' . $dialect; 53 | } 54 | 55 | $this->drivers[$name] = $driver; 56 | } 57 | 58 | parent::__construct($name); 59 | } 60 | 61 | /** 62 | * {@inheritdoc} 63 | */ 64 | protected function configure() 65 | { 66 | $this 67 | ->setName('benchmark') 68 | ->setDescription('Run a benchmark with selected parser and profile') 69 | ->addOption('parser', '', InputOption::VALUE_REQUIRED, 'Name of a parser. Available: ' . implode(', ', array_keys($this->drivers))) 70 | ->addOption('profile', 'p', InputOption::VALUE_OPTIONAL, 'Name of a profile.', 'default') 71 | ; 72 | } 73 | 74 | /** 75 | * {@inheritdoc} 76 | */ 77 | protected function execute(InputInterface $input, OutputInterface $output) 78 | { 79 | $runner = new Runner(); 80 | $this->registerDrivers($runner, $input->getOption('parser')); 81 | 82 | $profiles = $this->getProfiles(); 83 | $profile = $input->getOption('profile'); 84 | 85 | if (!isset($profiles[$profile])) { 86 | $output->writeln('Unknown profile: ' . $profile . ''); 87 | 88 | return 1; 89 | } 90 | 91 | foreach ($runner->getDrivers() as $driver) { 92 | if (!isset($this->versions[$driver->getName()])) { 93 | $this->versions[$driver->getName()] = $this->getVersion($driver->getName()); 94 | } 95 | } 96 | 97 | $output->writeln('Runtime: PHP' . phpversion()); 98 | $output->writeln('Host: ' . php_uname()); 99 | 100 | $this->runProfile($runner, $profiles[$profile], $output); 101 | 102 | return 0; 103 | } 104 | 105 | /** 106 | * @param Runner $runner 107 | * @param ProfileInterface $profile 108 | * @param OutputInterface $output 109 | */ 110 | protected function runProfile(Runner $runner, ProfileInterface $profile, OutputInterface $output) 111 | { 112 | $output->writeln(sprintf('Profile: %s', $profile->getDescription())); 113 | $output->writeln('Class: ' . get_class($profile)); 114 | $output->writeln(''); 115 | 116 | $results = $runner->run($profile->getContent(), $profile->getLoopCount()); 117 | 118 | // order by duration 119 | uasort($results, function (Result $A, Result $B) { 120 | if ($A->getDuration() == $B->getDuration()) { 121 | return 0; 122 | } 123 | 124 | return $A->getDuration() > $B->getDuration() ? 1 : -1; 125 | }); 126 | 127 | /* @var \Symfony\Component\Console\Helper\TableHelper $table */ 128 | $table = $this->getHelper('table'); 129 | 130 | $table->setHeaders(['package', 'version', 'dialect', 'duration (MS)', 'MEM (B)', 'PEAK MEM (B)']); 131 | 132 | foreach ($results as $value) { 133 | $table->addRow( 134 | [ 135 | $value->getName(), 136 | $this->versions[$value->getName()], 137 | $value->getDialect(), 138 | $value->getDuration(), 139 | $value->getMemory(), 140 | $value->getPeakMemory() 141 | ] 142 | ); 143 | } 144 | 145 | $table->render($output); 146 | } 147 | 148 | /** 149 | * @return ProfileInterface[] 150 | */ 151 | protected function getProfiles() 152 | { 153 | $profiles = []; 154 | $finder = Finder::create() 155 | ->in(__DIR__ . '/../../Profile') 156 | ->files() 157 | ->name('*Profile.php'); 158 | 159 | foreach ($finder as $file) { 160 | /* @var \Symfony\Component\Finder\SplFileInfo $file */ 161 | /* @var ProfileInterface $profile */ 162 | 163 | $className = str_replace('.php', '', $file->getRelativePathname()); 164 | $fqcn = 'Markbench\\Profile\\' . $className; 165 | $profile = new $fqcn(); 166 | 167 | $profiles[$profile->getName()] = $profile; 168 | } 169 | 170 | return $profiles; 171 | } 172 | 173 | /** 174 | * @param Runner $runner 175 | * @param string $parserName Name of parser to include drivers for 176 | */ 177 | protected function registerDrivers(Runner $runner, $parserName) 178 | { 179 | foreach ($this->drivers as $driver) { 180 | $matchParser = (!$parserName || in_array($parserName, [ 181 | $driver->getName(), 182 | $driver->getName().':'.$driver->getDialect() 183 | ])); 184 | 185 | if ($matchParser && $driver->checkRequirements()) { 186 | $runner->addDriver($driver); 187 | } 188 | } 189 | } 190 | 191 | /** 192 | * @param $package 193 | * 194 | * @throws \RuntimeException 195 | * @throws \Markbench\Exception\TooMuchPackageFoundException 196 | * @return string 197 | */ 198 | protected function getVersion($package) 199 | { 200 | $json_path = ''; 201 | $json_paths = [ 202 | getcwd() . '/composer.json', 203 | __DIR__.'/../../../../composer.json', 204 | __DIR__.'/../../../../../../composer.json', 205 | ]; 206 | 207 | foreach ($json_paths as $path) { 208 | if (file_exists($path)) { 209 | $json_path = $path; 210 | break; 211 | } 212 | } 213 | 214 | if (!$json_path) { 215 | throw new \RuntimeException('Unable to find composer.json'); 216 | } 217 | 218 | $composer = Factory::create(new NullIO(), $json_path); 219 | $repository = $composer->getRepositoryManager()->getLocalRepository(); 220 | /* @var \Composer\Package\Package[] $packages */ 221 | $packages = $repository->findPackages($package); 222 | 223 | if (($count = count($packages)) > 1) { 224 | throw new TooMuchPackageFoundException(); 225 | } elseif ($count == 0) { 226 | $git = new Git(); 227 | $git->setRepository(dirname($json_path)); 228 | 229 | return $git->describe->tags(); 230 | } else { 231 | return $packages[0]->getPrettyVersion(); 232 | } 233 | } 234 | 235 | } 236 | --------------------------------------------------------------------------------