amphp / amp

A non-blocking concurrency framework for PHP applications. 🐘
https://amphp.org/amp
MIT License
4.25k stars 257 forks source link

Comparison with ReactPHP #428

Open abdullahseba opened 10 months ago

abdullahseba commented 10 months ago

I've been trying for some time to get my head around the difference between ReactPHP and AMPHP. I've read https://amphp.org/faq, but this gives a very basic answer and is perhaps subjective. The FAQ says that readability and performance are a focus for AMP. However, I've found React Async more readable (from the docs) and more familiar with what's used in JS. That may be subjective, however, and it would be good to understand the performance benefit AMPHP has over React.

I'm also finding the event loop situation incredibly confusing. From what I gather, AMPHP doesn't use its own loop, but instead, uses Revolt. I'm trying to use Guzzle as a HTTP client as I need support for digest authentication. I settled with Guzzle because I saw it had Async support but then I realised that it uses its own promise implementation. Whereas graphql-php, which I have used before, uses a generic interface that allows you to write your own interface and ships with an adapter for React and previous versions of AMPHP. Adding to the confusion is that the first example in the docs has this piece of code which doesn't appear to use AMPHP at all:


<?php

require __DIR__ . '/vendor/autoload.php';

use Revolt\EventLoop;

$suspension = EventLoop::getSuspension();

EventLoop::delay(5, function () use ($suspension): void {
    print '++ Executing callback created by EventLoop::delay()' . PHP_EOL;

    $suspension->resume(null);
});

print '++ Suspending to event loop...' . PHP_EOL;

$suspension->suspend();

print '++ Script end' . PHP_EOL;

I'm finding this and the docs generally really confusing. A lot of the examples don't work and require additional libraries to be installed which doesn't help either. await() in AMPPHP behaves completely differently to the way it does in JS and React and I don't understand why. When I first read the docs, I assumed await() would await a promise/future to complete in a synchronous-like way. But I eventually realised that it only takes a collection of promises and futures. There's no real explanation of what the difference between a future and a promise is if any. Then there is another example which further confused me:


<?php // Example async producer using DeferredFuture

use Revolt\EventLoop;

function asyncMultiply(int $x, int $y): Future
{
    $deferred = new Amp\DeferredFuture;

    // Complete the async result one second from now
    EventLoop::delay(1, function () use ($deferred, $x, $y) {
        $deferred->complete($x * $y);
    });

    return $deferred->getFuture();
}

$future = asyncMultiply(6, 7);
$result = $future->await();

var_dump($result); // int(42)

Here, await is being used differently and takes no argument but it is not clear at all from the docs that the arguments are optional and in which circumstances.

It would be good to understand what the advantages are of using AMPHP + Revolt vs React Async + React EventLoop, especially how it has better performance and fibre support. My understanding is that both use PHP fibres and don't need promises with chains of .then(). I'm really looking for something that uses fibres only and not the old generator method. The React Async package is only supported by PHP 8.1 onwards but the loop looks to be supported by older versions of PHP. So I'm not sure if that means it doesn't fully utilise fibres. I'm also not sure if React's libraries support fibres like AMPHP's libraries do and if there is a theoretical performance gain there. It would also be good if there was an explanation of the difference between a future and a promise if any. I've spent hours of research on this but can't find anything useful especially as Async is a niche in PHP and most examples relate to JS. Most tutorials and comparisons online and code on GitHub relate to older versions of AMPHP and I can't find any simple apps like a todo list that would help get an idea of how the echo system would work.

bennnjamin commented 10 months ago

I can clear up a few things but one of the maintainers can provide more insight into the internals and comparisons than I can. First, the current amphp version is 3, and most packages have some development or release tag compatible with amphp v3. There is some documentation but not a lot. The best learning resources are reading the tests, examples, and source code of those respective packages. There are almost no community answers (other than Github issues) relating to v3 that I've found.

I'm trying to use Guzzle as a HTTP client as I need support for digest authentication. I settled with Guzzle because I saw it had Async support but then I realised that it uses its own promise implementation.

I've replaced the Guzzle HTTP driver with amphp/http-client but I would recommend just using amp and implementing digest authentication yourself. It will be easier.

await() in AMPPHP behaves completely differently to the way it does in JS and React and I don't understand why. When I first read the docs, I assumed await() would await a promise/future to complete in a synchronous-like way. But I eventually realised that it only takes a collection of promises and futures.

There are two awaits. Maybe the naming could be improved, but their difference is covered in the amp docs https://github.com/amphp/amp. Make sure to read entire page, including the article titled "What color is your function?" referenced because that should help elucidate some of the differences between PHP and JavaScript when it comes to async.

  1. Amp\Future::await() https://github.com/amphp/amp?tab=readme-ov-file#motivation
  2. Amp\Future\await() https://github.com/amphp/amp?tab=readme-ov-file#await

If you look at the source code, you'll see that the await() taking a list of Futures really just calls Future::await() and it's also declared as a function in a namespace whereas the other is an instance method in the Future class.

https://github.com/amphp/amp/blob/e365d653a650296554769e1bfa10c6b8ef94b666/src/Future/functions.php#L146

It would be good to understand what the advantages are of using AMPHP + Revolt vs React Async + React EventLoop

Revolt actually provides an adapter so you don't have to choose. You can run React and Amp libraries on the same Revolt event loop https://github.com/revoltphp/event-loop-adapter-react

most examples relate to JS

When using Amp, I have found it's best to not relate it to JS. You will see similar async programming concepts but you don't have the same language limitations of JS. Calling async or sync functions is identical and so it can ease some burden on the programmer for having to know whether something is async or not. The benefit of fibers is that it will work regardless.

robopzet commented 10 months ago

To take advantage of asynchronous PHP I'm investigating the options. @bennnjamin mentions the What color is your function? reference. It describes side effects of changing code fom synchronous to asynchronous.

AMPHP does not have that specific problem. But as far as I understand, a similar problem exists: you can no longer use any standard PHP I/O functions.

AMPHP has it's own database drivers, HTTP clients, stream implementation. That means existing code in my application or from external packages has to be rewritten if they are called inside an asynchronous function. So , no ORM that uses PDO, or the normal file read/write functions.

Is that correct?

bwoebi commented 10 months ago

@robopzet Yes, but that applies to all asynchronous libraries.

robopzet commented 10 months ago

I see. That's unfortunate.

bennnjamin commented 10 months ago

you can no longer use any standard PHP I/O functions.

You can, they just aren't asynchronous.

That means existing code in my application or from external packages has to be rewritten if they are called inside an asynchronous function

Ideally you would use something like amphp/parallel to avoid blocking the main event loop if you have code that might block for an insignificant period of time.

It's non-trivial, but you can try to replace the PDO driver in an ORM with one provided by Amp. I have done it with a popular ORM but end up abandoning it as most PHP libraries are not written to be used in an async, long-running process and so even if you can get it working you will likely run into other issues.

kelunik commented 10 months ago

Wrapping your code in async doesn't make it magically non-blocking. Blocking IO will keep blocking.

bennnjamin commented 10 months ago

Would there be no point in even wrapping it async? If so, I will update my comment. Would it be correct to say that integrating blocking code in the event loop should be avoided at all costs?

kelunik commented 10 months ago

Blocking IO should generally be avoided our moved into separate threads using amphp/parallel. Sometimes, blocking for a short time might be acceptable for your application, e. g. during initialization.