Press "Enter" to skip to content

Yar 2.1 — new features

Yar (Yet Another RPC framework) is a lightweight PHP RPC framework that supports parallel calls. I built it back at Weibo to optimize Weibo's performance, and Yar's parallel calls were used extensively there to cut down user request time.

Recently, again thanks to the quarantine, I did a round of optimization on Yaf and Yaconf, and today I also finished optimizing Yar (in fact, Yar was written well enough to begin with, so there wasn't much to optimize — ha ha). It also gained two new capabilities. Let me give a quick intro:

Persistent connections

That is, YAR_OPT_PERSISTENT. This option has existed for a while, only it used to be designed for persistence across requests. This time I optimized it into connection keep-alive scoped to the lifetime of a single PHP request.

In other words, when you set YAR_OPT_PERSISTEN to true on a Yar_Client, after an RPC call finishes, Yar won't destroy the connection, which speeds up subsequent RPC calls against the same Client. Let's look at an example:

<?php
function bench($client) {
    $start = microtime(true);
    $client->header("connection");
    echo microtime(true) - $start, "s\n";
}
$client = new Yar_Client("http://remote_host/index.php");

$client->setOpt(YAR_OPT_PERSISTENT, 1);
bench($client);
bench($client);
bench($client);

Output:

0.090613842010498s
0.045492887496948s
0.045512914657593s

As you can see, the second call takes half the time, because the time spent establishing the TCP connection is saved.

Finally, the connection is released entirely after the PHP request ends — that is, it lives for the duration of the PHP request's lifetime, does not span across requests, so there's no need to worry about memory leaks.

However, regrettably, this can't be used to speed up parallel calls:

function callback($retval, $callinfo) {
    global $start;
    if ($callinfo) {
        echo "Num ", $callinfo["sequence"] , " costs: ", microtime(true) - $start, "s\n";
    }
}

Yar_Concurrent_Client::call("http://remote_host/index.php", "header", array("connection"), NULL,  NULL, array(YAR_OPT_PERSISTENT=>1));
$start = microtime(true);
Yar_Concurrent_Client::loop("callback", function($error) { var_dump($error); });
$start = microtime(true);
Yar_Concurrent_Client::loop("callback", function($error) { var_dump($error); });

We find the two calls show no speed-up effect:

Num 1 costs: 0.091023921966553s
Num 1 costs: 0.090677976608276s

This is related to libcurl, which Yar uses under the hood. From libcurl's official docs on curl_multi_add_handle:

When an easy interface is added to a multi handle, it will use a shared connection cache owned by the multi handle. Removing and adding new easy handles will not affect the pool of connections or the ability to do connection re-use.

That is, as soon as I add an opened libcurl cp to the parallel queue via curl_multi_add_handle, that cp will use the shared connection pool managed by libcurl multi itself, and will no longer be affected by whether Yar manages it as persistent. And this "shared" refers to sharing across the whole of a single libcurl multi request process, but in fact, because we fire off all the requests at once, there's never a situation where one request finishes and the next begins — so there's no reuse to speak of.

However, I later observed that on certain versions of libcurl and above — e.g. the 7.58.0 I tested under — a speed-up effect can be seen (this is a local test, so the speed looks much faster):

Num 1 costs: 0.0017080307006836s
Num 1 costs: 0.0010600090026855s

I'll need to look into this more carefully in due course — which version of libcurl the change starts from.

Custom DNS

This one comes from a PR by HuangeChaodian. Basically, sometimes we need to customize the DNS resolution result for a given Host — for example, pointing a hostname to localhost during testing.

Surely we can achieve this by editing the hosts file, but if we can switch it in code, that would be a lot more convenient. Here's an example:

<?php
$client = new Yar_Client("http://remote_host/index.php");

$client->setOpt(YAR_OPT_RESOLVE, "remote_host:80:127.0.0.1");

That way, Yar resolves remote_host to 127.0.0.1, which makes local debugging easier.

Performance optimization

I've now noticed that the performance of the code I write improves noticeably as time passes. Because Yar was written relatively late, there weren't many points left to optimize, so I just did some minor patchups and won't go into detail. 🙂

Finally, 2.1.0 has been released: Yar-2.1.0.

Enjoy!

Be First to Comment

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.