Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

When does Zephir Shine? Only 10% Performance Gain? #2119

Closed
ajhalls opened this issue Oct 5, 2020 · 3 comments
Closed

When does Zephir Shine? Only 10% Performance Gain? #2119

ajhalls opened this issue Oct 5, 2020 · 3 comments

Comments

@ajhalls
Copy link

ajhalls commented Oct 5, 2020

I am looking to optimize my web API which receives a steady stream of medical data from hundreds of offices. As a proof of concept, I rewrote a portion of the API in Zephir to build the Patient object.

The Zephir Code:

    #under class Checks
    public static function PhoneOrNull(incoming=NULL)
    {
        if(empty(incoming)){
            return NULL;
        }
        
            if(strlen(incoming)>=10){
                var original_phone = urldecode(incoming);

                var phone = preg_replace("/[^0-9]/", "", original_phone);
                if (strlen(phone) == 10 && str_split(phone)[0] != 1) {
                    return "+1" . phone;
                }
                if (strlen(phone) == 11 && str_split(phone)[0] == 1) {
                    return "+" . phone;
                }

                return original_phone;
           
            }
        return NULL;
    }
    
    #under class Patients
    public static function NewPatient(incoming=NULL)
    {
            if(incoming == NULL){
            return NULL;
            }
            var gender = 0;
            var NewRecord=    [
                    "reference_id" : incoming->reference_id,
                    "parent_id" : Checks::StringOrNull(incoming->parent_id),
                    "family_id" : Checks::StringOrNull(incoming->family_id),
                    "first_name" : incoming->first_name,
                    "last_name" : incoming->last_name,
                    "email" : Checks::StringOrNull(incoming->email),
                    "home_phone" : Checks::PhoneOrNull(incoming->home_phone),
                    "work_phone" : Checks::PhoneOrNull(incoming->work_phone) ,
                    "wireless_phone" : Checks::PhoneOrNull(incoming->wireless_phone) ,
                    "preferred_confirm" : Checks::PhoneOrNull(incoming->preferred_confirm),
                    "preferred_contact" : Checks::PhoneOrNull(incoming->preferred_contact),
                    "preferred_recall" : Checks::PhoneOrNull(incoming->preferred_recall) ,
                    "preferred_confidential" : Checks::PhoneOrNull(incoming->preferred_confidential),
                    "address" : Checks::StringOrNull(incoming->address),
                    "city" : Checks::StringOrNull(incoming->city),
                    "state" : Checks::StringOrNull(incoming->state),
                    "zip" : Checks::StringOrNull(incoming->zip),
                    "birthday" : isset(incoming->birthday) && strtotime(incoming->birthday) > strtotime("-100 years") ? incoming->birthday : null,
                    "gender" : gender,
                    "is_text" : 1,
                    "is_email" : 1,
                    "is_phone" : 1,
                    "is_mail" : 1,
                    "reference_status" : Checks::StringOrNull(incoming->reference_status),
                    "first_visit" : isset(incoming->first_visit) && strtotime(incoming->first_visit) > strtotime("-100 years") ? incoming->first_visit : null,
                    "appointment_total" : Checks::IntOrNull(incoming->appointment_total),
                    "appointment_missed" : Checks::IntOrNull(incoming->appointment_missed),
                    "appointment_show_rate" : Checks::IntOrNull(incoming->appointment_show_rate),
                    "ssn" : isset(incoming->ssn) && strpos(incoming->ssn, "-") === false && (incoming->ssn > 0 && incoming->ssn <= 9999) ? incoming->ssn : null
                ];
                return NewRecord;
    }

Our old way was:

$createPatients[] = new Patient([
                   'reference_id' => $record->reference_id,
                   'parent_id' => isset($record->parent_id) ? $record->parent_id : null,
                   'family_id' => isset($record->family_id) ? $record->family_id : null,
                   'office_id' => $user->office_id,
                   'first_name' => $record->first_name,
                   'last_name' => $record->last_name,
                   'email' => isset($record->email) ? $record->email : null,
                   'home_phone' => isset($record->home_phone) ? databasePhone($record->home_phone) : null,
                   'work_phone' => isset($record->work_phone) ? databasePhone($record->work_phone) : null,
                   'wireless_phone' => isset($record->wireless_phone) ? databasePhone($record->wireless_phone) : null,
                   'preferred_confirm' => isset($record->preferred_confirm) ? databasePhone($record->preferred_confirm) : null,
                   'preferred_contact' => isset($record->preferred_contact) ? databasePhone($record->preferred_contact) : null,
                   'preferred_recall' => isset($record->preferred_recall) ? databasePhone($record->preferred_recall) : null,
                   'preferred_confidential' => isset($record->preferred_confidential) ? databasePhone($record->preferred_confidential) : null,
                   'address' => isset($record->address) ? $record->address : null,
                   'city' => isset($record->city) ? $record->city : null,
                   'state' => isset($record->state) ? $record->state : null,
                   'zip' => isset($record->zip) ? $record->zip : null,
                   'birthday' => isset($record->birthday) && strtotime($record->birthday) > strtotime('-100 years') ? $record->birthday : null,
                   'gender' => $gender,
                   'is_text' => 1,
                   'is_email' => 1,
                   'is_phone' => 1,
                   'is_mail' => 1,
                   'language_id' => $language_id,
                   'communication_id' => $user->office->communication_id,
                   'reference_status' => isset($record->reference_status) ? $record->reference_status : null,
                   'status_type_id' => $type_id,
                   'first_visit' => isset($record->first_visit) && strtotime($record->first_visit) > strtotime('-100 years') ? $record->first_visit : null,
                   'appointment_total' => isset($record->appointment_total) ? $record->appointment_total : null,
                   'appointment_missed' => isset($record->appointment_missed) ? $record->appointment_missed : null,
                   'appointment_show_rate' => isset($record->appointment_show_rate) ? $record->appointment_show_rate : null,
                   'ssn' => isset($record->ssn) && strpos($record->ssn, '-') === false && ($record->ssn > 0 && $record->ssn <= 9999) ? $record->ssn : null,
               ]);

I had stripped out a couple keys in the beginning of the test such as 'communication_id' => $user->office->communication_id which wasn't part of the incoming data just to be simpler. So the Zephir one was doing about 3 less keys than the raw PHP per cycle.

To test, I used the following:

$times=array();
for ($f=0; $f < 50; $f++) { 

    $time_start = microtime(true); 
    for ($i=0; $i < 500; $i++) { 
                      $createPatients[] = new Patient(\Rd\Patients::NewPatient($record));
    }


    $time_end = microtime(true);
    //dividing with 60 will give the execution time in minutes otherwise seconds
    $times[] = ($time_end - $time_start);

}
//execution time of the script
print_r($times);

The average times were:
~0.068428039550781 Seconds - Zephir
~0.076008081436157 Seconds - PHP

I have run the test a number of times and it seems Zephir gives me about a 10% boost, is that expected? Is there something more I should be doing to get better performance gains? Seems like I could almost get that just optimizing what I have a little more.

The tasks I was looking to move over would be string manipulations such as the PhoneOrNull, and those hit most often by the APIs. I am looking to optimize which processes are handling data to minimize calls to the database, so I would be passing in 2 objects, cross comparing values, and only sending updates to the database when records actually change.

Running on Ubuntu 18.04, PHP 7.2, Zephir 0.12.19

@cypherbits
Copy link

Hi, I discovered this project just a few days ago and I thought it was a great idea and could benefit with performance.

But just learned the project is officially abandoned...

Besides, your tests show it actually does not improves performance too much...

You tested on PHP 7.2 while on the latest 7.4 it should be even faster.

Are you using Opcache or Preload in the test?

@ajhalls
Copy link
Author

ajhalls commented Oct 6, 2020

I didn't realize it was abandoned (https://www.reddit.com/r/PHP/comments/iogji4/zephir_will_not_be_maintained_any_longer_and_will/), thank you for letting me know. I wasn't using Opcache or Preload.

I know I can upgrade the PHP for additional performance, part of why I was building this into modules was to be able to whitelabel my application while maintaining some control over it through compiled obfuscation, the other was performance. Looking into other options.

Other options seem to be to build it in Rust and then use PHP FFI, or build a standalone API application server to handle those transformations for me. While I do a fair amount of .NET C# work, I haven't done much with C or C++ and feel that Rust may be more beneficial to learn if I have to learn something new. I realize PHP has a .NET option, but only if you run on a Windows server, which I will not likely ever do.

@Jeckerson
Copy link
Member

Jeckerson commented Feb 3, 2021

@cypherbits

But just learned the project is officially abandoned...

@ajhalls

I didn't realize it was abandoned

Reddit

Zephir will not be maintained any longer and will not be compatible with PHP 8

I completely understand that we live in era of fake news, but on original news was written, quote:
https://blog.phalcon.io/post/the-future-of-phalcon

Now that Serghei has stepped down, we have an issue with Zephir. We no longer have any active maintainers for the project to assume the lead and help with development/bugs etc.

and more important, quoute:

With no other options available, we will put active development of Zephir on hold, and we do not expect it to be compatible with PHP 8. Maintainers are always welcome to step up and help with Zephir.


@ajhalls
Now about performance diff only 10%. You won't gain any extra performance in loops and cycles like yours due array as char and massive overhead, so you have near the same performance in PHP or in C. Single difference will be in memory usage, ~ x2 more in PHP.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

3 participants