I'm using a PHP 8.x script to process a series of images, performing various conversions and resizing tasks. To make the most of the server's multiple cores, I employ the pcntl_fork() function to create child processes that can simultaneously handle different images. This means instead of processing images sequentially, each image can be processed concurrently on separate cores.
For instance, if I have 10 images to process and each takes 3 seconds individually, without parallel processing, it would take a total of 30 seconds. However, with parallel processing, all 10 images can finish processing simultaneously in just 3 seconds.
This approach has been effective until we updated to FreeBSD 13.3. After the update, the forked processes no longer distribute across different cores; instead, they all run on a single core. Consequently, if I have 10 forked processes running, each is constrained to using only 10% of a single core, resulting in a 10-fold increase in processing time.
We've conducted tests with FreeBSD versions ranging from 9.x up to 13.2-RELEASE-p11 and found that the issue doesn't occur. Additionally, when using a 13.2 userland and temporarily booting the 13.3 kernel, the problem still doesn't manifest. However, when both the userland and kernel are updated to 13.3, the problem consistently occurs.
Further tests with a fresh installation of FreeBSD 14.0 on a separate system confirm that the issue persists there as well.
We've also ruled out PHP version as a factor, as testing across versions 8.0 to 8.3 yields the same results.
Do you have any insights into what might be causing this issue, or suggestions for resolving it?
Edit: Adding MRE code, as suggested in the comments:
<?php $fullLocalImagePath = "test.jpg"; $forkedProcessIds = array(); for($i = 0; $i < 5; $i++) { $pid = pcntl_fork();// Create a child process if($pid == -1) { exit(-1); // Could not fork the process } else if($pid) { $forkedProcessIds[] = $pid; // Keep track of the child IDs } else { $finalImagePath = "test_result_".$i.".jpg"; // Load the image using Imagick $imagick = new Imagick(); $imagick->readImage($fullLocalImagePath); echo " → Finished reading image $i into Imagick.\n"; $imagick->setImageCompressionQuality(88); $imagick->resizeImage(4800, 4800, imagick::FILTER_LANCZOS, .9, false); $imagick->writeImage($finalImagePath); echo " → → Finished resizing and saving image $i into Imagick.\n"; $imagick->clear(); exit(0); // Exit the child process after processing the image } } // Wait for the forked processes to finish while ($childPID = pcntl_waitpid(0, $status)) { if ($childPID == -1) { // No child processes left to wait for break; } echo " → → → Child process $childPID has finished.\n"; // Handle the exit status based on the child process PID if (in_array($childPID, $forkedProcessIds)) { // Remove the child process ID from the tracking array $forkedProcessIds = array_diff($forkedProcessIds, array($childPID)); } } ?>