Skip to content

Commit

Permalink
corrections, update example, added two utility functions similar to N…
Browse files Browse the repository at this point in the history
…odeJS, Python usage

- `random_uniform` and `timer_for`
- update readme to show php version same as nodejs and python
  • Loading branch information
TheTechsTech committed Mar 1, 2021
1 parent 1dc49e6 commit 5fdf2e1
Show file tree
Hide file tree
Showing 6 changed files with 215 additions and 33 deletions.
51 changes: 48 additions & 3 deletions Coroutine/Core.php
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,49 @@
\define('EOL', \PHP_EOL);
\define('CRLF', "\r\n");

/**
* Returns a random float between two numbers.
*
* Works similar to Python's `random.uniform()`
* @see https://docs.python.org/3/library/random.html#random.uniform
*
* @param int $min
* @param int $max
* @return float
*/
function random_uniform($min, $max)
{
return ($min + \lcg_value() * (\abs($max - $min)));
}

/**
* Return the value (in fractional seconds) of a performance counter, i.e. a clock with the highest
* available resolution to measure a short duration. Using either `hrtime` or system's `microtime`.
*
* @param string $tag
* - A reference point used to set, to get the difference between the results of consecutive calls.
* - Will be cleared/unset on the next consecutive call.
*
* @return float|void
*
* @see https://docs.python.org/3/library/time.html#time.perf_counter
* @see https://nodejs.org/docs/latest-v11.x/api/console.html#console_console_time_label
*/
function timer_for(string $tag = 'perf_counter')
{
global $__timer__;
if (isset($__timer__[$tag])) {
$perf_counter = $__timer__[$tag];
$__timer__[$tag] = null;
unset($GLOBALS['__timer__'][$tag]);
return (float) ($__timer__['hrtime']
? (\hrtime(true) / 1e+9) - $perf_counter
: \microtime(true) - $perf_counter);
}

$__timer__[$tag] = (float) ($__timer__['hrtime'] ? \hrtime(true) / 1e+9 : \microtime(true));
}

/**
* Makes an resolvable function from label name that's callable with `away`
* The passed in `function/callable/task` is wrapped to be `awaitAble`
Expand Down Expand Up @@ -81,7 +124,8 @@ function result($value)
* - This function needs to be prefixed with `yield`
*
* @param Generator|callable $awaitableFunction
* @param mixed $args - if `generator`, $args can hold `customState`, and `customData`
* @param mixed ...$args - if **$awaitableFunction** is `Generator`, $args can hold `customState`, and `customData`
* - for third party code integration.
*
* @return int $task id
*/
Expand Down Expand Up @@ -1481,11 +1525,12 @@ function coroutine_instance(): ?CoroutineInterface

function coroutine_clear()
{
global $__coroutine__;
global $__coroutine__, $__timer__;
if ($__coroutine__ instanceof CoroutineInterface) {
$__coroutine__->setup(false);
unset($GLOBALS['__coroutine__']);
unset($GLOBALS['__coroutine__'], $GLOBALS['__timer__']);
$__coroutine__ = null;
$__timer__ = null;
}
}

Expand Down
4 changes: 2 additions & 2 deletions Coroutine/Coroutine.php
Original file line number Diff line number Diff line change
Expand Up @@ -235,7 +235,7 @@ public function close()
*/
public function __construct()
{
global $__coroutine__;
global $__coroutine__, $__timer__;
$__coroutine__ = $this;
$this->initSignals();

Expand Down Expand Up @@ -272,7 +272,7 @@ public function __construct()
};
}

$this->isHighTimer = \function_exists('hrtime');
$this->isHighTimer = $__timer__['hrtime'] = \function_exists('hrtime');
$this->parallel = new Parallel($this);
$this->taskQueue = new \SplQueue();
}
Expand Down
147 changes: 145 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@

[![Coroutine](https://github.com/symplely/coroutine/workflows/Coroutine/badge.svg)](https://github.com/symplely/coroutine/actions)[![codecov](https://codecov.io/gh/symplely/coroutine/branch/master/graph/badge.svg)](https://codecov.io/gh/symplely/coroutine)[![Codacy Badge](https://api.codacy.com/project/badge/Grade/44a6f32f03194872b7d4cd6a2411ff79)](https://www.codacy.com/app/techno-express/coroutine?utm_source=github.com&utm_medium=referral&utm_content=symplely/coroutine&utm_campaign=Badge_Grade)[![Maintainability](https://api.codeclimate.com/v1/badges/1bfc3497fde67b111a04/maintainability)](https://codeclimate.com/github/symplely/coroutine/maintainability)

> For versions `1.5.x` onward, has features of an PHP extension [UV](https://github.com/bwoebi/php-uv), of **Node.js** [libuv](https://github.com/libuv/libuv) library, see the online [book](https://nikhilm.github.io/uvbook/index.html) for a full tutorial overview.
> For versions `1.5.x` onward, has features of an PHP extension [UV](https://github.com/amphp/ext-uv), of **Node.js** [libuv](https://github.com/libuv/libuv) library, see the online [book](https://nikhilm.github.io/uvbook/index.html) for a full tutorial overview.
> Currently all `libuv` [network](https://github.com/bwoebi/php-uv/issues) `socket/stream/udp/tcp` like features are broken on *Windows*, as such will not be implemented for *Windows*, will continue to use native `stream_select` instead.
> Currently all `libuv` [network](https://github.com/amphp/ext-uv/issues) `socket/stream/udp/tcp` like features are broken on *Windows*, as such will not be implemented for *Windows*, will continue to use native `stream_select` instead.
## Table of Contents

Expand Down Expand Up @@ -52,6 +52,149 @@ This package follows a new paradigm [Behavioral Programming](http://www.wisdom.w

The base overall usage of [Swoole Coroutine](https://www.swoole.co.uk/coroutine), and [FaceBook's Hhvm](https://docs.hhvm.com/hack/asynchronous-operations/introduction) **PHP** follows the same outline implementations as others and put forth here.

To illustrate further take this comparison between **NodeJS** and **Python** from [Intro to Async Concurrency in Python vs. Node.js](https://medium.com/@interfacer/intro-to-async-concurrency-in-python-and-node-js-69315b1e3e36).

```js
// async_scrape.js (tested with node 11.3)
const sleep = ts => new Promise(resolve => setTimeout(resolve, ts * 1000));

async function fetchUrl(url) {
console.log(`~ executing fetchUrl(${url})`);
console.time(`fetchUrl(${url})`);
await sleep(1 + Math.random() * 4);
console.timeEnd(`fetchUrl(${url})`);
return `<em>fake</em> page html for ${url}`;
}

async function analyzeSentiment(html) {
console.log(`~ analyzeSentiment("${html}")`);
console.time(`analyzeSentiment("${html}")`);
await sleep(1 + Math.random() * 4);
const r = {
positive: Math.random()
}
console.timeEnd(`analyzeSentiment("${html}")`);
return r;
}

const urls = [
"https://www.ietf.org/rfc/rfc2616.txt",
"https://en.wikipedia.org/wiki/Asynchronous_I/O",
]
const extractedData = {}

async function handleUrl(url) {
const html = await fetchUrl(url);
extractedData[url] = await analyzeSentiment(html);
}

async function main() {
console.time('elapsed');
await Promise.all(urls.map(handleUrl));
console.timeEnd('elapsed');
}

main()
```

```py
# async_scrape.py (requires Python 3.7+)
import asyncio, random, time

async def fetch_url(url):
print(f"~ executing fetch_url({url})")
t = time.perf_counter()
await asyncio.sleep(random.randint(1, 5))
print(f"time of fetch_url({url}): {time.perf_counter() - t:.2f}s")
return f"<em>fake</em> page html for {url}"

async def analyze_sentiment(html):
print(f"~ executing analyze_sentiment('{html}')")
t = time.perf_counter()
await asyncio.sleep(random.randint(1, 5))
r = {"positive": random.uniform(0, 1)}
print(f"time of analyze_sentiment('{html}'): {time.perf_counter() - t:.2f}s")
return r

urls = [
"https://www.ietf.org/rfc/rfc2616.txt",
"https://en.wikipedia.org/wiki/Asynchronous_I/O",
]
extracted_data = {}

async def handle_url(url):
html = await fetch_url(url)
extracted_data[url] = await analyze_sentiment(html)

async def main():
t = time.perf_counter()
await asyncio.gather(*(handle_url(url) for url in urls))
print("> extracted data:", extracted_data)
print(f"time elapsed: {time.perf_counter() - t:.2f}s")

asyncio.run(main())
```

**Using this package as setout, it's the same simplicity:**

```php
// This is in the examples folder as "async_scrape.php"
include 'vendor/autoload.php';

function fetch_url($url)
{
print("~ executing fetch_url($url)" . \EOL);
\timer_for($url);
yield \sleep_for(\random_uniform(1, 5));
print("time of fetch_url($url): " . \timer_for($url) . 's' . \EOL);
return "<em>fake</em> page html for $url";
};

function analyze_sentiment($html)
{
print("~ executing analyze_sentiment('$html')" . \EOL);
\timer_for($html . '.url');
yield \sleep_for(\random_uniform(1, 5));
$r = "positive: " . \random_uniform(0, 1);
print("time of analyze_sentiment('$html'): " . \timer_for($html . '.url') . 's' . \EOL);
return $r;
};

function handle_url($url)
{
yield;
$extracted_data = [];
$html = yield fetch_url($url);
$extracted_data[$url] = yield analyze_sentiment($html);
return yield $extracted_data;
};

function main()
{
$urls = [
"https://www.ietf.org/rfc/rfc2616.txt",
"https://en.wikipedia.org/wiki/Asynchronous_I/O"
];
$urlID = [];

\timer_for();
foreach ($urls as $url)
$urlID[] = yield \away(handle_url($url));

$result_data = yield \gather($urlID);
foreach ($result_data as $id => $extracted_data) {
echo "> extracted data:";
\print_r($extracted_data);
}

print("time elapsed: " . \timer_for() . 's');
}

\coroutine_run(main());
```

Try recreating this with the other pure *PHP* async implementations, they would need an rewrite first to come close.

-------

A **Coroutine** here are specially crafted functions that are based on __generators__, with the use of `yield` and `yield from`. When used, they **control context**, meaning `capture/release` an application's execution flow.
Expand Down
33 changes: 14 additions & 19 deletions examples/async_scrape.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,9 +41,9 @@
}
async function main() {
console.time('ellapsed');
console.time('elapsed');
await Promise.all(urls.map(handleUrl));
console.timeEnd('ellapsed');
console.timeEnd('elapsed');
}
main()
Expand Down Expand Up @@ -89,29 +89,23 @@
asyncio.run(main())
```
*/
function random_float($min, $max)
{
return ($min + lcg_value() * (abs($max - $min)));
}

function fetch_url($url)
{
print("~ executing fetch_url($url)" . \EOL);
$sleep = random_float(1, 5);
$t = \microtime(true);
yield \sleep_for($sleep);
print("time of fetch_url($url): " . (\microtime(true) - $t) . 's' . \EOL);
return "<em>fake</em> page html for $url" . \EOL;
\timer_for($url);
yield \sleep_for(\random_uniform(1, 5));
print("time of fetch_url($url): " . \timer_for($url) . 's' . \EOL);
return "<em>fake</em> page html for $url";
};

function analyze_sentiment($html)
{
print("~ executing analyze_sentiment('$html')" . \EOL);
$sleep = random_float(1, 5);
$t = \microtime(true);
yield \sleep_for($sleep);
$r = "positive: " . \random_float(0, 1);
print("time of analyze_sentiment('$html'): " . (\microtime(true) - $t) . 's' . \EOL);
\timer_for($html . '.url');
yield \sleep_for(\random_uniform(1, 5));
$r = "positive: " . \random_uniform(0, 1);
print("time of analyze_sentiment('$html'): " . \timer_for($html . '.url') . 's' . \EOL);
return $r;
};

Expand All @@ -132,17 +126,18 @@ function main()
];

$urlID = [];
$t = \microtime(true);

\timer_for();
foreach ($urls as $url)
$urlID[] = yield \away(handle_url($url));

$result_data = yield \gather($urlID);
foreach ($result_data as $id => $extracted_data) {
echo "> extracted data:";
print_r($extracted_data);
\print_r($extracted_data);
}

print("time elapsed: " . ((\microtime(true) - $t)) . 's');
print("time elapsed: " . \timer_for() . 's');
}

\coroutine_run(main());
5 changes: 2 additions & 3 deletions tests/CoroutineSignalerTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,9 @@ public function testSignalsKeepTheLoopRunningAndRemovingItStopsTheLoop()

private function assertRunFasterThan($maxInterval)
{
$start = microtime(true);
\timer_for();
$this->loop->run();
$end = microtime(true);
$interval = $end - $start;
$interval = \timer_for();
$this->assertLessThan($maxInterval, $interval);
}
}
8 changes: 4 additions & 4 deletions tests/KernelTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -224,11 +224,11 @@ public function lapse(int $taskId = null)

public function taskSleepFor()
{
$t0 = \microtime(true);
$done = yield Kernel::sleepFor(1, 'done sleeping');
$t1 = \microtime(true);
\timer_for('true');
$done = yield Kernel::sleepFor(\random_uniform(1, 1), 'done sleeping');
$t1 = \timer_for('true');
$this->assertEquals('done sleeping', $done);
$this->assertGreaterThan(.9, (float) ($t1 - $t0));
$this->assertGreaterThan(.9, $t1);
yield \shutdown();
}

Expand Down

0 comments on commit 5fdf2e1

Please sign in to comment.