1

I've been exploring compiling Perl programs as a way to improve the initial startup time on a complex Perl CGI program. @Rurban's detailed overview was very helpful to me. Compiling my main process script yields a faster startup time using perlcc -o launcher launcher.fpl. The resultant process is at least 20% faster -- not overwhelming, but still meaningful.

Why am I trying to compile as oppose to simply optimize further? I've spent months using Devel::NYTProf to catch inefficient code, so I believe I've attacked most of the low hanging optimization fruit. The code is fast, once loaded and it is run via mod_fcgi FastCGI, so it stays persistently loaded. However, if it should happen to crash or if FastCGI needs to spawn a new process (either because the previous one has hit its maximum request level or there is enough demand that another worker is needed), the slow load time rears its ugly head. The first request on a new process may take 1-1.5 seconds, while subsequent requests clock in at under 200ms.

The issue: a lot of the program is in the form of Perl modules that the core "loader" dynamically loads based on what it is doing. The module loaded for the front page of my site is one of the slowest, for example. So, it stands to reason, some of the most important optimization would come from compiling those modules. However, using both perlcc -B Front.pm -o Front.pmc and perl -c Front.pm, I get an unhelpful segfault when I try to run the program:

[root@server code]# ./launcher.fpl
Segmentation fault (core dumped)

The compilation process provides no debug messages to suggest a problem. (To be clear: to keep things simple, in this example, launcher.fpl is not compiled, just straightforward Perl.) If I remove the pmc file so that launcher.fpl can load the uncompiled module once again, it loads without errors.

I've also tried:

perl -MO=Bytecode,-m,-oFront.pmc Front.pm

Which seems to work slightly better but produces this error when I try to run the program:

[root@server cgi-bin]# ./launcher.fpl
Number found where operator expected at /home/user/www/cgi-bin/Front.pm line 1, near "multi0.12"
Local: Sat Jul 10 14:58:18 2021: Dying from error.; At file: /home/user/www/cgi-bin/Front.pm; line: 1; id: 
Error: Unrecognized character \x08; marked by <-- HERE after ulti<-- HERE near column 36 at /home/user/www/cgi-bin/Front.pm line 1.
Compilation failed in require at /home/user/www/cgi-bin/Loader.pm line 117.

Loader.pm line 117 is where the Front.pm(c) module is required:

state $moduleFront = require Front;

This leads to two main questions: (1) am I going about this the right way to begin with and (2) if I am not on the wrong path entirely, is there a way to debug why my module segfaults if it is compiled?

Timothy R. Butler
  • 1,097
  • 7
  • 20
  • I'd rather fix the script so it doesn't crash when another script starts. – choroba Jul 10 '21 at 20:09
  • @choroba, oh, it doesn't crash when another script starts. What I meant was on occasion, something might go wrong and it'll crash. Or, FastCGI restarts the process intentionally after a specified number of uses. I just clarified that. Sorry! – Timothy R. Butler Jul 10 '21 at 20:11

1 Answers1

1

if it should happen to crash or if FastCGI needs to spawn a new process, the slow load time rears its ugly head.

That's not true; it should simply be a fork, which is instantaneous. (And even if it did take time, it would be between requests and not while a client is waiting.) There is no problem to solve here.

If you aren't forking, this is where you should be focusing your efforts. With forking, you can preload the modules your scripts use. The net effect over CGI is not only avoiding the time needed to load perl but the time needed to execute the modules as well.

ikegami
  • 367,544
  • 15
  • 269
  • 518
  • That's curious -- if I have two requests go to the server at the same time, one responds quickly but the second one occurs much more slowly, just as if FastCGI were launching the process from scratch. Is that something else amiss then? I find FastCGI documentation so sparse I _think_ I have that nailed down, but could be wrong. I'm using mod_fcgi on Apache... – Timothy R. Butler Jul 11 '21 at 01:54
  • Re "*just as if FastCGI were launching the process from scratch.*", FastCGI doesn't launch anything. That's the whole point of FastCGI. FastCGI is a protocol that permits an *existing* process to listen to requests from a socket. You surely want multiple listeners, which you achieve by forking *before* accepting the request (i.e. using a prefork server model). – ikegami Jul 11 '21 at 04:49
  • I should clarify: I mean as fast as mod_fcgi on Apache. It spins up listeners when an eligible script is first requested. I can’t seem to get it to make multiple ones out of the gate, though, try as I might with different config settings. So, if two requests hit at once, only one benefits… – Timothy R. Butler Jul 11 '21 at 06:49
  • I actually inquired awhile back on ServerFault about this FastCGI issue, but didn’t get any suggestions: https://serverfault.com/questions/1060978/change-default-number-of-processes-with-fastcgi-on-apache – Timothy R. Butler Jul 11 '21 at 07:49
  • You're saying there's only one process running your script? That's not right, and entirely contradictory to the idea of Fast CGI. "*mod_fcgid is a high performance alternative to mod_cgi or mod_cgid, which starts a sufficient number instances of the CGI program to handle*" – ikegami Jul 11 '21 at 14:35
  • It doesn't look like you can run a proper prefork server with mod_fcgid (assuming that's what you meant). It's far less work for the app if Apache manages the processes, but it does mean that spinning up a new one isn't instantaneous. Even so, it should still have spares running at all times so that shouldn't matter. That's the "sufficient instances" mentioned above. – ikegami Jul 11 '21 at 14:39
  • Thanks for the pointers. Yes, often it only spins up one process. I've been trying to figure out how to force its hand, but haven't been successful thus far when I've changed settings. It sounds like I need to create some sort of independent server for the script instead? – Timothy R. Butler Jul 13 '21 at 16:40
  • My experience is with nginx, which forces the issue. It has built in support for Fast CGI. Just the protocol. It's up to the app to provide its own worker creation and management. – ikegami Jul 13 '21 at 16:45
  • Thanks. Yeah, that's my longterm destination, although I'm trying to get my head around how to have the different fastcgi processes run unprivileged as different users nginx, so I'm using Apache still for it until I get that through my thick head. I've been reading up on PSGI, as well. – Timothy R. Butler Jul 16 '21 at 20:25
  • That really hobbles you. Once a process has been assign to a user, there's no coming back. If you have lots of different users, you best solution might be to use a post fork model. But then it would be very complex to reuse that process. If you don't reuse that process, that means the user's scripts and any modules they use (except those loaded pre-fork) must be compiled/executed for every request, losing some of the gains. – ikegami Jul 16 '21 at 20:31
  • So, if I wanted to achieve something better performance wise, would my best bet be to have it run as its own user and then have it detect which virtual host it was running under and tailor each request appropriately within the script logic? Since each user (using the same script) has a different database, I'd need to have it disconnect and reconnect between requests if the processes were shared between users. I'd guess that script process's user would need to be privileged to read the data of each user it might serve, too... – Timothy R. Butler Jul 16 '21 at 20:48
  • Lots of variables would affect what's best. If you have but a few users, each could run their own daemon managing their own workers. In nginx, you'd dispatch the URLs to the correct daemon based on vhost, url, whatever. – ikegami Jul 16 '21 at 21:57
  • Thanks. That helps -- that's the exact situation. I've seen a few different pieces of sample code floating around for the daemon to interface between NGINX and a Perl script. Do you have one you recommend? – Timothy R. Butler Jul 17 '21 at 00:09