-----BEGIN PGP SIGNED MESSAGE----- Hash: SHA1 =pod A problem that many programmers are encountering these days is a high startup cost for their application. We love reuse, but unfortunately, reuse is not free -- big libraries have to be loaded from disk, code has to be generated (for runtime efficiency), and so on. This is a particular problem for Perl programmers; we are used to writing tiny scripts in Perl that glue our lives together, but as the collective learns more about programming, we are pushed in the direction of using bigger libraries. This is gradually making our tiny scripts start up more slowly. (But, they work better and go together even more quickly, so it's generally a worthwhile tradeoff.) One of the biggest pain points, specifically, is Moose. We all love using it, since it makes our code so easy to write and understand, but it does trade startup time for runtime (and programming-time) efficiency. This is irrelevant for most of the work we do, like web applications, since we start them up maybe once a week. It I relevant for command-line applications, though, since we start those up frequently. If you've ever used C you know that Moose apps can take a while to start up. One solution to this problem is called C. It is most of Moose's syntax sugar, without a deep MOP or as much modularity. Using Mouse means you can say C ( is => 'ro' )> without impacting the startup time of your application. This was OK for a while, but the Moose community is moving too quickly for Mouse to track it. This makes Mouse a Moose-alike, not a drop-in replacement for Moose -- once in a while, someone comes into the Moose irc channel and complains of a bug that we know we fixed months ago; they don't bother to mention that they're actually using I. Mouse really has nothing to do with Moose, it's a totally separate project with a totally separate community now. So if you really like Moose and the MooseX:: modules, you can't just start using Mouse; you need to rewrite your app and possibly port a bunch of MooseX:: modules over to Mouse. This means that Mouse isn't really a solution to startup performance problems with Moose applications; it's simply a decision you can make when you want something kind of like Moose, but really want fast startup at the expense of everything else, including runtime speed. (Sort of like writing your application in Haskell instead of Perl. Both are programming languages, but Haskell is not a drop-in replacement that makes Perl scripts faster; it's its own thing. Mouse is like Moose in that they are both object systems for Perl, but they are different enough for each to be "its own thing".) So anyway, with this in mind, we're left without a solution to the startup problem. I decided to look at other big applications that I use to see how they handle their startup time problems. The apps I spend most of my time using are emacs, L, rxvt-unicode, and xmms2. All of these apps are somewhat big, but need to start up quickly. Emacs needs to start quickly when I want to edit a file in a terminal, conkeror needs to instantly display a URL I pass to it on the command line, rxvt-unicode needs to give me a new terminal when I need one, and xmms needs to skip to the next song as soon as I press enter after typing C. Waiting for an application to start up would not be acceptable in any of these cases. Emacs handles the startup time issue with a client-server architecture. You start C when you log in, and then you get an emacs window by running C or C. This is great; C takes almost 10 seconds to startup while I'm logging in, but C windows come up instantly. A perfect solution to the startup speed problem. Conkeror and urxvt do something similar. They start up, and when they get a message from their command-line app, they perform the action in the original process. (urxvtd/urxvtc, anyway; plain urxvt doesn't do anything special, but who uses plain urxvt?) The xmms2 command-line utility is just a client that talks to the xmms2 server, which is started the first time you use the client, or at login. The common theme is a client-server architecture. You have a server that does the hard stuff, which might cause it to take a while to start up, and you have a client, which doesn't do anything hard, and can startup instantly. At the cost of some extra disk activity at login, the app starts quickly every time you use it. I think this is a fine solution to the problem of slow-starting apps, so I wrote a framework for writing apps like this in Perl. I know there is already L, but it suffers from being too magical. It tries to make an arbitrary script persistent without the script knowing that it's persistent. Like C and friends, sometimes it works, but often it doesn't quite work. Since it's a big ball of XS magic, you are basically fucked if it doesn't work. You can't fine-tune it inside your application; you are stuck with the same behavior for every perl script you ever want to make persistent. I don't like this (or being "basically fucked"), but I do like programming. So I wrote L, which is a library that makes writing persistent command-line applications really easy. It has two parts, the server C, and the client, C. The client does most of the interesting stuff; it reads the environment, current working directory, program name, and so on, and sends that to the server. The server passes this information to your app code, along with pipes representing stdin, stdout, and stderr. The app then runs normally; with the client and the server passing the input and output around as appropriate. As a user, you won't notice that you aren't using the real app directly. As an app author, you won't have to write your own persistence solution or debug any magic. I've actually been working on this for some time, but yesterday I hit the milestone I was waiting for; B. I used L as my example app. The Mouse version of sd, which lazy-loads modules for each command sd can run as that command is run, runs C and begins producing output after about B<0.3 seconds> on my machine. (I wrote a script to see how long it takes for output to arrive; see L.) This is pretty good, you never really worry about C starting up. You type sd, and it does its thing. I then wrote a frontend to sd called C, which uses Moose and loads all of sd's commands at startup time. This takes about 12 seconds. (The time is spent loading the various modules implementing each command; not loading Moose. Moose is not that slow.) I made this frontend use C to handle requests. I then timed how long it took to run the help command via C. The result; for a fully-Moose, non-lazy-loaded request? B<0.2 seconds>. Yes that's right, the Moose version consistently starts producing output sooner than the Mouse version. B The fact that it's faster doesn't really matter. The fact that you can get fast-starting command-line apps with without writing your own custom object system and creating your own object system community is the big deal. (Did I mention that C is I faster? It used to take about 3 seconds per invocation to start; now it is under 0.2 seconds! For various reasons, the persistent version is not in the svn or git repository yet. Ask me on irc if you care, it's a looong story.) If you want to try C yourself, it's pretty easy to get started. Here's an example app that reads from stdin and echoes it back to you: lang:Perl use App::Persistent::Server; my $server = App::Persistent::Server->new( code => sub { while(1){ local $| = 1; print "> "; my $line = <>; print "You said: $line"; exit 0 if $line =~ /quit/; }; }, ); $server->start; exit $server->completion_condvar->wait; (The server opens up a control channel that lets you cleanly stop it, but if you don't care about that, you can just C or whatever.) If you called this echo.pl, just run C. The server will start, and you can connect to it with C: lang:undef $ pclient > Hello You said: Hello > quit You said: quit $ (The "0" exit code is of course relayed back to your shell.) You can connect more than one client at the same time; each client gets its own forked process. (This will be configurable in the future, the goal is to have many interfaces; a code block with pipes you can read/write from/to with AnyEvent, a coro thread with things like @ARGV localized to the thread, a separate process, etc. Each is appropriate for different kinds of apps, and being able to pick the one that suits your use case is the point of having a framework instead of quick hack.) If you want to do something more complicated, the code to cut-n-paste-n-customize is: lang:Perl my $server = App::Persistent::Server->new( code => sub { my $info = shift; # fake environment local %ENV = $info->environment; local $0 = $info->program_name; chdir $info->working_directory; local @ARGV = $info->cmdline_args; # code goes here }, ); This will set up everything as though you were a new program executed from the command-line, rather than a block that the App::Persistent server is running. (All the information comes in the form of an object you can inspect; there is no magic. If you want magic, you can do it yourself in a library. It's up to you.) So this is what I have right now. In the future, I'd like to make the protocol that the client and server speak a bit richer; specifically, I would like to add a command for prompting for input interactively (like Term::ReadLine that will work across the pclient/server connection), and an command for starting a text editor on a given file, in the pclient window. (sd needs both of these things.) I also want to add a feature for sharing a virtual terminal, instead of communicating over sockets. I am not sure how to do this yet, so if you know how to do this, let me know. (screen does it, but I haven't read the code yet. I have read a lot about ptys, but I am not sure how to share them between processes that are in different process groups.) There are also issues with interactive signals; right now these are ignored. (If you hit Control-c, you kill pclient, not the server process that pclient is acting as a surrogate for. This is not the correct behavior.) There is the issue of using AnyEvent after the fork. AnyEvent doesn't have a way to cancel the parent event loop, so it will continue running in the child, which will lead to incorrect behavior. I have hacked around this in C for POE and Event, but not for EV, the loop that I most frequently use. (The obvious solutions seem to make EV segfault!) When I figure this out generically, I will put it in its own module (AnyEventX::Cancel?). Finally, someone should write a pure Perl pclient. The version in the repository is written in Haskell. (pclient actually starts up faster than C, though.) The next client I plan to write is an emacs client, so I can use any persistent app right from emacs, without having to emulate a terminal. Anyway, that's about it. Try it out, tell me what features you need, and then we can spend our time writing applications instead of reimplementing common libraries so they load slightly faster! -----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.9 (GNU/Linux) iEYEARECAAYFAkpxRS0ACgkQ2rw+dVvzZm18aQCfVSENfhcxcyjYe1wt+ylMZjls KWcAnAv0BiOXVmun+V35gYynqBG1e22a =edDv -----END PGP SIGNATURE-----